diff --git a/pkg/api/api.go b/pkg/api/api.go index 035cbcca..8c345030 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -272,7 +272,7 @@ func (a *StreamplaceAPI) Handler(ctx context.Context) (http.Handler, error) { if err != nil { return nil, err } - linker, err := linking.NewLinker(ctx, bs) + linker, err := linking.NewLinker(ctx, bs, a.StatefulDB, a.CLI) if err != nil { return nil, err } diff --git a/pkg/linking/linking.go b/pkg/linking/linking.go index 4b9d2fed..72b44476 100644 --- a/pkg/linking/linking.go +++ b/pkg/linking/linking.go @@ -3,31 +3,38 @@ package linking import ( "bytes" "context" + "encoding/json" "errors" "fmt" + "log" "net/url" "golang.org/x/net/html" + "stream.place/streamplace/pkg/config" + "stream.place/streamplace/pkg/statedb" "stream.place/streamplace/pkg/streamplace" ) type Linker struct { BaseHTML []byte + sdb *statedb.StatefulDB + cli *config.CLI } -func NewLinker(ctx context.Context, baseHTML []byte) (*Linker, error) { +func NewLinker(ctx context.Context, baseHTML []byte, sdb *statedb.StatefulDB, cli *config.CLI) (*Linker, error) { _, err := html.Parse(bytes.NewReader(baseHTML)) if err != nil { return nil, err } - return &Linker{BaseHTML: baseHTML}, nil + return &Linker{BaseHTML: baseHTML, sdb: sdb, cli: cli}, nil } type PageConfig struct { Title string Metas []MetaTag SentryDSN string + Branding []string } // Define all meta tags in a structured way @@ -37,6 +44,56 @@ type MetaTag struct { Content string } +var BrandingAssetList = [...]string{ + "siteTitle", + "siteDescription", + "primaryColor", + "accentColor", + "defaultStreamer", + "mainLogo", + "favicon", + "sidebarBg", + "legalLinks", +} + +// fetch branding assets for a given broadcaster DID +func (l *Linker) getBrandingAssets(broadcasterDid string) ([]streamplace.BrandingGetBranding_BrandingAsset, error) { + ret := make([]streamplace.BrandingGetBranding_BrandingAsset, 0) + for _, asset := range BrandingAssetList { + blob, err := l.sdb.GetBrandingBlob(broadcasterDid, asset) + if err != nil { + // this can probably include a 'record not found' error, in which case we skip + log.Printf("error fetching branding asset %s for broadcaster %s: %v", asset, broadcasterDid, err) + continue + } + asset := streamplace.BrandingGetBranding_BrandingAsset{ + Key: blob.Key, + MimeType: blob.MimeType, + } + + if blob.Width != nil { + w := int64(*blob.Width) + asset.Width = &w + } + if blob.Height != nil { + h := int64(*blob.Height) + asset.Height = &h + } + + // process based on mime type + if blob.MimeType == "text/plain" { + str := string(blob.Data) + asset.Data = &str + } else { + url := fmt.Sprintf("/xrpc/place.stream.branding.getBlob?key=%s&broadcaster=%s", blob.Key, broadcasterDid) + asset.Url = &url + } + ret = append(ret, asset) + } + + return ret, nil +} + func (l *Linker) GenerateStreamerCard(ctx context.Context, u *url.URL, lsv *streamplace.Livestream_LivestreamView, sentryDSN string) ([]byte, error) { if u == nil { return nil, errors.New("url is nil") @@ -199,6 +256,35 @@ func (l *Linker) GenerateHTML(ctx context.Context, pc *PageConfig) ([]byte, erro }) } + if l.sdb != nil && l.cli != nil { + + branding, err := l.getBrandingAssets("did:web:" + l.cli.BroadcasterHost) + + if err == nil { + + for i := range branding { + val := branding[i] + // + marshalledJson, err := json.Marshal(val) + if err != nil { + fmt.Printf("error marshalling branding asset %s: %v\n", val.Key, err) + continue + } + head.AppendChild(&html.Node{ + Type: html.ElementNode, + Data: "meta", + Attr: []html.Attribute{ + {Key: "name", Val: "internal-brand:" + val.Key}, + {Key: "content", Val: string(marshalledJson)}, + }, + }) + + } + } else { + // log but we should not block rendering + fmt.Printf("error fetching branding assets: %v\n", err) + } + } // Render the HTML to a string var buf bytes.Buffer if err := html.Render(&buf, root); err != nil { diff --git a/pkg/linking/linking_test.go b/pkg/linking/linking_test.go index 8b5c9907..d442f792 100644 --- a/pkg/linking/linking_test.go +++ b/pkg/linking/linking_test.go @@ -29,14 +29,14 @@ func IndexHTML(t *testing.T) []byte { func TestNewLinker(t *testing.T) { index := IndexHTML(t) - linker, err := NewLinker(context.Background(), index) + linker, err := NewLinker(context.Background(), index, nil, nil) require.NoError(t, err) require.NotNil(t, linker) } func TestGenerateLinkCard(t *testing.T) { index := IndexHTML(t) - linker, err := NewLinker(context.Background(), index) + linker, err := NewLinker(context.Background(), index, nil, nil) require.NoError(t, err) require.NotNil(t, linker) diff --git a/pkg/spxrpc/place_stream_branding.go b/pkg/spxrpc/place_stream_branding.go index d5a74a41..4edcc82d 100644 --- a/pkg/spxrpc/place_stream_branding.go +++ b/pkg/spxrpc/place_stream_branding.go @@ -38,7 +38,7 @@ func (s *Server) getBroadcasterID(ctx context.Context, broadcasterDID string) st return s.cli.BroadcasterHost } -func (s *Server) getBrandingBlob(ctx context.Context, broadcasterID, key string) ([]byte, string, *int, *int, error) { +func (s *Server) GetBrandingBlob(ctx context.Context, broadcasterID, key string) ([]byte, string, *int, *int, error) { // cache miss - fetch from db blob, err := s.statefulDB.GetBrandingBlob(broadcasterID, key) if err == gorm.ErrRecordNotFound { @@ -61,7 +61,7 @@ func (s *Server) handlePlaceStreamBrandingGetBlob(ctx context.Context, broadcast // HandlePlaceStreamBrandingGetBlobDirect is the exported version for direct calls func (s *Server) HandlePlaceStreamBrandingGetBlobDirect(ctx context.Context, broadcasterDID string, key string) (io.Reader, error) { broadcasterID := s.getBroadcasterID(ctx, broadcasterDID) - data, _, _, _, err := s.getBrandingBlob(ctx, broadcasterID, key) + data, _, _, _, err := s.GetBrandingBlob(ctx, broadcasterID, key) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func (s *Server) HandlePlaceStreamBrandingGetBrandingDirect(ctx context.Context, // build output assets := make([]*placestreamtypes.BrandingGetBranding_BrandingAsset, 0, len(allKeys)) for key := range allKeys { - data, mimeType, width, height, err := s.getBrandingBlob(ctx, broadcasterID, key) + data, mimeType, width, height, err := s.GetBrandingBlob(ctx, broadcasterID, key) if err != nil { continue // skip if error } @@ -238,7 +238,7 @@ func (s *Server) HandleFaviconICO(c echo.Context) error { broadcasterID := s.cli.BroadcasterHost log.Log(ctx, "fetching favicon", "broadcasterID", broadcasterID) - data, mimeType, _, _, err := s.getBrandingBlob(ctx, "did:web:"+broadcasterID, "favicon") + data, mimeType, _, _, err := s.GetBrandingBlob(ctx, "did:web:"+broadcasterID, "favicon") if err != nil || data == nil { log.Log(ctx, "using fallback favicon", "err", err, "data_nil", data == nil)