From f5079b8158d87073222f834dfb40039b99650a27 Mon Sep 17 00:00:00 2001 From: Caidan Williams Date: Tue, 9 Sep 2025 20:03:50 -0700 Subject: feat: add OpenGraph metadata for feed URLs in bskyweb Enable rich link previews when feed URLs are shared in iMessage, Slack, and other social platforms. Adds feed title, description, creator info, and avatar images to improve sharing experience. --- bskyweb/cmd/bskyweb/server.go | 53 +++++++++++++++++++++++++++++++++++++++++- bskyweb/templates/feed.html | 54 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 bskyweb/templates/feed.html diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index f305f0d3c..8d75bb6ef 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -313,7 +313,7 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handleOrDID/known-followers", server.WebGeneric) e.GET("/profile/:handleOrDID/search", server.WebGeneric) e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric) - e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric) + e.GET("/profile/:handleOrDID/feed/:rkey", server.WebFeed) e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric) e.GET("/profile/:handleOrDID/labeler/liked-by", server.WebGeneric) @@ -603,6 +603,57 @@ func (srv *Server) WebProfile(c echo.Context) error { return c.Render(http.StatusOK, "profile.html", data) } +func (srv *Server) WebFeed(c echo.Context) error { + ctx := c.Request().Context() + data := srv.NewTemplateContext() + + // sanity check arguments. don't 4xx, just let app handle if not expected format + rkeyParam := c.Param("rkey") + rkey, err := syntax.ParseRecordKey(rkeyParam) + if err != nil { + return c.Render(http.StatusOK, "feed.html", data) + } + handleOrDIDParam := c.Param("handleOrDID") + handleOrDID, err := syntax.ParseAtIdentifier(handleOrDIDParam) + if err != nil { + return c.Render(http.StatusOK, "feed.html", data) + } + + identifier := handleOrDID.Normalize().String() + + // requires two fetches: first fetch profile to get DID + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, identifier) + if err != nil { + log.Warnf("failed to fetch profile for: %s\t%v", identifier, err) + return c.Render(http.StatusOK, "feed.html", data) + } + unauthedViewingOkay := true + for _, label := range pv.Labels { + if label.Src == pv.Did && label.Val == "!no-unauthenticated" { + unauthedViewingOkay = false + } + } + + if !unauthedViewingOkay { + return c.Render(http.StatusOK, "feed.html", data) + } + did := pv.Did + data["did"] = did + + // then fetch the feed generator + feedURI := fmt.Sprintf("at://%s/app.bsky.feed.generator/%s", did, rkey) + fgv, err := appbsky.FeedGetFeedGenerator(ctx, srv.xrpcc, feedURI) + if err != nil { + log.Warnf("failed to fetch feed generator: %s\t%v", feedURI, err) + return c.Render(http.StatusOK, "feed.html", data) + } + req := c.Request() + data["feedView"] = fgv.View + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) + + return c.Render(http.StatusOK, "feed.html", data) +} + type IPCCRequest struct { IP string `json:"ip"` } diff --git a/bskyweb/templates/feed.html b/bskyweb/templates/feed.html new file mode 100644 index 000000000..716a2c65c --- /dev/null +++ b/bskyweb/templates/feed.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% block head_title %} +{%- if feedView -%} + {{ feedView.DisplayName }} by @{{ feedView.Creator.Handle }} | Bluesky Feed +{%- else -%} + Bluesky +{%- endif -%} +{% endblock %} + +{% block html_head_extra -%} +{%- if feedView -%} + + + {%- if requestURI %} + + + {% endif -%} + + {%- if feedView.DisplayName %} + + {% else %} + + {% endif -%} + + {%- if feedView.Description %} + + + + {% endif -%} + + {%- if feedView.Avatar %} + + + + {% endif %} + + + + + +{% endif -%} +{%- endblock %} + +{% block noscript_extra -%} +{%- if feedView -%} +
+

Feed

+

{{ feedView.DisplayName }}

+

{{ feedView.Creator.Handle }}

+

{{ feedView.Description }}

+
+{% endif -%} +{%- endblock %} \ No newline at end of file -- cgit 1.4.1