about summary refs log tree commit diff
path: root/bskyweb/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'bskyweb/cmd')
-rw-r--r--bskyweb/cmd/bskyweb/formating.go57
-rw-r--r--bskyweb/cmd/bskyweb/formatting_test.go39
-rw-r--r--bskyweb/cmd/bskyweb/rss.go9
-rw-r--r--bskyweb/cmd/bskyweb/server.go27
-rw-r--r--bskyweb/cmd/bskyweb/testdata/atproto_embed_post.json60
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!"
+}