cleanup webpost
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -576,76 +576,22 @@ func (srv *Server) WebPost(c echo.Context) error {
|
|||||||
data["canonicalURL"] = canonicalURL
|
data["canonicalURL"] = canonicalURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any undesirable labels are set, the embed will not be included in
|
// Embed-hidden gate, post text, image thumbs, and video metadata are all
|
||||||
// metadata
|
// derived from helpers in jsonld.go so the og:* / twitter:* meta tags
|
||||||
isEmbedHidden := false
|
// and the JSON-LD payload stay in lockstep — Google's Rich Results
|
||||||
for _, label := range postView.Labels {
|
// validator requires og:image and JSON-LD image[] to be byte-identical.
|
||||||
isNeg := label.Neg != nil && *label.Neg
|
isEmbedHidden := postEmbedHidden(postView, hideEmbedLabels)
|
||||||
if hideEmbedLabels[label.Val] && !isNeg {
|
data["postText"] = postRecordText(postView)
|
||||||
isEmbedHidden = true
|
|
||||||
break
|
if thumbs := extractPostMedia(postView, isEmbedHidden); len(thumbs) > 0 {
|
||||||
}
|
data["imgThumbUrls"] = thumbs
|
||||||
}
|
}
|
||||||
|
if vm := extractVideoMeta(postView, isEmbedHidden); vm.URL != "" {
|
||||||
if postView.Record != nil {
|
data["videoUrl"] = vm.URL
|
||||||
postRecord, ok := postView.Record.Val.(*appbsky.FeedPost)
|
data["videoType"] = vm.Type
|
||||||
if ok {
|
if vm.HasSize {
|
||||||
data["postText"] = ExpandPostText(postRecord)
|
data["videoWidth"] = vm.Width
|
||||||
|
data["videoHeight"] = vm.Height
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user