···11+package main
22+33+import (
44+ "fmt"
55+ "slices"
66+ "strings"
77+88+ appbsky "github.com/bluesky-social/indigo/api/bsky"
99+)
1010+1111+// Function to expand shortened links in rich text back to full urls, replacing shortened urls in social card meta tags and the noscript output.
1212+//
1313+// This essentially reverses the effect of the typescript function `shortenLinks()` in `src/lib/strings/rich-text-manip.ts`
1414+func ExpandPostText(post *appbsky.FeedPost) string {
1515+ postText := post.Text
1616+ var charsAdded int = 0
1717+ // iterate over facets, check if they're link facets, and if found, grab the uri
1818+ for _, facet := range post.Facets {
1919+ linkUri := ""
2020+ if slices.ContainsFunc(facet.Features, func(feat *appbsky.RichtextFacet_Features_Elem) bool {
2121+ if feat.RichtextFacet_Link == nil || feat.RichtextFacet_Link.LexiconTypeID != "app.bsky.richtext.facet#link" {
2222+ return false
2323+ }
2424+2525+ // bail out if bounds checks fail
2626+ if int(facet.Index.ByteStart)+charsAdded > len(postText) || int(facet.Index.ByteEnd)+charsAdded > len(postText) {
2727+ return false
2828+ }
2929+ linkText := postText[int(facet.Index.ByteStart)+charsAdded : int(facet.Index.ByteEnd)+charsAdded]
3030+ linkUri = feat.RichtextFacet_Link.Uri
3131+3232+ // only expand uris that have been shortened (as opposed to those with non-uri anchor text)
3333+ if strings.HasSuffix(linkText, "...") && strings.Contains(linkUri, linkText[0:len(linkText)-3]) {
3434+ return true
3535+ }
3636+ return false
3737+ }) {
3838+ // replace the shortened uri with the full length one from the facet using utf8 byte offsets
3939+ // NOTE: we already did bounds check above
4040+ postText = postText[0:int(facet.Index.ByteStart)+charsAdded] + linkUri + postText[int(facet.Index.ByteEnd)+charsAdded:]
4141+ charsAdded += len(linkUri) - int(facet.Index.ByteEnd-facet.Index.ByteStart)
4242+ }
4343+ }
4444+ // if the post has an embeded link and its url doesn't already appear in postText, append it to
4545+ // the end to avoid social cards with missing links
4646+ if post.Embed != nil && post.Embed.EmbedExternal != nil && post.Embed.EmbedExternal.External != nil {
4747+ externalURI := post.Embed.EmbedExternal.External.Uri
4848+ if !strings.Contains(postText, externalURI) {
4949+ postText = fmt.Sprintf("%s\n%s", postText, externalURI)
5050+ }
5151+ }
5252+ // TODO: could embed the actual post text?
5353+ if post.Embed != nil && (post.Embed.EmbedRecord != nil || post.Embed.EmbedRecordWithMedia != nil) {
5454+ postText = fmt.Sprintf("%s\n\n[contains quote post or other embeded content]", postText)
5555+ }
5656+ return postText
5757+}
+39
bskyweb/cmd/bskyweb/formatting_test.go
···11+package main
22+33+import (
44+ "encoding/json"
55+ "io"
66+ "os"
77+ "strings"
88+ "testing"
99+1010+ appbsky "github.com/bluesky-social/indigo/api/bsky"
1111+)
1212+1313+func loadPost(t *testing.T, p string) appbsky.FeedPost {
1414+1515+ f, err := os.Open(p)
1616+ if err != nil {
1717+ t.Fatal(err)
1818+ }
1919+ defer func() { _ = f.Close() }()
2020+2121+ postBytes, err := io.ReadAll(f)
2222+ if err != nil {
2323+ t.Fatal(err)
2424+ }
2525+ var post appbsky.FeedPost
2626+ if err := json.Unmarshal(postBytes, &post); err != nil {
2727+ t.Fatal(err)
2828+ }
2929+ return post
3030+}
3131+3232+func TestExpandPostText(t *testing.T) {
3333+ post := loadPost(t, "testdata/atproto_embed_post.json")
3434+3535+ text := ExpandPostText(&post)
3636+ if !strings.Contains(text, "https://github.com/snarfed/bridgy-fed") {
3737+ t.Fail()
3838+ }
3939+}
···1010 "net/http"
1111 "os"
1212 "os/signal"
1313- "slices"
1413 "strings"
1514 "syscall"
1615 "time"
···353352 }
354353 }
355354356356- data["postText"] = expandPostLinks(postView)
357357-358358- return c.Render(http.StatusOK, "post.html", data)
359359-}
360360-361361-// function to expand shortened links in rich text back to full urls, replacing shortened urls in
362362-// social card meta tags and the noscript output. this essentially reverses the effect
363363-// of shortenLinks() in src/lib/strings/rich-text-manip.ts
364364-func expandPostLinks(postView *appbsky.FeedDefs_PostView) string {
365355 if postView.Record != nil {
366366- rec := postView.Record.Val.(*appbsky.FeedPost)
367367- postText := rec.Text
368368- var charsAdded int64 = 0
369369- // iterate over facets, check if they're link facets, and if found, grab the uri
370370- for _, facet := range rec.Facets {
371371- linkUri := ""
372372- if slices.ContainsFunc(facet.Features, func(feat *appbsky.RichtextFacet_Features_Elem) bool {
373373- if feat.RichtextFacet_Link != nil && feat.RichtextFacet_Link.LexiconTypeID == "app.bsky.richtext.facet#link" {
374374- linkUri = feat.RichtextFacet_Link.Uri
375375- // only expand uris that have been shortened (as opposed to those with non-uri anchor text)
376376- if int64(len(postText)) >= facet.Index.ByteEnd+charsAdded &&
377377- strings.HasSuffix(postText[facet.Index.ByteStart+charsAdded:facet.Index.ByteEnd+charsAdded], "...") &&
378378- strings.Contains(linkUri, postText[facet.Index.ByteStart+charsAdded:(facet.Index.ByteEnd+charsAdded)-3]) {
379379- return true
380380- }
381381- }
382382- return false
383383- }) {
384384- // replace the shortened uri with the full length one from the facet using utf8 byte offsets
385385- if int64(len(postText)) >= facet.Index.ByteEnd+charsAdded {
386386- postText = postText[0:facet.Index.ByteStart+charsAdded] + linkUri + postText[facet.Index.ByteEnd+charsAdded:]
387387- charsAdded += int64(len(linkUri)) - (facet.Index.ByteEnd - facet.Index.ByteStart)
388388- }
389389- }
356356+ postRecord, ok := postView.Record.Val.(*appbsky.FeedPost)
357357+ if ok {
358358+ data["postText"] = ExpandPostText(postRecord)
390359 }
391391- // if the post has an embeded link and its url doesn't already appear in postText, append it to
392392- // the end to avoid social cards with missing links
393393- if postView.Embed != nil &&
394394- postView.Embed.EmbedExternal_View != nil &&
395395- !strings.Contains(postText, postView.Embed.EmbedExternal_View.External.Uri) {
396396- postText = fmt.Sprintf("%s\n%s", postText, postView.Embed.EmbedExternal_View.External.Uri)
397397- }
398398- return postText
399360 }
400400- return ""
361361+362362+ return c.Render(http.StatusOK, "post.html", data)
401363}
402364403365func (srv *Server) WebProfile(c echo.Context) error {