cleanup webpost

This commit is contained in:
Michael Black
2026-05-26 11:15:36 -05:00
parent 7f1976ae12
commit c51164786b
3 changed files with 137 additions and 69 deletions
+53
View File
@@ -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
}
+69
View File
@@ -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)
}
}
+15 -69
View File
@@ -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
}
}