Created
August 5, 2025 21:49
-
-
Save brittanyellich/c9763854c86f81d8e731ad67040bb1a3 to your computer and use it in GitHub Desktop.
Showing Likes and Comments on Bluesky
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
export type CommentPost = { | |
post: { | |
author: { | |
did: string; | |
handle: string; | |
displayName: string; | |
avatar: string; | |
}; | |
record: { | |
text: string; | |
createdAt: string; | |
}; | |
}; | |
}; | |
interface Props { | |
comments: CommentPost[]; | |
} | |
const BlueskyCommentsReact: React.FC<Props> = ({ comments }) => ( | |
<div> | |
<p className="mb-2 font-semibold text-left">Comments:</p> | |
<ul className="space-y-4"> | |
{comments.length > 0 ? ( | |
comments.map((comment) => ( | |
<li className="flex items-start space-x-3" key={comment.post.author.did + comment.post.record.createdAt}> | |
<a | |
href={`https://bsky.app/profile/${comment.post.author.handle}`} | |
target="_blank" | |
rel="noopener noreferrer" | |
> | |
<img | |
src={comment.post.author.avatar} | |
alt={comment.post.author.displayName} | |
className="w-14 h-14 lg:w-16 lg:h-16 rounded-full object-cover border border-gray-200" | |
/> | |
</a> | |
<div> | |
<strong className="block text-sm text-left">{comment.post.author.displayName}</strong> | |
<p className="text-gray-700 text-sm text-left">{comment.post.record.text}</p> | |
</div> | |
</li> | |
)) | |
) : ( | |
<li className="text-gray-500 text-sm">Oh no, no comments, how sad! How about you add one?</li> | |
)} | |
</ul> | |
</div> | |
); | |
export default BlueskyCommentsReact; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
export type Actor = { | |
actor: { | |
handle: string; | |
displayName: string; | |
avatar: string; | |
}; | |
}; | |
interface Props { | |
actors: Actor[]; | |
likesOverLimit: number; | |
} | |
const BlueskyLikesReact: React.FC<Props> = ({ actors, likesOverLimit }) => ( | |
<div> | |
<p className="mb-2 font-semibold text-left">Likes:</p> | |
{actors.length > 0 ? ( | |
Array.from({ length: Math.ceil(actors.length / 8) }).map((_, rowIndex) => { | |
const start = rowIndex * 8; | |
const end = start + 8; | |
const rowActors = actors.slice(start, end); | |
// Only show likesOverLimit on the last row | |
const isLastRow = rowIndex === Math.floor(actors.length / 8); | |
return ( | |
<ul key={rowIndex} className="flex justify-center -space-x-3 items-center mb-2"> | |
{rowActors.map((actor) => ( | |
<li key={actor.actor.handle}> | |
<a | |
href={`https://bsky.app/profile/${actor.actor.handle}`} | |
target="_blank" | |
rel="noopener noreferrer" | |
> | |
{actor.actor.avatar ? ( | |
<img | |
className="w-14 h-14 lg:w-16 lg:h-16 rounded-full object-cover border-2 border-white shadow-sm" | |
src={actor.actor.avatar} | |
alt={actor.actor.displayName} | |
/> | |
) : ( | |
<div className="w-14 h-14 lg:w-16 lg:h-16 flex items-center justify-center rounded-full bg-gray-200 border-2 border-white shadow-sm text-2xl"> | |
🦋 | |
</div> | |
)} | |
</a> | |
</li> | |
))} | |
{isLastRow && likesOverLimit > 0 && ( | |
<li> | |
<div className="w-14 h-14 lg:w-16 lg:h-16 flex items-center justify-center rounded-full bg-gray-200 border-2 border-white text-xs font-semibold text-gray-700 shadow-sm"> | |
+{likesOverLimit} | |
</div> | |
</li> | |
)} | |
</ul> | |
); | |
}) | |
): ( | |
<ul className="flex justify-center -space-x-3 items-center mb-2"> | |
<li className="text-gray-500 text-sm">Oh no, no likes, how sad! How about you add one?</li> | |
</ul> | |
)} | |
</div> | |
); | |
export default BlueskyLikesReact; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
import BlueskyPostReact from './BlueskyPostReact.tsx'; | |
const { id } = Astro.props; | |
// TODO: This is where ya gotta send your bluesky id to (the end of a post id, ex: in the link `https://bsky.app/profile/brittanyellich.com/post/3lvobbdqvgk2w`, `3lvobbdqvgk2w` is the id) | |
--- | |
{ id && ( | |
<BlueskyPostReact id={id} client:load /> | |
)} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useState } from "react"; | |
import BlueskyLikes, {type Actor} from "./BlueskyLikes"; | |
import BlueskyComments from "./BlueskyComments"; | |
interface Props { | |
id: string; | |
} | |
export default function BlueskyPostReact({ id }: Props) { | |
const [likes, setLikes] = useState(0); | |
const [likesOverLimit, setLikesOverLimit] = useState(0); | |
const [likesActors, setLikesActors] = useState<Actor[]>([]); | |
const [comments, setComments] = useState([]); | |
// TODO: Replace with your DID, you can find that by searching your handle here: https://pdsls.dev/ | |
const myDid = "did:plc:4hodhjl2kposuchzvpiviwps"; | |
const bskyAPI = "https://public.api.bsky.app/xrpc/"; | |
const LIMIT = 55; | |
const getLikesURL = `${bskyAPI}app.bsky.feed.getLikes?limit=${LIMIT}&uri=`; | |
const getPostURL = `${bskyAPI}app.bsky.feed.getPosts?uris=`; | |
// TODO: replace with your handle | |
const postUrl = `https://bsky.app/profile/brittanyellich.com/post/${id}`; | |
const postUri = `at://${myDid}/app.bsky.feed.post/${id}`; | |
useEffect(() => { | |
async function fetchData() { | |
try { | |
const bskyPost = await fetch(getPostURL + postUri); | |
const bskyPostLikes = await fetch(getLikesURL + postUri); | |
const bskyPostComments = await fetch( | |
`${bskyAPI}app.bsky.feed.getPostThread?uri=${postUri}&depth=1` | |
); | |
const postData = await bskyPost.json(); | |
const likesData = await bskyPostLikes.json(); | |
const commentsData = await bskyPostComments.json(); | |
const totalLikesCount = postData.posts[0].likeCount; | |
setLikes(totalLikesCount); | |
if (totalLikesCount > LIMIT) { | |
setLikesOverLimit(totalLikesCount - LIMIT); | |
} | |
setLikesActors(likesData.likes || []); | |
setComments(commentsData.thread.replies || []); | |
} catch (error) { | |
console.error("Error fetching Bluesky post data:", error); | |
} | |
} | |
fetchData(); | |
}, [id]); | |
return ( | |
<div className="mx-auto max-w-xl flex flex-col items-center text-center p-6 bg-white"> | |
<h3 className="mb-2 font-semibold text-xl flex items-center gap-2 justify-center"> | |
<span aria-hidden="true">🦋</span> | |
{likes} Likes on Bluesky | |
</h3> | |
<div className="mb-4 w-full"> | |
<BlueskyLikes actors={likesActors} likesOverLimit={likesOverLimit} /> | |
<a | |
href={postUrl} | |
target="_blank" | |
rel="noopener noreferrer" | |
className="underline text-blue-600 hover:text-blue-800 transition-colors mt-2 block" | |
> | |
Like this post on Bluesky to see your face show up here | |
</a> | |
</div> | |
<div className="mt-6 w-full"> | |
<BlueskyComments comments={comments} /> | |
<a | |
href={postUrl} | |
target="_blank" | |
rel="noopener noreferrer" | |
className="underline text-blue-600 hover:text-blue-800 transition-colors mt-2 block" | |
> | |
Comment on Bluesky to see your comment show up here | |
</a> | |
</div> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
import BlueskyPost from '~/components/common/BlueskyPost.astro'; | |
export interface Props { | |
post: Post; | |
} | |
const { post } = Astro.props; | |
// An example of how to include this in Astro. In this case I have a blueskyId on my post object | |
--- | |
<section> | |
<article> | |
// blah blah blah, your post here | |
<BlueskyPost id={post.blueskyId} /> | |
</article> | |
</section> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment