--- import { readFileSync } from 'node:fs'; import { DEFAULT_LOCALE, type Locale } from '~/consts'; import { getLocaleFromUrl, t } from '~/i18n/ui'; let fileDebug = ''; function readTokenFromFile(): string | undefined { const paths = ['/app/.webmention-token', '.webmention-token']; const logs: string[] = []; logs.push(`cwd=${process.cwd()}`); for (const p of paths) { try { const raw = readFileSync(p, 'utf-8'); logs.push(`${p}:ok(${raw.length})`); const t = raw.trim(); if (t) { fileDebug = logs.join(';'); return t; } } catch (err) { logs.push(`${p}:err(${(err as Error).code ?? 'unknown'})`); } } fileDebug = logs.join(';'); return undefined; } const FILE_TOKEN = readTokenFromFile(); const WEBMENTION_TOKEN = FILE_TOKEN ?? (import.meta.env as Record).WEBMENTION_TOKEN; interface WMAuthor { name?: string; url?: string; photo?: string; } interface WMEntry { author?: WMAuthor; url: string; published?: string; 'wm-received'?: string; 'wm-id'?: number; 'wm-property'?: string; content?: { text?: string }; } interface Props { target: string | URL; locale?: Locale; } const { target, locale = getLocaleFromUrl(Astro.url) ?? DEFAULT_LOCALE } = Astro.props; interface FetchResult { mentions: WMEntry[]; debug: string; } async function fetchMentions(target: string): Promise { const token = WEBMENTION_TOKEN; const tokenLen = typeof token === 'string' ? token.length : 0; const source = FILE_TOKEN ? 'file' : 'env'; if (!token) return { mentions: [], debug: `no-token(src=${source},len=${tokenLen},probe=${fileDebug})` }; const withSlash = target.endsWith('/') ? target : `${target}/`; const withoutSlash = target.replace(/\/+$/, ''); const fetchOne = async (t: string) => { const url = new URL('https://webmention.io/api/mentions.jf2'); url.searchParams.set('target', t); 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 }; const json = (await res.json()) as { children?: WMEntry[] }; return { entries: json.children ?? [], status: 200 }; }; 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]) { const id = m['wm-id']; if (id == null || seen.has(id)) continue; seen.add(id); merged.push(m); } return { mentions: merged, debug: `ok(src=${source},len=${tokenLen},probe=${fileDebug}) slash=${a.status}:${a.entries.length} noslash=${b.status}:${b.entries.length}`, }; } const targetStr = target.toString(); const { mentions: all, debug: fetchDebug } = await fetchMentions(targetStr); const likes = all.filter((m) => m['wm-property'] === 'like-of'); const reposts = all.filter((m) => m['wm-property'] === 'repost-of'); const replies = all.filter((m) => m['wm-property'] === 'in-reply-to'); const mentions = all.filter( (m) => !['like-of', 'repost-of', 'in-reply-to', 'bookmark-of'].includes(m['wm-property'] ?? ''), ); const facepile = [...likes, ...reposts]; function authorInitial(m: WMEntry) { return m.author?.name?.trim()?.[0]?.toUpperCase() ?? '?'; } function formatDate(iso?: string) { if (!iso) return ''; const d = new Date(iso); return d.toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US', { year: 'numeric', month: 'short', day: 'numeric', }); } const hasAny = facepile.length > 0 || replies.length > 0 || mentions.length > 0; --- { hasAny && (

{t(locale, 'webmentions.title')}

{facepile.length > 0 && (
{likes.length > 0 && (

{`${likes.length} ${t(locale, likes.length === 1 ? 'webmentions.like' : 'webmentions.likes')}`}

)} {reposts.length > 0 && (

{`${reposts.length} ${t(locale, reposts.length === 1 ? 'webmentions.repost' : 'webmentions.reposts')}`}

)}
)} {replies.length > 0 && (

{t(locale, 'webmentions.replies')}

    {replies.map((m) => (
  1. {m.content?.text &&

    {m.content.text}

    }
  2. ))}
)} {mentions.length > 0 && (

{t(locale, 'webmentions.mentions')}

)}
) }