Changeset 4838760 in Klonkt
- Timestamp:
- 08/08/2026 05:23:46 PM (4 weeks ago)
- Branches:
- main
- Children:
- d1075a1
- Parents:
- abcf51a
- Files:
-
- 1 added
- 3 edited
-
src/config/database.js (modified) (1 diff)
-
src/routes/activitypub.js (modified) (2 diffs)
-
src/services/ActivityPubService.js (modified) (4 diffs)
-
test/own-card.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
rabcf51a r4838760 718 718 // time so buildNote (sync, also used by the outbox) needs no network. 719 719 ensureColumn('posts', 'quote_uri', 'TEXT'); // the quoted object's id 720 // De kaart op je EIGEN post (shaer-k3f): dezelfde snapshots die ap_timeline 721 // voor binnenkomende posts draagt, maar dan voor wat je zelf publiceert -- 722 // zonder deze twee kan de app een eigen post nooit als kaart tonen. 723 ensureColumn('posts', 'quote_json', 'TEXT'); // FEP-044f resolved quote snapshot 724 ensureColumn('posts', 'embed_json', 'TEXT'); // externe linkkaart (oEmbed/OG), thumbnail-only 720 725 ensureColumn('posts', 'quote_actor', 'TEXT'); // its author, so we can address them 721 726 ensureColumn('ap_timeline', 'embed_json', 'TEXT'); // resolved EXTERNAL embed (oEmbed/provider), thumbnail-only; gated per site (sites.external_embeds) -
src/routes/activitypub.js
rabcf51a r4838760 191 191 // entries carry, so your own cards get a header too (avatar + name). 192 192 const me = AP.selfAuthor(baseUrl(req), site); 193 // De kaart op je eigen post (shaer-k3f): dezelfde shaer:quote/shaer:embed 194 // die de tijdlijn voor andermans posts draagt, uit de snapshots die 195 // deliverCreate bij het publiceren opsloeg. Op note-id gekoppeld, want 196 // buildOutbox sorteert en mengt tracks erdoorheen. De embed alleen voor de 197 // BEARER en langs zijn eigen poort: een remote vriend krijgt hem niet 198 // (diens server resolvet en gate zelf bij ontvangst), en een ward zonder 199 // open embeds-poort krijgt hem hier net zo min als in de tijdlijn. 200 const byNote = new Map(posts.map((p) => [AP.noteId(baseUrl(req), p.id), p])); 201 const bearerEmbeds = bearer ? (() => { 202 const isWard = (() => { try { return Guardianship.listGuardians(bearer.site.slug).length > 0; } catch { return false; } })(); 203 return Guardianship.externalEmbedsAllowed(bearer.site.external_embeds, isWard) 204 ? { playback: Guardianship.externalPlaybackAllowed(bearer.site.external_playback, isWard) } : null; 205 })() : null; 193 206 for (const it of ob.orderedItems) { 194 if (it && it.object && typeof it.object === 'object') it.object['shaer:author'] = me; 207 if (it && it.object && typeof it.object === 'object') { 208 it.object['shaer:author'] = me; 209 const row = byNote.get(it.object.id); 210 if (row) { 211 it.object['shaer:quote'] = AP.timelineQuote(row.quote_json); 212 if (bearerEmbeds) it.object['shaer:embed'] = AP.timelineEmbed(row.embed_json, { playback: bearerEmbeds.playback }); 213 } 214 } 195 215 } 196 216 } … … 813 833 814 834 // ── Replies collection ── lets remote servers fetch a post's whole thread. 835 // ── De composer-preview (shaer-k3f): een URL wordt alvast een kaart ── 836 // 837 // Bearer-only, net als de thread: dit is de eigen app die tijdens het typen 838 // vraagt wat een link gaat worden. Dezelfde pijplijn als publiceren, dus de 839 // preview kan niet iets beloven dat de post niet waarmaakt. De embed gaat 840 // langs de eigen poort van de lezer -- een ward zonder open embeds-poort 841 // krijgt in de composer geen kaart die zijn feed hem ook niet zou tonen. 842 router.get('/ap/users/:slug/card', async (req, res) => { 843 const auth = OAuth.verifyBearer(req.headers.authorization); 844 if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end(); 845 const uit = await AP.previewCard(String(req.query.url || '')); 846 const isWard = (() => { try { return Guardianship.listGuardians(auth.site.slug).length > 0; } catch { return false; } })(); 847 const embedsAllowed = Guardianship.externalEmbedsAllowed(auth.site.external_embeds, isWard); 848 const playback = embedsAllowed && Guardianship.externalPlaybackAllowed(auth.site.external_playback, isWard); 849 AP.sendAP(res, { 850 '@context': AP.AP_CONTEXT, 851 'shaer:quote': AP.timelineQuote(uit.quoteJson), 852 'shaer:embed': embedsAllowed ? AP.timelineEmbed(uit.embedJson, { playback }) : undefined, 853 }, 'private, no-store'); 854 }); 855 815 856 // ── De thread onder een post (shaer-tqz): ophalen, niet bewaren ──── 816 857 // -
src/services/ActivityPubService.js
rabcf51a r4838760 2955 2955 post2 = { ...post2, quote_uri: q.uri, quote_actor: q.actor || null }; 2956 2956 } 2957 } 2958 // De kaart op de eigen post (shaer-k3f), langs dezelfde pijplijn als een 2959 // binnenkomende: een fediverse-quote wordt een quote-snapshot, anders 2960 // probeert de link een externe kaart. VOOR de vroege return hieronder, want 2961 // ook een post zonder volgers hoort zijn kaart te krijgen -- de app leest 2962 // hem uit de outbox, niet uit een bezorging. Best-effort en eenmalig: wat 2963 // hier niet lukt blijft een kale link, precies wat het was. 2964 if (!post2.quote_json && !post2.embed_json) { 2965 try { 2966 if (post2.quote_uri) { 2967 const qj = await resolveQuoteByUri(post2.quote_uri); 2968 if (qj) { db.prepare('UPDATE posts SET quote_json = ? WHERE id = ?').run(qj, post.id); post2 = { ...post2, quote_json: qj }; } 2969 } else { 2970 const ej = await resolveExternalEmbed(post2.content || ''); 2971 if (ej) { db.prepare('UPDATE posts SET embed_json = ? WHERE id = ?').run(ej, post.id); post2 = { ...post2, embed_json: ej }; } 2972 } 2973 } catch { /* een kaart is nooit een blokkade voor de post zelf */ } 2957 2974 } 2958 2975 if (post2.quote_actor) { … … 4996 5013 } 4997 5014 5015 /** 5016 * De composer-preview (shaer-k3f): één URL langs exact dezelfde pijplijn als 5017 * publiceren, zodat wat de preview toont ook is wat de post krijgt. Twee 5018 * uitkomsten, hoogstens een gevuld: een AP-object wordt een quote-snapshot, 5019 * een externe link probeert een kaart. Beide als JSON-string, dezelfde vorm 5020 * als de kolommen -- de route serveert ze door timelineQuote/timelineEmbed en 5021 * de gate, net als de tijdlijn. 5022 */ 5023 export async function previewCard(url) { 5024 if (!/^https?:\/\//i.test(String(url || ''))) return {}; 5025 const html = `<a href="${String(url).replace(/"/g, '"')}">x</a>`; 5026 const q = await resolveOwnQuote(html); 5027 if (q && q.uri) { 5028 const quoteJson = await resolveQuoteByUri(q.uri).catch(() => null); 5029 if (quoteJson) return { quoteJson }; 5030 } else { 5031 const embedJson = await resolveExternalEmbed(html).catch(() => null); 5032 if (embedJson) return { embedJson }; 5033 } 5034 return {}; 5035 } 5036 4998 5037 /** The first http(s) link in sanitized note HTML that is not a mention/hashtag. */ 4999 5038 export function firstExternalUrl(html) { … … 5051 5090 const url = quoteHrefOf(note); 5052 5091 if (!url) return null; 5092 return resolveQuoteByUri(url); 5093 } 5094 5095 /** Hetzelfde snapshot, maar vanaf een kale URI: eigen posts en de 5096 * composer-preview (shaer-k3f) kennen alleen de link, niet de tag-vorm. */ 5097 async function resolveQuoteByUri(url) { 5053 5098 const q = await apGetJson(url); 5054 5099 if (!q || typeof q !== 'object') return null; … … 6270 6315 acceptGatedFollow, rejectGatedFollow, isWardGuardian, outboxAudience, sendFollowDecision, 6271 6316 gateOutgoingFollow, performApprovedFollow, 6272 parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs, 6317 parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs, previewCard, 6273 6318 autoBoostCount, boostedCount, setReaction, getReaction, getReactionsFor, canonicalReactionUri, migrateReactions, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline, 6274 6319 getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
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)