Changeset a677616 in Klonkt
- Timestamp:
- 07/26/2026 08:47:16 AM (6 weeks ago)
- Branches:
- main
- Children:
- f3caf19
- Parents:
- 46a30f0
- Location:
- src
- Files:
-
- 3 edited
-
config/database.js (modified) (1 diff)
-
routes/activitypub.js (modified) (1 diff)
-
services/ActivityPubService.js (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
r46a30f0 ra677616 550 550 ensureColumn('ap_timeline', 'link_json', 'TEXT'); // FEP-e232 object-link (quote/ref) tags from the inbound note, served back as `tag` 551 551 ensureColumn('ap_timeline', 'quote_json', 'TEXT'); // FEP-044f resolved quoted-post snapshot (author + content), for the embedded quote card 552 ensureColumn('ap_timeline', 'author_emoji_json', 'TEXT'); // FEP-9098 custom emojis in the author's display name (shaer:author.emojis) 552 553 ensureColumn('ap_timeline', 'reblog_name', 'TEXT'); // a followed account boosted this → "X boosted" 553 554 ensureColumn('ap_timeline', 'reblog_handle', 'TEXT'); // the booster's @handle -
src/routes/activitypub.js
r46a30f0 ra677616 180 180 name: t.author_name || undefined, handle: t.author_handle || undefined, 181 181 icon: t.author_icon || undefined, url: t.author_url || undefined, 182 // FEP-9098: emojis in the display name (":shortcode:"), if any. 183 emojis: (() => { try { return t.author_emoji_json ? JSON.parse(t.author_emoji_json) : undefined; } catch { return undefined; } })(), 182 184 } : undefined, 183 185 // When a followed account boosted this, who did ("X boosted"). Omitted for -
src/services/ActivityPubService.js
r46a30f0 ra677616 869 869 const handle = doc && doc.preferredUsername ? `@${doc.preferredUsername}@${host}` : deriveHandle(actorUri); 870 870 const icon = doc && doc.icon ? (doc.icon.url || (Array.isArray(doc.icon) && doc.icon[0] && doc.icon[0].url)) : null; 871 const name = (doc && (doc.name || doc.preferredUsername)) || handle; 871 872 return { 872 name : (doc && (doc.name || doc.preferredUsername)) || handle,873 name, 873 874 handle, 874 875 url: safeUrl((doc && (doc.url || doc.id)) || actorUri) || null, 875 876 icon: safeUrl(icon) || null, 877 // FEP-9098 custom emojis in the display name (":shortcode:"), so the byline 878 // renders them. Only computed when the name actually has a shortcode. 879 emojis: /:[A-Za-z0-9_+-]+:/.test(name) ? actorNameEmojis(doc) : undefined, 876 880 }; 881 } 882 883 // Map ":shortcode:" → image url from an actor doc's Emoji tags (for a custom- 884 // emoji display name). Undefined when there are none. 885 function actorNameEmojis(doc) { 886 const arr = doc && Array.isArray(doc.tag) ? doc.tag : (doc && doc.tag ? [doc.tag] : []); 887 const out = {}; 888 for (const t of arr) { 889 if (!t || (Array.isArray(t.type) ? t.type[0] : t.type) !== 'Emoji' || typeof t.name !== 'string' || !t.icon) continue; 890 const u = t.icon.url || (Array.isArray(t.icon) && t.icon[0] && t.icon[0].url); 891 if (u) out[t.name] = u; 892 } 893 return Object.keys(out).length ? out : undefined; 877 894 } 878 895 … … 1539 1556 // FEP-9098: keep the note's custom-emoji tags so the C2S inbox read can serve them. 1540 1557 { const ej = extractEmojiTags(o.tag); if (ej) { try { db.prepare('UPDATE ap_timeline SET emoji_json = ? WHERE id = ? AND slug = ?').run(ej, o.id, s.slug); } catch { /* ignore */ } } } 1558 storeAuthorEmoji(o.id, s.slug, ai); // custom-emoji display name for the byline 1559 1541 1560 // FEP-e232 + FEP-044f: keep the note's object-link/quote tags for the same read. 1542 1561 { const lj = extractLinkJson(o); if (lj) { try { db.prepare('UPDATE ap_timeline SET link_json = ? WHERE id = ? AND slug = ?').run(lj, o.id, s.slug); } catch { /* ignore */ } } } … … 1672 1691 try { const r = tlStmts().ins.run(bn.id, s.slug, origUri || '', oai.name, oai.handle, oai.icon, oai.url, html, bn.url || null, new Date().toISOString(), media, bn.sensitive ? 1 : 0, bn.summary || null); inserted = r.changes > 0; } catch { /* ignore */ } 1673 1692 if (inserted) { try { db.prepare('UPDATE ap_timeline SET reblog_name = ?, reblog_handle = ?, reblog_icon = ? WHERE slug = ? AND id = ?').run(booster.name, booster.handle, booster.icon, s.slug, bn.id); } catch { /* ignore */ } } 1693 storeAuthorEmoji(bn.id, s.slug, oai); // custom-emoji display name for the byline 1674 1694 } 1675 1695 console.log('[AP] timeline boost +', actorUri, 'x' + subs.length); … … 2665 2685 } 2666 2686 2687 // Store the author's display-name emoji map (from actorInfo().emojis) on a 2688 // timeline row, so the byline can render a ":shortcode:" name. No-op when the 2689 // name has no custom emoji (the common case). 2690 function storeAuthorEmoji(id, slug, ai) { 2691 if (!ai || !ai.emojis || !Object.keys(ai.emojis).length) return; 2692 try { db.prepare('UPDATE ap_timeline SET author_emoji_json = ? WHERE id = ? AND slug = ?').run(JSON.stringify(ai.emojis), id, slug); } catch { /* ignore */ } 2693 } 2694 2667 2695 // ── Cirkel = posts from the accounts you auto-boost ("feature an artist") ── 2668 2696 let _abCount, _cirkelPosts, _cirkelMembers; … … 2761 2789 // during a flux window, e.g. a fleet-wide update), and drops notes that are gone 2762 2790 // (404/410). Bump SELFHEAL_VERSION only on a release that warrants a re-sync. 2763 const SELFHEAL_VERSION = 1 2; // v12: quote card snapshot media as array + quoted-post emojis2791 const SELFHEAL_VERSION = 13; // v13: capture custom-emoji display names (author_emoji_json) onto already-cached posts 2764 2792 async function fetchNoteAP(url) { 2765 2793 try { … … 2856 2884 // FEP-9098: keep custom-emoji tags from backfilled posts too. 2857 2885 { const ej = extractEmojiTags(o.tag); if (ej) { try { db.prepare('UPDATE ap_timeline SET emoji_json = ? WHERE id = ? AND slug = ?').run(ej, o.id, slug); } catch { /* ignore */ } } } 2886 storeAuthorEmoji(o.id, slug, ai); // custom-emoji display name for the byline 2858 2887 // FEP-e232 + FEP-044f: keep object-link/quote tags from backfilled posts too. 2859 2888 { const lj = extractLinkJson(o); if (lj) { try { db.prepare('UPDATE ap_timeline SET link_json = ? WHERE id = ? AND slug = ?').run(lj, o.id, slug); } catch { /* ignore */ } } } … … 2980 3009 if (cur >= SELFHEAL_VERSION) return; // already healed for this version — skip on normal boots 2981 3010 let rows = []; 2982 try { rows = db.prepare('SELECT id, content, media_json, nsfw, cw, url, emoji_json, link_json, quote_json FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ }3011 try { rows = db.prepare('SELECT id, content, media_json, nsfw, cw, url, emoji_json, link_json, quote_json, author_uri, author_name, author_emoji_json FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ } 2983 3012 let healed = 0, failed = 0; 2984 3013 for (const r of rows) { … … 3000 3029 db.prepare('UPDATE ap_timeline SET content = ?, media_json = ?, nsfw = ?, cw = ?, url = COALESCE(?, url), emoji_json = ?, link_json = ?, quote_json = ? WHERE id = ?').run(html || r.content, media, nsfw, cw, url, emoji, link, quote, r.id); 3001 3030 healed++; 3031 } 3032 // v13: a custom-emoji display name needs the author's emoji map. Fetch 3033 // the actor once, only for rows whose name has a shortcode and no map yet. 3034 if (/:[A-Za-z0-9_+-]+:/.test(r.author_name || '') && !r.author_emoji_json && r.author_uri) { 3035 const ai = actorInfo(await fetchActor(r.author_uri), r.author_uri); 3036 if (ai.emojis) { try { db.prepare('UPDATE ap_timeline SET author_emoji_json = ? WHERE id = ?').run(JSON.stringify(ai.emojis), r.id); } catch { /* ignore */ } } 3002 3037 } 3003 3038 } catch { failed++; /* per-note best-effort */ }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)