BlueskyCommmentSection.astro
1<script>
2 import { AtpAgent } from '@atproto/api';
3 import {
4 isThreadViewPost,
5 type PostView,
6 } from '@atproto/api/dist/client/types/app/bsky/feed/defs';
7
8 export class BlueskyCommentsSection extends HTMLElement {
9 static agent = new AtpAgent({
10 service: new URL('https://api.bsky.app'),
11 });
12
13 constructor() {
14 super();
15 }
16
17 connectedCallback() {
18 this.getComments()
19 .then((comments) => {
20 comments.forEach((comment) => this.createCommentArticle(comment));
21 })
22 .catch(console.error);
23 }
24
25 async getComments() {
26 const { blueskyDid, blogTitle } = this.dataset;
27
28 if (!blueskyDid) throw new Error('A Bluesky DID is required!');
29
30 if (!blogTitle) throw new Error('A blog title is required!');
31
32 const post = await BlueskyCommentsSection.agent.app.bsky.feed
33 .searchPosts({
34 q: `#blog #${blogTitle.toLowerCase().replaceAll(' ', '-')}`,
35 author: blueskyDid,
36 limit: 1,
37 })
38 .then(({ data }) => {
39 if (!data.posts) return undefined;
40
41 return data.posts[0];
42 });
43
44 if (!post) return [];
45
46 if (post.replyCount === 0) return [];
47
48 const comments = await BlueskyCommentsSection.agent
49 .getPostThread({
50 uri: post.uri,
51 depth: 1,
52 })
53 .then(({ data }) => {
54 if (!isThreadViewPost(data.thread)) return [];
55
56 if (!data.thread.replies) return [];
57
58 return data.thread.replies
59 .filter(isThreadViewPost)
60 .map((reply) => reply.post);
61 });
62
63 return comments;
64 }
65
66 createCommentArticle(comment: PostView) {
67 const { likeCount = 0, repostCount = 0, replyCount = 0 } = comment;
68
69 const commentArticle = document.createElement('article', {
70 is: 'bluesky-comment',
71 });
72
73 commentArticle.setAttribute(
74 'display-name',
75 comment.author.displayName ?? 'Unknown'
76 );
77
78 commentArticle.setAttribute(
79 'handle',
80 comment.author.handle ?? 'unknown-handle'
81 );
82
83 commentArticle.setAttribute('likes', likeCount.toString());
84 commentArticle.setAttribute('replies', replyCount.toString());
85 commentArticle.setAttribute('reposts', repostCount.toString());
86
87 const body = document.createElement('span');
88 body.setAttribute('slot', 'default');
89 body.innerText = comment.record.text as string;
90
91 commentArticle.appendChild(body);
92
93 this.appendChild(commentArticle);
94 }
95 }
96
97 customElements.define('bluesky-comments-section', BlueskyCommentsSection, {
98 extends: 'section',
99 });
100</script>
101
102<template id={'bluesky-comments-section'}></template>