Bluesky app fork with some witchin' additions 💫

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.

+106 -1
+52 -1
bskyweb/cmd/bskyweb/server.go
··· 313 313 e.GET("/profile/:handleOrDID/known-followers", server.WebGeneric) 314 314 e.GET("/profile/:handleOrDID/search", server.WebGeneric) 315 315 e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric) 316 - e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric) 316 + e.GET("/profile/:handleOrDID/feed/:rkey", server.WebFeed) 317 317 e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric) 318 318 e.GET("/profile/:handleOrDID/labeler/liked-by", server.WebGeneric) 319 319 ··· 601 601 data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 602 602 data["requestHost"] = req.Host 603 603 return c.Render(http.StatusOK, "profile.html", data) 604 + } 605 + 606 + func (srv *Server) WebFeed(c echo.Context) error { 607 + ctx := c.Request().Context() 608 + data := srv.NewTemplateContext() 609 + 610 + // sanity check arguments. don't 4xx, just let app handle if not expected format 611 + rkeyParam := c.Param("rkey") 612 + rkey, err := syntax.ParseRecordKey(rkeyParam) 613 + if err != nil { 614 + return c.Render(http.StatusOK, "feed.html", data) 615 + } 616 + handleOrDIDParam := c.Param("handleOrDID") 617 + handleOrDID, err := syntax.ParseAtIdentifier(handleOrDIDParam) 618 + if err != nil { 619 + return c.Render(http.StatusOK, "feed.html", data) 620 + } 621 + 622 + identifier := handleOrDID.Normalize().String() 623 + 624 + // requires two fetches: first fetch profile to get DID 625 + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, identifier) 626 + if err != nil { 627 + log.Warnf("failed to fetch profile for: %s\t%v", identifier, err) 628 + return c.Render(http.StatusOK, "feed.html", data) 629 + } 630 + unauthedViewingOkay := true 631 + for _, label := range pv.Labels { 632 + if label.Src == pv.Did && label.Val == "!no-unauthenticated" { 633 + unauthedViewingOkay = false 634 + } 635 + } 636 + 637 + if !unauthedViewingOkay { 638 + return c.Render(http.StatusOK, "feed.html", data) 639 + } 640 + did := pv.Did 641 + data["did"] = did 642 + 643 + // then fetch the feed generator 644 + feedURI := fmt.Sprintf("at://%s/app.bsky.feed.generator/%s", did, rkey) 645 + fgv, err := appbsky.FeedGetFeedGenerator(ctx, srv.xrpcc, feedURI) 646 + if err != nil { 647 + log.Warnf("failed to fetch feed generator: %s\t%v", feedURI, err) 648 + return c.Render(http.StatusOK, "feed.html", data) 649 + } 650 + req := c.Request() 651 + data["feedView"] = fgv.View 652 + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 653 + 654 + return c.Render(http.StatusOK, "feed.html", data) 604 655 } 605 656 606 657 type IPCCRequest struct {
+54
bskyweb/templates/feed.html
··· 1 + {% extends "base.html" %} 2 + 3 + {% block head_title %} 4 + {%- if feedView -%} 5 + {{ feedView.DisplayName }} by @{{ feedView.Creator.Handle }} | Bluesky Feed 6 + {%- else -%} 7 + Bluesky 8 + {%- endif -%} 9 + {% endblock %} 10 + 11 + {% block html_head_extra -%} 12 + {%- if feedView -%} 13 + <meta property="og:site_name" content="Bluesky Social"> 14 + <meta property="og:type" content="website"> 15 + {%- if requestURI %} 16 + <meta property="og:url" content="{{ requestURI }}"> 17 + <link rel="canonical" href="{{ requestURI|canonicalize_url }}" /> 18 + {% endif -%} 19 + 20 + {%- if feedView.DisplayName %} 21 + <meta property="og:title" content="{{ feedView.DisplayName }} by @{{ feedView.Creator.Handle }}"> 22 + {% else %} 23 + <meta property="og:title" content="Feed by @{{ feedView.Creator.Handle }}"> 24 + {% endif -%} 25 + 26 + {%- if feedView.Description %} 27 + <meta name="description" content="{{ feedView.Description }}"> 28 + <meta property="og:description" content="{{ feedView.Description }}"> 29 + <meta property="twitter:description" content="{{ feedView.Description }}"> 30 + {% endif -%} 31 + 32 + {%- if feedView.Avatar %} 33 + <meta property="og:image" content="{{ feedView.Avatar }}"> 34 + <meta property="twitter:image" content="{{ feedView.Avatar }}"> 35 + <meta name="twitter:card" content="summary"> 36 + {% endif %} 37 + 38 + <meta name="twitter:label1" content="Created by"> 39 + <meta name="twitter:value1" content="@{{ feedView.Creator.Handle }}"> 40 + 41 + <link rel="alternate" href="{{ feedView.Uri }}" /> 42 + {% endif -%} 43 + {%- endblock %} 44 + 45 + {% block noscript_extra -%} 46 + {%- if feedView -%} 47 + <div id="bsky_feed_summary"> 48 + <h3>Feed</h3> 49 + <p id="bsky_feed_name">{{ feedView.DisplayName }}</p> 50 + <p id="bsky_feed_creator">{{ feedView.Creator.Handle }}</p> 51 + <p id="bsky_feed_description">{{ feedView.Description }}</p> 52 + </div> 53 + {% endif -%} 54 + {%- endblock %}