48b6d920c4
Key changes merged from svemagie/blog-eleventy-indiekit: - feat: /updated.xml feed for recently edited posts - feat: sitemap.xml generation in eleventy.after hook - feat: excludePostTypes filter for homepage section config - feat: view mode toggle (repo/type) for changelog page - feat: replyTargets config for platform-to-syndicator mapping - feat: syndication badge + linked timestamp on owner replies - perf: memoize aiPosts/aiStats/hash filters; batch unfurl pre-fetch - perf: clear eleventy-img in-memory cache between builds (OOM fix) - perf: memory profiler (logMemory) at build phases - perf: OG batch tracking (totalGenerated/batch counters) - fix: h-entry u-url absolute for IndieNews compatibility - fix: webmention platform detection in build-time templates - fix: deduplicate interactions via interactionKey - fix: reply form syndication via replyTargets (not hardcoded platforms) - fix: remove skeleton loader CSS (CLS fix) - fix: avatar dimensions 96→128 to match CSS classes - css: remove unused skeleton loader rules Local customisations preserved: - Gitea-based data files (githubActivity, githubRepos, githubStarred) - Funkwhale cover image cache copy in eleventy.after - URL fallback arrays in funkwhale/lastfm data fetchers - CONFIGURABLE cache durations (FUNKWHALE_FETCH_CACHE_DURATION etc.) - OG_CACHE_DIR naming (not cacheDir) - Our ogSlug format (plain slug, not date-prefixed) - Gruvbox design tokens (link colours, selection colours) - Unfurl manifest optimisation (skip re-fetching known URLs) - CLAUDE.md, README.md, .github/workflows (ours) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
75 lines
2.1 KiB
JavaScript
75 lines
2.1 KiB
JavaScript
/**
|
|
* Bluesky Feed Data
|
|
* Fetches recent posts from Bluesky using the AT Protocol API
|
|
*/
|
|
|
|
import { cachedFetch } from "../lib/data-fetch.js";
|
|
|
|
export default async function () {
|
|
const rawHandle = (process.env.BLUESKY_HANDLE || "")
|
|
.trim()
|
|
.replace(/^@+/, "");
|
|
const handle =
|
|
rawHandle && !rawHandle.includes(".") && !rawHandle.startsWith("did:")
|
|
? `${rawHandle}.bsky.social`
|
|
: rawHandle;
|
|
|
|
if (!handle) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
// Get the author's feed using public API (no auth needed for public posts)
|
|
const feedUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${handle}&limit=10`;
|
|
|
|
const response = await cachedFetch(feedUrl, {
|
|
duration: "15m", // Cache for 15 minutes
|
|
type: "json",
|
|
fetchOptions: {
|
|
headers: {
|
|
Accept: "application/json",
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!response.feed) {
|
|
console.log("No Bluesky feed found for handle:", handle);
|
|
return [];
|
|
}
|
|
|
|
// Transform the feed into a simpler format
|
|
return response.feed.map((item) => {
|
|
// Extract rkey from AT URI (at://did:plc:xxx/app.bsky.feed.post/rkey)
|
|
const rkey = item.post.uri.split("/").pop();
|
|
const postUrl = `https://bsky.app/profile/${item.post.author.handle}/post/${rkey}`;
|
|
|
|
return {
|
|
text: item.post.record.text,
|
|
createdAt: item.post.record.createdAt,
|
|
uri: item.post.uri,
|
|
url: postUrl,
|
|
cid: item.post.cid,
|
|
author: {
|
|
handle: item.post.author.handle,
|
|
displayName: item.post.author.displayName,
|
|
avatar: item.post.author.avatar,
|
|
},
|
|
likeCount: item.post.likeCount || 0,
|
|
repostCount: item.post.repostCount || 0,
|
|
replyCount: item.post.replyCount || 0,
|
|
// Extract any embedded links or images
|
|
embed: item.post.embed
|
|
? {
|
|
type: item.post.embed.$type,
|
|
images: item.post.embed.images || [],
|
|
external: item.post.embed.external || null,
|
|
}
|
|
: null,
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error("Error fetching Bluesky feed:", error.message);
|
|
return [];
|
|
}
|
|
}
|