Skip to content

Instantly share code, notes, and snippets.

@brittanyellich
Created August 5, 2025 21:49
Show Gist options
  • Save brittanyellich/c9763854c86f81d8e731ad67040bb1a3 to your computer and use it in GitHub Desktop.
Save brittanyellich/c9763854c86f81d8e731ad67040bb1a3 to your computer and use it in GitHub Desktop.
Showing Likes and Comments on Bluesky
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;
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;
---
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 />
)}
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>
);
}
---
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