Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more social card tweaks, and include in RSS as well (#2599)

* move link expander to new file, add test, refactor a bit

* text formatting: include indication if a quote post exists

* rss: include expanded links

authored by

bnewbold and committed by
GitHub
a2f49bb0 c58e6500

+166 -45
+57
bskyweb/cmd/bskyweb/formating.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "slices" 6 + "strings" 7 + 8 + appbsky "github.com/bluesky-social/indigo/api/bsky" 9 + ) 10 + 11 + // Function to expand shortened links in rich text back to full urls, replacing shortened urls in social card meta tags and the noscript output. 12 + // 13 + // This essentially reverses the effect of the typescript function `shortenLinks()` in `src/lib/strings/rich-text-manip.ts` 14 + func ExpandPostText(post *appbsky.FeedPost) string { 15 + postText := post.Text 16 + var charsAdded int = 0 17 + // iterate over facets, check if they're link facets, and if found, grab the uri 18 + for _, facet := range post.Facets { 19 + linkUri := "" 20 + if slices.ContainsFunc(facet.Features, func(feat *appbsky.RichtextFacet_Features_Elem) bool { 21 + if feat.RichtextFacet_Link == nil || feat.RichtextFacet_Link.LexiconTypeID != "app.bsky.richtext.facet#link" { 22 + return false 23 + } 24 + 25 + // bail out if bounds checks fail 26 + if int(facet.Index.ByteStart)+charsAdded > len(postText) || int(facet.Index.ByteEnd)+charsAdded > len(postText) { 27 + return false 28 + } 29 + linkText := postText[int(facet.Index.ByteStart)+charsAdded : int(facet.Index.ByteEnd)+charsAdded] 30 + linkUri = feat.RichtextFacet_Link.Uri 31 + 32 + // only expand uris that have been shortened (as opposed to those with non-uri anchor text) 33 + if strings.HasSuffix(linkText, "...") && strings.Contains(linkUri, linkText[0:len(linkText)-3]) { 34 + return true 35 + } 36 + return false 37 + }) { 38 + // replace the shortened uri with the full length one from the facet using utf8 byte offsets 39 + // NOTE: we already did bounds check above 40 + postText = postText[0:int(facet.Index.ByteStart)+charsAdded] + linkUri + postText[int(facet.Index.ByteEnd)+charsAdded:] 41 + charsAdded += len(linkUri) - int(facet.Index.ByteEnd-facet.Index.ByteStart) 42 + } 43 + } 44 + // if the post has an embeded link and its url doesn't already appear in postText, append it to 45 + // the end to avoid social cards with missing links 46 + if post.Embed != nil && post.Embed.EmbedExternal != nil && post.Embed.EmbedExternal.External != nil { 47 + externalURI := post.Embed.EmbedExternal.External.Uri 48 + if !strings.Contains(postText, externalURI) { 49 + postText = fmt.Sprintf("%s\n%s", postText, externalURI) 50 + } 51 + } 52 + // TODO: could embed the actual post text? 53 + if post.Embed != nil && (post.Embed.EmbedRecord != nil || post.Embed.EmbedRecordWithMedia != nil) { 54 + postText = fmt.Sprintf("%s\n\n[contains quote post or other embeded content]", postText) 55 + } 56 + return postText 57 + }
+39
bskyweb/cmd/bskyweb/formatting_test.go
··· 1 + package main 2 + 3 + import ( 4 + "encoding/json" 5 + "io" 6 + "os" 7 + "strings" 8 + "testing" 9 + 10 + appbsky "github.com/bluesky-social/indigo/api/bsky" 11 + ) 12 + 13 + func loadPost(t *testing.T, p string) appbsky.FeedPost { 14 + 15 + f, err := os.Open(p) 16 + if err != nil { 17 + t.Fatal(err) 18 + } 19 + defer func() { _ = f.Close() }() 20 + 21 + postBytes, err := io.ReadAll(f) 22 + if err != nil { 23 + t.Fatal(err) 24 + } 25 + var post appbsky.FeedPost 26 + if err := json.Unmarshal(postBytes, &post); err != nil { 27 + t.Fatal(err) 28 + } 29 + return post 30 + } 31 + 32 + func TestExpandPostText(t *testing.T) { 33 + post := loadPost(t, "testdata/atproto_embed_post.json") 34 + 35 + text := ExpandPostText(&post) 36 + if !strings.Contains(text, "https://github.com/snarfed/bridgy-fed") { 37 + t.Fail() 38 + } 39 + }
+5 -2
bskyweb/cmd/bskyweb/rss.go
··· 96 96 if err != nil { 97 97 return err 98 98 } 99 - rec := p.Post.Record.Val.(*appbsky.FeedPost) 99 + rec, ok := p.Post.Record.Val.(*appbsky.FeedPost) 100 + if !ok { 101 + continue 102 + } 100 103 // only top-level posts in RSS (no replies) 101 104 if rec.Reply != nil { 102 105 continue ··· 108 111 } 109 112 posts = append(posts, Item{ 110 113 Link: fmt.Sprintf("https://%s/profile/%s/post/%s", req.Host, pv.Handle, aturi.RecordKey().String()), 111 - Description: rec.Text, 114 + Description: ExpandPostText(rec), 112 115 PubDate: pubDate, 113 116 GUID: ItemGUID{ 114 117 Value: aturi.String(),
+5 -43
bskyweb/cmd/bskyweb/server.go
··· 10 10 "net/http" 11 11 "os" 12 12 "os/signal" 13 - "slices" 14 13 "strings" 15 14 "syscall" 16 15 "time" ··· 353 352 } 354 353 } 355 354 356 - data["postText"] = expandPostLinks(postView) 357 - 358 - return c.Render(http.StatusOK, "post.html", data) 359 - } 360 - 361 - // function to expand shortened links in rich text back to full urls, replacing shortened urls in 362 - // social card meta tags and the noscript output. this essentially reverses the effect 363 - // of shortenLinks() in src/lib/strings/rich-text-manip.ts 364 - func expandPostLinks(postView *appbsky.FeedDefs_PostView) string { 365 355 if postView.Record != nil { 366 - rec := postView.Record.Val.(*appbsky.FeedPost) 367 - postText := rec.Text 368 - var charsAdded int64 = 0 369 - // iterate over facets, check if they're link facets, and if found, grab the uri 370 - for _, facet := range rec.Facets { 371 - linkUri := "" 372 - if slices.ContainsFunc(facet.Features, func(feat *appbsky.RichtextFacet_Features_Elem) bool { 373 - if feat.RichtextFacet_Link != nil && feat.RichtextFacet_Link.LexiconTypeID == "app.bsky.richtext.facet#link" { 374 - linkUri = feat.RichtextFacet_Link.Uri 375 - // only expand uris that have been shortened (as opposed to those with non-uri anchor text) 376 - if int64(len(postText)) >= facet.Index.ByteEnd+charsAdded && 377 - strings.HasSuffix(postText[facet.Index.ByteStart+charsAdded:facet.Index.ByteEnd+charsAdded], "...") && 378 - strings.Contains(linkUri, postText[facet.Index.ByteStart+charsAdded:(facet.Index.ByteEnd+charsAdded)-3]) { 379 - return true 380 - } 381 - } 382 - return false 383 - }) { 384 - // replace the shortened uri with the full length one from the facet using utf8 byte offsets 385 - if int64(len(postText)) >= facet.Index.ByteEnd+charsAdded { 386 - postText = postText[0:facet.Index.ByteStart+charsAdded] + linkUri + postText[facet.Index.ByteEnd+charsAdded:] 387 - charsAdded += int64(len(linkUri)) - (facet.Index.ByteEnd - facet.Index.ByteStart) 388 - } 389 - } 356 + postRecord, ok := postView.Record.Val.(*appbsky.FeedPost) 357 + if ok { 358 + data["postText"] = ExpandPostText(postRecord) 390 359 } 391 - // if the post has an embeded link and its url doesn't already appear in postText, append it to 392 - // the end to avoid social cards with missing links 393 - if postView.Embed != nil && 394 - postView.Embed.EmbedExternal_View != nil && 395 - !strings.Contains(postText, postView.Embed.EmbedExternal_View.External.Uri) { 396 - postText = fmt.Sprintf("%s\n%s", postText, postView.Embed.EmbedExternal_View.External.Uri) 397 - } 398 - return postText 399 360 } 400 - return "" 361 + 362 + return c.Render(http.StatusOK, "post.html", data) 401 363 } 402 364 403 365 func (srv *Server) WebProfile(c echo.Context) error {
+60
bskyweb/cmd/bskyweb/testdata/atproto_embed_post.json
··· 1 + { 2 + "$type": "app.bsky.feed.post", 3 + "createdAt": "2023-12-04T19:30:03.024Z", 4 + "embed": { 5 + "$type": "app.bsky.embed.external", 6 + "external": { 7 + "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.", 8 + "thumb": { 9 + "$type": "blob", 10 + "ref": { 11 + "$link": "bafkreidplhjcnrl2c74r3xs7nh7k7q3ny6ul7cgxr2fophblvdeky6t64e" 12 + }, 13 + "mimeType": "image/jpeg", 14 + "size": 347998 15 + }, 16 + "title": "GitHub - snarfed/bridgy-fed: 🕸 Bridges the IndieWeb to Mastodon and the fediverse via ActivityPub...", 17 + "uri": "https://github.com/snarfed/bridgy-fed" 18 + } 19 + }, 20 + "facets": [ 21 + { 22 + "features": [ 23 + { 24 + "$type": "app.bsky.richtext.facet#link", 25 + "uri": "https://github.com/snarfed/bridgy-fed" 26 + } 27 + ], 28 + "index": { 29 + "byteEnd": 92, 30 + "byteStart": 66 31 + } 32 + }, 33 + { 34 + "features": [ 35 + { 36 + "$type": "app.bsky.richtext.facet#mention", 37 + "did": "did:plc:fdme4gb7mu7zrie7peay7tst" 38 + } 39 + ], 40 + "index": { 41 + "byteEnd": 149, 42 + "byteStart": 137 43 + } 44 + } 45 + ], 46 + "langs": [ 47 + "en" 48 + ], 49 + "reply": { 50 + "parent": { 51 + "cid": "bafyreifaidyl62p4snkdwsygviemsxyidi3cd7dxvjomh5644sovxhsppa", 52 + "uri": "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3kfqklhpalh2c" 53 + }, 54 + "root": { 55 + "cid": "bafyreibiimdwmsp5mqpm7utqcdmvo6fdqmofblp5obs3h7ub6652zyooci", 56 + "uri": "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3kfqkkjdkic2e" 57 + } 58 + }, 59 + "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!" 60 + }