From c51164786b4bc8b851777d15f9800c52fcfb48ba Mon Sep 17 00:00:00 2001 From: Michael Black Date: Tue, 26 May 2026 11:15:36 -0500 Subject: [PATCH] cleanup webpost --- bskyweb/cmd/bskyweb/embedmeta.go | 53 +++++++++++++++++ bskyweb/cmd/bskyweb/embedmeta_test.go | 69 ++++++++++++++++++++++ bskyweb/cmd/bskyweb/server.go | 84 +++++---------------------- 3 files changed, 137 insertions(+), 69 deletions(-) create mode 100644 bskyweb/cmd/bskyweb/embedmeta.go create mode 100644 bskyweb/cmd/bskyweb/embedmeta_test.go diff --git a/bskyweb/cmd/bskyweb/embedmeta.go b/bskyweb/cmd/bskyweb/embedmeta.go new file mode 100644 index 0000000000..d54b20a2e5 --- /dev/null +++ b/bskyweb/cmd/bskyweb/embedmeta.go @@ -0,0 +1,53 @@ +package main + +import ( + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +// Helpers for extracting Open Graph / Twitter Card metadata from post +// embeds. These produce data for og:* and twitter:* meta tags only — they +// are not used by the schema.org JSON-LD output (which lives in jsonld.go). +// +// The og:video tags exist as a separate code path because JSON-LD does not +// currently emit a VideoObject (deferred — requires `duration` from the +// appview that is not exposed today). + +// videoMeta is the metadata needed for og:video meta tags. +type videoMeta struct { + URL string + Type string + Width int64 + Height int64 + HasSize bool +} + +// extractVideoMeta returns og:video metadata for the post if it has a video +// embed, otherwise the zero value. Respects the same embedHidden gate as +// extractPostMedia (in jsonld.go) so og:video and og:image suppression stay +// in sync. +func extractVideoMeta(pv *appbsky.FeedDefs_PostView, embedHidden bool) videoMeta { + if pv == nil || pv.Embed == nil || embedHidden { + return videoMeta{} + } + var v *appbsky.EmbedVideo_View + if pv.Embed.EmbedVideo_View != nil { + v = pv.Embed.EmbedVideo_View + } else if pv.Embed.EmbedRecordWithMedia_View != nil && + pv.Embed.EmbedRecordWithMedia_View.Media != nil && + pv.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View != nil { + v = pv.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View + } + if v == nil || v.Playlist == "" { + return videoMeta{} + } + out := videoMeta{ + URL: v.Playlist, + Type: "application/vnd.apple.mpegurl", + } + if v.AspectRatio != nil { + out.Width = v.AspectRatio.Width + out.Height = v.AspectRatio.Height + out.HasSize = true + } + return out +} diff --git a/bskyweb/cmd/bskyweb/embedmeta_test.go b/bskyweb/cmd/bskyweb/embedmeta_test.go new file mode 100644 index 0000000000..8270520633 --- /dev/null +++ b/bskyweb/cmd/bskyweb/embedmeta_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "testing" + + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +func TestExtractVideoMeta(t *testing.T) { + // Plain post — no video. + pv := makePostView("alice.bsky.social", "did:plc:alice", "abc", "hi") + if vm := extractVideoMeta(pv, false); vm.URL != "" { + t.Errorf("non-video post produced video meta: %+v", vm) + } + + // Video embed with playlist + aspect ratio. + playlist := "https://video.bsky.app/playlist.m3u8" + pv = makePostView("alice.bsky.social", "did:plc:alice", "abc", "watch") + pv.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedVideo_View: &appbsky.EmbedVideo_View{ + Thumbnail: strPtr("https://cdn.bsky.app/thumb@jpg"), + Playlist: playlist, + AspectRatio: &appbsky.EmbedDefs_AspectRatio{Width: 16, Height: 9}, + }, + } + vm := extractVideoMeta(pv, false) + if vm.URL != playlist { + t.Errorf("URL wrong: %v", vm.URL) + } + if vm.Type != "application/vnd.apple.mpegurl" { + t.Errorf("Type wrong: %v", vm.Type) + } + if !vm.HasSize || vm.Width != 16 || vm.Height != 9 { + t.Errorf("aspect ratio not propagated: %+v", vm) + } + + // Hidden embed gate suppresses video too. + if vm := extractVideoMeta(pv, true); vm.URL != "" { + t.Errorf("hidden embed should suppress video meta") + } + + // Video inside record-with-media. + pv2 := makePostView("alice.bsky.social", "did:plc:alice", "abc", "quote w/ video") + pv2.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedRecordWithMedia_View: &appbsky.EmbedRecordWithMedia_View{ + Media: &appbsky.EmbedRecordWithMedia_View_Media{ + EmbedVideo_View: &appbsky.EmbedVideo_View{Playlist: playlist}, + }, + }, + } + vm2 := extractVideoMeta(pv2, false) + if vm2.URL != playlist { + t.Errorf("record-with-media video URL not extracted: %+v", vm2) + } + if vm2.HasSize { + t.Errorf("expected HasSize=false when no aspect ratio") + } + + // Video without playlist — skip entirely. + pv3 := makePostView("alice.bsky.social", "did:plc:alice", "abc", "no playlist") + pv3.Embed = &appbsky.FeedDefs_PostView_Embed{ + EmbedVideo_View: &appbsky.EmbedVideo_View{ + Thumbnail: strPtr("https://cdn.bsky.app/thumb@jpg"), + }, + } + if vm := extractVideoMeta(pv3, false); vm.URL != "" { + t.Errorf("video without playlist should produce empty meta, got %+v", vm) + } +} diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 6f492d584d..d0d728df09 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -576,76 +576,22 @@ func (srv *Server) WebPost(c echo.Context) error { data["canonicalURL"] = canonicalURL } - // If any undesirable labels are set, the embed will not be included in - // metadata - isEmbedHidden := false - for _, label := range postView.Labels { - isNeg := label.Neg != nil && *label.Neg - if hideEmbedLabels[label.Val] && !isNeg { - isEmbedHidden = true - break - } + // Embed-hidden gate, post text, image thumbs, and video metadata are all + // derived from helpers in jsonld.go so the og:* / twitter:* meta tags + // and the JSON-LD payload stay in lockstep — Google's Rich Results + // validator requires og:image and JSON-LD image[] to be byte-identical. + isEmbedHidden := postEmbedHidden(postView, hideEmbedLabels) + data["postText"] = postRecordText(postView) + + if thumbs := extractPostMedia(postView, isEmbedHidden); len(thumbs) > 0 { + data["imgThumbUrls"] = thumbs } - - if postView.Record != nil { - postRecord, ok := postView.Record.Val.(*appbsky.FeedPost) - if ok { - data["postText"] = ExpandPostText(postRecord) - - if !isEmbedHidden && postRecord.Labels != nil && postRecord.Labels.LabelDefs_SelfLabels != nil { - for _, label := range postRecord.Labels.LabelDefs_SelfLabels.Values { - if hideEmbedLabels[label.Val] { - isEmbedHidden = true - break - } - } - } - } - } - - if postView.Embed != nil && !isEmbedHidden { - hasImages := postView.Embed.EmbedImages_View != nil - hasVideo := postView.Embed.EmbedVideo_View != nil - hasMedia := postView.Embed.EmbedRecordWithMedia_View != nil && postView.Embed.EmbedRecordWithMedia_View.Media != nil - hasMediaImages := hasMedia && postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View != nil - hasMediaVideo := hasMedia && postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View != nil - - if hasImages { - var thumbUrls []string - for i := range postView.Embed.EmbedImages_View.Images { - thumbUrls = append(thumbUrls, postView.Embed.EmbedImages_View.Images[i].Thumb) - } - data["imgThumbUrls"] = thumbUrls - } else if hasVideo { - if postView.Embed.EmbedVideo_View.Thumbnail != nil { - data["imgThumbUrls"] = []string{*postView.Embed.EmbedVideo_View.Thumbnail} - } - if postView.Embed.EmbedVideo_View.Playlist != "" { - data["videoUrl"] = postView.Embed.EmbedVideo_View.Playlist - data["videoType"] = "application/vnd.apple.mpegurl" - if postView.Embed.EmbedVideo_View.AspectRatio != nil { - data["videoWidth"] = postView.Embed.EmbedVideo_View.AspectRatio.Width - data["videoHeight"] = postView.Embed.EmbedVideo_View.AspectRatio.Height - } - } - } else if hasMediaImages { - var thumbUrls []string - for i := range postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View.Images { - thumbUrls = append(thumbUrls, postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View.Images[i].Thumb) - } - data["imgThumbUrls"] = thumbUrls - } else if hasMediaVideo { - if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Thumbnail != nil { - data["imgThumbUrls"] = []string{*postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Thumbnail} - } - if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Playlist != "" { - data["videoUrl"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.Playlist - data["videoType"] = "application/vnd.apple.mpegurl" - if postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio != nil { - data["videoWidth"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio.Width - data["videoHeight"] = postView.Embed.EmbedRecordWithMedia_View.Media.EmbedVideo_View.AspectRatio.Height - } - } + if vm := extractVideoMeta(postView, isEmbedHidden); vm.URL != "" { + data["videoUrl"] = vm.URL + data["videoType"] = vm.Type + if vm.HasSize { + data["videoWidth"] = vm.Width + data["videoHeight"] = vm.Height } }