An ATproto social media client -- with an independent Appview.

chore: merge from upstream at 2025-09-18-011544

+51 -10
+10 -4
bskyweb/cmd/bskyweb/server.go
··· 488 488 } 489 489 } 490 490 491 + req := c.Request() 491 492 if !unauthedViewingOkay { 493 + // Provide minimal OpenGraph data for auth-required posts 494 + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 495 + data["requiresAuth"] = true 496 + data["profileHandle"] = pv.Handle 497 + if pv.DisplayName != nil { 498 + data["profileDisplayName"] = *pv.DisplayName 499 + } 492 500 return c.Render(http.StatusOK, "post.html", data) 493 501 } 494 - did := pv.Did 495 - data["did"] = did 496 502 497 503 // then fetch the post thread (with extra context) 498 - uri := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey) 504 + uri := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", pv.Did, rkey) 499 505 tpv, err := appbsky.FeedGetPostThread(ctx, srv.xrpcc, 1, 0, uri) 500 506 if err != nil { 501 507 log.Warnf("failed to fetch post: %s\t%v", uri, err) 502 508 return c.Render(http.StatusOK, "post.html", data) 503 509 } 504 - req := c.Request() 510 + 505 511 postView := tpv.Thread.FeedDefs_ThreadViewPost.Post 506 512 data["postView"] = postView 507 513 data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path)
+1 -1
bskyweb/templates/base.html
··· 94 94 <meta name="theme-color"> 95 95 <meta name="application-name" content="Bluesky"> 96 96 <meta name="generator" content="bskyweb"> 97 - <meta property="og:site_name" content="Bluesky Social" /> 97 + <meta property="og:site_name" content="Bluesky Social"> 98 98 <meta name="twitter:site" content="@bluesky" /> 99 99 <link type="application/activity+json" href="" /> 100 100
+40 -5
bskyweb/templates/post.html
··· 3 3 {% block head_title %} 4 4 {%- if postView -%} 5 5 @{{ postView.Author.Handle }} on Bluesky 6 + {%- elif requiresAuth and profileHandle -%} 7 + @{{ profileHandle }} on Bluesky 6 8 {%- else -%} 7 9 Bluesky 8 10 {%- endif -%} ··· 11 13 {% block html_head_extra -%} 12 14 {%- if postView -%} 13 15 <meta property="og:type" content="article"> 14 - <meta property="profile:username" content="{{ profileView.Handle }}"> 16 + <meta property="profile:username" content="{{ postView.Author.Handle }}"> 15 17 {%- if requestURI %} 16 18 <meta property="og:url" content="{{ requestURI }}"> 17 19 <link rel="canonical" href="{{ requestURI|canonicalize_url }}" /> ··· 32 34 <meta property="twitter:image" content="{{ imgThumbUrl }}"> 33 35 {% endfor %} 34 36 <meta name="twitter:card" content="summary_large_image"> 35 - {%- elif postView.Author.Avatar %} 36 - <meta name="twitter:card" content="summary"> 37 + {% else %} 37 38 <meta property="og:image" content="{{ postView.Author.Avatar }}"> 38 39 <meta property="twitter:image" content="{{ postView.Author.Avatar }}"> 40 + <meta name="twitter:card" content="summary"> 39 41 {% endif %} 40 42 <meta name="twitter:label1" content="Posted At"> 41 43 <meta name="twitter:value1" content="{{ postView.IndexedAt }}"> 42 - <meta name="article:published_time" content="{{ postView.IndexedAt }}"> 43 - <meta name="article:published_time" content="{{ postView.IndexedAt }}"> 44 + {%- if postView.LikeCount %} 45 + <meta name="twitter:label2" content="Likes"> 46 + <meta name="twitter:value2" content="{{ postView.LikeCount }}"> 47 + {% endif -%} 48 + {%- if postView.ReplyCount %} 49 + <meta name="twitter:label3" content="Replies"> 50 + <meta name="twitter:value3" content="{{ postView.ReplyCount }}"> 51 + {% endif -%} 52 + {%- if postView.RepostCount %} 53 + <meta name="twitter:label4" content="Reposts"> 54 + <meta name="twitter:value4" content="{{ postView.RepostCount }}"> 55 + {% endif -%} 56 + <meta property="article:published_time" content="{{ postView.IndexedAt }}"> 44 57 <link rel="alternate" type="application/json+oembed" href="https://embed.bsky.app/oembed?format=json&url={{ postView.Uri | urlencode }}" /> 45 58 <link rel="alternate" href="{{ postView.Uri }}" /> 59 + {%- elif requiresAuth and profileHandle -%} 60 + <meta property="og:type" content="article"> 61 + <meta property="profile:username" content="{{ profileHandle }}"> 62 + {%- if requestURI %} 63 + <meta property="og:url" content="{{ requestURI }}"> 64 + <link rel="canonical" href="{{ requestURI|canonicalize_url }}" /> 65 + {% endif -%} 66 + {%- if profileDisplayName %} 67 + <meta property="og:title" content="{{ profileDisplayName }} (@{{ profileHandle }})"> 68 + {% else %} 69 + <meta property="og:title" content="@{{ profileHandle }}"> 70 + {% endif -%} 71 + <meta name="description" content="This post requires authentication to view."> 72 + <meta property="og:description" content="This post requires authentication to view."> 73 + <meta property="twitter:description" content="This post requires authentication to view."> 74 + <meta name="twitter:card" content="summary"> 46 75 {% endif -%} 47 76 {%- endblock %} 48 77 ··· 55 84 <p id="bsky_did">{{ postView.Author.Did }}</p> 56 85 <p id="bsky_post_text">{{ postText }}</p> 57 86 <p id="bsky_post_indexedat">{{ postView.IndexedAt }}</p> 87 + </div> 88 + {%- elif requiresAuth and profileHandle -%} 89 + <div id="bsky_post_summary"> 90 + <h3>Post</h3> 91 + <p id="bsky_handle">{{ profileHandle }}</p> 92 + <p id="bsky_post_text">This post requires authentication to view.</p> 58 93 </div> 59 94 {% endif -%} 60 95 {%- endblock %}