import { AtpAgent } from "@atproto/api";
export async function getTimeline(identifier, password) {
const agent = new AtpAgent({ service: "https://melkat.blue" });
await agent.login({
identifier,
password,
});
const did = agent.assertDid;
if (!did) {
return;
}
return await agent.getTimeline({ limit: 100 });
}
function filter(feed) {
return feed.filter(({ reply, reason }) => {
return !(reply || reason);
});
}
function parseFacets(facets) {
const links = [];
const tags = [];
if (Array.isArray(facets)) {
for (const facet of facets) {
if (Array.isArray(facet.features)) {
for (const feature of facet.features) {
switch (feature?.$type) {
case "app.bsky.richtext.facet#link":
links.push(feature.uri);
break;
case "app.bsky.richtext.facet#tag":
tags.push(feature.tag);
break;
default:
break;
}
}
}
}
}
return { links, tags };
}
function parseEmbed(embed) {
const images = [];
const video = {};
const quote = {};
switch (embed?.$type) {
case "app.bsky.embed.images#view":
if (embed.images) {
for (const image of embed.images) {
images.push({
fullsize: image?.fullsize,
alt: image?.alt,
});
}
}
break;
case "app.bsky.embed.video#view":
video.playlist = embed.playlist;
video.thumbnail = embed.thumbnail;
video.alt = embed.alt;
break;
case "app.bsky.embed.record#view":
if (embed.record) {
quote.handle = embed.record.author.handle;
quote.displayName = embed.record.author.displayName;
quote.avatar = embed.record.author.avatar;
quote.url = `https://bsky.app/profile/${
quote.handle
}/post/${embed.record.uri.split("/").at(-1)}`;
const moreEmbeds = parseEmbed(embed.record?.embeds?.[0]);
const { links, tags } = parseFacets(embed?.record?.value?.facets);
quote.text = embed?.record?.value?.text;
quote.images = moreEmbeds?.images;
quote.video = moreEmbeds?.video;
quote.links = links;
quote.tags = tags;
quote.stats = {
likeCount: embed.record?.likeCount || 0,
replyCount: embed.record?.replyCount || 0,
repostCount: embed.record?.repostCount || 0,
quoteCount: embed.record?.quoteCount || 0,
};
}
break;
default:
break;
}
return {
images,
video,
quote,
};
}
function buildHtml({ images, links, text, video, stats, quote }) {
const videoHtml =
video?.thumbnail && video?.playlist
? ``
: "";
const imagesHtml =
images
.map(({ alt, fullsize }) => ``)
.join("
") || "";
const linksHtml =
links?.length > 0
? `
${quote.displayName} (${ quote.handle })` : ""; const statsHtml = `
${buildHtml(quote)}
${text}
${quoteHtml}${videoHtml}${imagesHtml}${linksHtml}${statsHtml}`; } function processFeed(feed) { const cleanFeed = filter(feed); return cleanFeed.map(({ post }) => { const text = post.record.text; const name = post.author.displayName || post.author.handle; const url = `https://bsky.app/profile/${post.author.handle}/post/${post.uri .split("/") .at(-1)}`; const { links, tags } = parseFacets(post.record.facets); const { images, video, quote } = parseEmbed(post.embed); const stats = { replyCount: post.replyCount, repostCount: post.repostCount, likeCount: post.likeCount, quoteCount: post.quoteCount, }; const content_html = buildHtml({ images, links, quote, stats, text, video, }); return { author: { name, url: `https://bsky.app/profile/${post.author.handle}`, avatar: post.author.avatar, }, date_published: post.indexedAt, external_url: links?.[0], id: url, image: images?.[0]?.fullsize, summary: post.record.text, tags, title: name, url, content_html, }; }); } export function buildJsonFeed(handle, feed) { const items = processFeed(feed.data.feed); return { version: "https://jsonfeed.org/version/1.1", title: `${handle}'s Timeline`, description: "A personalized Bluesky feed", home_page_url: `https://bsky.app/profile/${handle}`, items, }; }