diff options
Diffstat (limited to 'bskyweb/cmd')
-rw-r--r-- | bskyweb/cmd/bskyweb/formating.go | 57 | ||||
-rw-r--r-- | bskyweb/cmd/bskyweb/formatting_test.go | 39 | ||||
-rw-r--r-- | bskyweb/cmd/bskyweb/rss.go | 9 | ||||
-rw-r--r-- | bskyweb/cmd/bskyweb/server.go | 27 | ||||
-rw-r--r-- | bskyweb/cmd/bskyweb/testdata/atproto_embed_post.json | 60 |
5 files changed, 184 insertions, 8 deletions
diff --git a/bskyweb/cmd/bskyweb/formating.go b/bskyweb/cmd/bskyweb/formating.go new file mode 100644 index 000000000..023ba3f51 --- /dev/null +++ b/bskyweb/cmd/bskyweb/formating.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "slices" + "strings" + + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +// Function to expand shortened links in rich text back to full urls, replacing shortened urls in social card meta tags and the noscript output. +// +// This essentially reverses the effect of the typescript function `shortenLinks()` in `src/lib/strings/rich-text-manip.ts` +func ExpandPostText(post *appbsky.FeedPost) string { + postText := post.Text + var charsAdded int = 0 + // iterate over facets, check if they're link facets, and if found, grab the uri + for _, facet := range post.Facets { + linkUri := "" + if slices.ContainsFunc(facet.Features, func(feat *appbsky.RichtextFacet_Features_Elem) bool { + if feat.RichtextFacet_Link == nil || feat.RichtextFacet_Link.LexiconTypeID != "app.bsky.richtext.facet#link" { + return false + } + + // bail out if bounds checks fail + if int(facet.Index.ByteStart)+charsAdded > len(postText) || int(facet.Index.ByteEnd)+charsAdded > len(postText) { + return false + } + linkText := postText[int(facet.Index.ByteStart)+charsAdded : int(facet.Index.ByteEnd)+charsAdded] + linkUri = feat.RichtextFacet_Link.Uri + + // only expand uris that have been shortened (as opposed to those with non-uri anchor text) + if strings.HasSuffix(linkText, "...") && strings.Contains(linkUri, linkText[0:len(linkText)-3]) { + return true + } + return false + }) { + // replace the shortened uri with the full length one from the facet using utf8 byte offsets + // NOTE: we already did bounds check above + postText = postText[0:int(facet.Index.ByteStart)+charsAdded] + linkUri + postText[int(facet.Index.ByteEnd)+charsAdded:] + charsAdded += len(linkUri) - int(facet.Index.ByteEnd-facet.Index.ByteStart) + } + } + // if the post has an embeded link and its url doesn't already appear in postText, append it to + // the end to avoid social cards with missing links + if post.Embed != nil && post.Embed.EmbedExternal != nil && post.Embed.EmbedExternal.External != nil { + externalURI := post.Embed.EmbedExternal.External.Uri + if !strings.Contains(postText, externalURI) { + postText = fmt.Sprintf("%s\n%s", postText, externalURI) + } + } + // TODO: could embed the actual post text? + if post.Embed != nil && (post.Embed.EmbedRecord != nil || post.Embed.EmbedRecordWithMedia != nil) { + postText = fmt.Sprintf("%s\n\n[contains quote post or other embeded content]", postText) + } + return postText +} diff --git a/bskyweb/cmd/bskyweb/formatting_test.go b/bskyweb/cmd/bskyweb/formatting_test.go new file mode 100644 index 000000000..1fbf8d5ee --- /dev/null +++ b/bskyweb/cmd/bskyweb/formatting_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "encoding/json" + "io" + "os" + "strings" + "testing" + + appbsky "github.com/bluesky-social/indigo/api/bsky" +) + +func loadPost(t *testing.T, p string) appbsky.FeedPost { + + f, err := os.Open(p) + if err != nil { + t.Fatal(err) + } + defer func() { _ = f.Close() }() + + postBytes, err := io.ReadAll(f) + if err != nil { + t.Fatal(err) + } + var post appbsky.FeedPost + if err := json.Unmarshal(postBytes, &post); err != nil { + t.Fatal(err) + } + return post +} + +func TestExpandPostText(t *testing.T) { + post := loadPost(t, "testdata/atproto_embed_post.json") + + text := ExpandPostText(&post) + if !strings.Contains(text, "https://github.com/snarfed/bridgy-fed") { + t.Fail() + } +} diff --git a/bskyweb/cmd/bskyweb/rss.go b/bskyweb/cmd/bskyweb/rss.go index 64e67fd22..76689abb5 100644 --- a/bskyweb/cmd/bskyweb/rss.go +++ b/bskyweb/cmd/bskyweb/rss.go @@ -80,7 +80,7 @@ func (srv *Server) WebProfileRSS(c echo.Context) error { } } - af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, did.String(), "", "", 30) + af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, did.String(), "", "posts_no_replies", 30) if err != nil { log.Warn("failed to fetch author feed", "did", did, "err", err) return err @@ -96,7 +96,10 @@ func (srv *Server) WebProfileRSS(c echo.Context) error { if err != nil { return err } - rec := p.Post.Record.Val.(*appbsky.FeedPost) + rec, ok := p.Post.Record.Val.(*appbsky.FeedPost) + if !ok { + continue + } // only top-level posts in RSS (no replies) if rec.Reply != nil { continue @@ -108,7 +111,7 @@ func (srv *Server) WebProfileRSS(c echo.Context) error { } posts = append(posts, Item{ Link: fmt.Sprintf("https://%s/profile/%s/post/%s", req.Host, pv.Handle, aturi.RecordKey().String()), - Description: rec.Text, + Description: ExpandPostText(rec), PubDate: pubDate, GUID: ItemGUID{ Value: aturi.String(), diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 7a3b8bf16..8e7d618c2 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -193,6 +193,7 @@ func serve(cctx *cli.Context) error { e.GET("/settings/home-feed", server.WebGeneric) e.GET("/settings/saved-feeds", server.WebGeneric) e.GET("/settings/threads", server.WebGeneric) + e.GET("/settings/external-embeds", server.WebGeneric) e.GET("/sys/debug", server.WebGeneric) e.GET("/sys/log", server.WebGeneric) e.GET("/support", server.WebGeneric) @@ -335,13 +336,29 @@ func (srv *Server) WebPost(c echo.Context) error { postView := tpv.Thread.FeedDefs_ThreadViewPost.Post data["postView"] = postView data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) - if postView.Embed != nil && postView.Embed.EmbedImages_View != nil { - var thumbUrls []string - for i := range postView.Embed.EmbedImages_View.Images { - thumbUrls = append(thumbUrls, postView.Embed.EmbedImages_View.Images[i].Thumb) + if postView.Embed != nil { + if postView.Embed.EmbedImages_View != nil { + 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 postView.Embed.EmbedRecordWithMedia_View != nil && postView.Embed.EmbedRecordWithMedia_View.Media != nil && postView.Embed.EmbedRecordWithMedia_View.Media.EmbedImages_View != nil { + 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 + } + } + + if postView.Record != nil { + postRecord, ok := postView.Record.Val.(*appbsky.FeedPost) + if ok { + data["postText"] = ExpandPostText(postRecord) } - data["imgThumbUrls"] = thumbUrls } + return c.Render(http.StatusOK, "post.html", data) } diff --git a/bskyweb/cmd/bskyweb/testdata/atproto_embed_post.json b/bskyweb/cmd/bskyweb/testdata/atproto_embed_post.json new file mode 100644 index 000000000..2e54854ee --- /dev/null +++ b/bskyweb/cmd/bskyweb/testdata/atproto_embed_post.json @@ -0,0 +1,60 @@ +{ + "$type": "app.bsky.feed.post", + "createdAt": "2023-12-04T19:30:03.024Z", + "embed": { + "$type": "app.bsky.embed.external", + "external": { + "description": "🕸 Bridges the IndieWeb to Mastodon and the fediverse via ActivityPub. - GitHub - snarfed/bridgy-fed: 🕸 Bridges the IndieWeb to Mastodon and the fediverse via ActivityPub.", + "thumb": { + "$type": "blob", + "ref": { + "$link": "bafkreidplhjcnrl2c74r3xs7nh7k7q3ny6ul7cgxr2fophblvdeky6t64e" + }, + "mimeType": "image/jpeg", + "size": 347998 + }, + "title": "GitHub - snarfed/bridgy-fed: 🕸 Bridges the IndieWeb to Mastodon and the fediverse via ActivityPub...", + "uri": "https://github.com/snarfed/bridgy-fed" + } + }, + "facets": [ + { + "features": [ + { + "$type": "app.bsky.richtext.facet#link", + "uri": "https://github.com/snarfed/bridgy-fed" + } + ], + "index": { + "byteEnd": 92, + "byteStart": 66 + } + }, + { + "features": [ + { + "$type": "app.bsky.richtext.facet#mention", + "did": "did:plc:fdme4gb7mu7zrie7peay7tst" + } + ], + "index": { + "byteEnd": 149, + "byteStart": 137 + } + } + ], + "langs": [ + "en" + ], + "reply": { + "parent": { + "cid": "bafyreifaidyl62p4snkdwsygviemsxyidi3cd7dxvjomh5644sovxhsppa", + "uri": "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3kfqklhpalh2c" + }, + "root": { + "cid": "bafyreibiimdwmsp5mqpm7utqcdmvo6fdqmofblp5obs3h7ub6652zyooci", + "uri": "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3kfqkkjdkic2e" + } + }, + "text": "Bridgy Fed is an open-source project — check out the code here: github.com/snarfed/brid...\n\nStay updated with the project by following @snarfed.org!" +} |