diff --git a/astro.config.mjs b/astro.config.mjs index 306244d..27c9723 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -10,7 +10,6 @@ import node from '@astrojs/node'; const envMode = process.env.NODE_ENV === 'production' ? 'production' : 'development'; const envVars = loadEnv(envMode, process.cwd(), ''); const WEBMENTION_TOKEN = envVars.WEBMENTION_TOKEN || process.env.WEBMENTION_TOKEN || ''; -console.log(`[astro.config] loadEnv mode=${envMode} token-len=${WEBMENTION_TOKEN.length}`); // https://astro.build/config export default defineConfig({ diff --git a/src/components/Webmentions.astro b/src/components/Webmentions.astro index 1224745..5d0f588 100644 --- a/src/components/Webmentions.astro +++ b/src/components/Webmentions.astro @@ -31,15 +31,9 @@ interface Props { const { target, locale = getLocaleFromUrl(Astro.url) ?? DEFAULT_LOCALE } = Astro.props; -interface FetchResult { - mentions: WMEntry[]; - debug: string; -} - -async function fetchMentions(target: string): Promise { +async function fetchMentions(target: string): Promise { const token = WEBMENTION_TOKEN; - const tokenLen = typeof token === 'string' ? token.length : 0; - if (!token) return { mentions: [], debug: `no-token(len=${tokenLen})` }; + if (!token) return []; const withSlash = target.endsWith('/') ? target : `${target}/`; const withoutSlash = target.replace(/\/+$/, ''); const fetchOne = async (t: string) => { @@ -48,27 +42,24 @@ async function fetchMentions(target: string): Promise { url.searchParams.set('token', token); url.searchParams.set('per-page', '100'); const res = await fetch(url); - if (!res.ok) return { entries: [] as WMEntry[], status: res.status }; + if (!res.ok) return [] as WMEntry[]; const json = (await res.json()) as { children?: WMEntry[] }; - return { entries: json.children ?? [], status: 200 }; + return json.children ?? []; }; const [a, b] = await Promise.all([fetchOne(withSlash), fetchOne(withoutSlash)]); const seen = new Set(); const merged: WMEntry[] = []; - for (const m of [...a.entries, ...b.entries]) { + for (const m of [...a, ...b]) { const id = m['wm-id']; if (id == null || seen.has(id)) continue; seen.add(id); merged.push(m); } - return { - mentions: merged, - debug: `ok(len=${tokenLen}) slash=${a.status}:${a.entries.length} noslash=${b.status}:${b.entries.length}`, - }; + return merged; } const targetStr = target.toString(); -const { mentions: all, debug: fetchDebug } = await fetchMentions(targetStr); +const all = await fetchMentions(targetStr); const likes = all.filter((m) => m['wm-property'] === 'like-of'); const reposts = all.filter((m) => m['wm-property'] === 'repost-of'); @@ -95,8 +86,6 @@ function formatDate(iso?: string) { const hasAny = facepile.length > 0 || replies.length > 0 || mentions.length > 0; --- - - { hasAny && (
diff --git a/src/lib/webmentions.ts b/src/lib/webmentions.ts deleted file mode 100644 index 4f7de25..0000000 --- a/src/lib/webmentions.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Build-time Webmention fetcher via webmention.io API. - * - * Requires WEBMENTION_TOKEN in the environment. Token is read-only and - * issued per registered webmention.io domain. - * - * Fetches per target URL (not domain-wide) to avoid inconsistencies when - * a domain is registered more than once at webmention.io. - */ - -export type WMProperty = 'in-reply-to' | 'like-of' | 'repost-of' | 'bookmark-of' | 'mention-of'; - -export interface WMAuthor { - type?: string; - name?: string; - url?: string; - photo?: string; -} - -export interface WMEntry { - type: 'entry'; - author?: WMAuthor; - url: string; - published?: string; - 'wm-received'?: string; - 'wm-id'?: number; - 'wm-source'?: string; - 'wm-target'?: string; - 'wm-property'?: WMProperty; - 'wm-private'?: boolean; - content?: { text?: string; html?: string }; - name?: string; -} - -interface WMResponse { - type: 'feed'; - name?: string; - children: WMEntry[]; -} - -const API = 'https://webmention.io/api/mentions.jf2'; - -let invocationCounter = 0; - -async function fetchForTarget(target: string): Promise { - const token = import.meta.env.WEBMENTION_TOKEN; - if (!token) { - console.warn('[webmentions] WEBMENTION_TOKEN is not set — skipping fetch.'); - return []; - } - console.log(`[webmentions] fetching ${target} (token length: ${token.length})`); - const url = new URL(API); - url.searchParams.set('target', target); - url.searchParams.set('token', token); - url.searchParams.set('per-page', '100'); - const res = await fetch(url); - if (!res.ok) { - console.warn(`[webmentions] API ${res.status} ${res.statusText} for ${target}`); - return []; - } - const json = (await res.json()) as WMResponse; - console.log(`[webmentions] → ${json.children?.length ?? 0} mentions`); - return json.children ?? []; -} - -/** - * Return all webmentions targeting a given absolute URL. Tries both the - * canonical URL and its trailing-slash variant, since webmention.io - * indexes targets verbatim. - */ -export async function getMentionsFor(target: string | URL): Promise { - const canonical = typeof target === 'string' ? target : target.toString(); - const withSlash = canonical.endsWith('/') ? canonical : `${canonical}/`; - const withoutSlash = canonical.replace(/\/+$/, ''); - - const invocation = ++invocationCounter; - console.log(`[webmentions getMentionsFor #${invocation}] target=${canonical}`); - - const [a, b] = await Promise.all([fetchForTarget(withSlash), fetchForTarget(withoutSlash)]); - const seen = new Set(); - const merged: WMEntry[] = []; - for (const m of [...a, ...b]) { - const id = m['wm-id']; - if (id == null || seen.has(id)) continue; - seen.add(id); - merged.push(m); - } - - console.log(`[webmentions getMentionsFor #${invocation}] returning ${merged.length} mentions for ${canonical}`); - return merged; -} - -export function groupMentions(mentions: WMEntry[]): { - likes: WMEntry[]; - reposts: WMEntry[]; - bookmarks: WMEntry[]; - replies: WMEntry[]; - mentions: WMEntry[]; -} { - const likes: WMEntry[] = []; - const reposts: WMEntry[] = []; - const bookmarks: WMEntry[] = []; - const replies: WMEntry[] = []; - const others: WMEntry[] = []; - for (const m of mentions) { - switch (m['wm-property']) { - case 'like-of': - likes.push(m); - break; - case 'repost-of': - reposts.push(m); - break; - case 'bookmark-of': - bookmarks.push(m); - break; - case 'in-reply-to': - replies.push(m); - break; - default: - others.push(m); - } - } - return { likes, reposts, bookmarks, replies, mentions: others }; -}