Files
svemagie 48b6d920c4 merge: upstream theme updates (54 commits)
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>
2026-04-09 14:41:09 +02:00

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 [];
}
}