Changeset 155c24e in Klonkt
- Timestamp:
- 07/20/2026 10:52:58 PM (7 weeks ago)
- Branches:
- main
- Children:
- 7922694
- Parents:
- e6c6e6f
- git-author:
- Robin <roboburr@…> (07/20/2026 10:52:57 PM)
- git-committer:
- Robin <roboburr@…> (07/20/2026 10:52:58 PM)
- Files:
-
- 3 edited
-
src/config/database.js (modified) (1 diff)
-
src/services/ActivityPubService.js (modified) (5 diffs)
-
test/c2s-direct.test.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
re6c6e6f r155c24e 443 443 ensureColumn('ap_outbox', 'visibility', 'TEXT'); // 'direct' = private mention, never Public (shaer-tqc) 444 444 ensureColumn('ap_outbox', 'to_actors', 'TEXT'); // JSON array of recipient actor URIs for direct notes 445 ensureColumn('ap_outbox', 'help_request', 'INTEGER'); // FEP-633c shaer:helpRequest (ward's call for help) 445 446 } 446 447 -
src/services/ActivityPubService.js
re6c6e6f r155c24e 46 46 // Question stays valid JSON-LD (a strict processor would otherwise drop votersCount). 47 47 votersCount: 'toot:votersCount', 48 // FEP-633c (Guardians): the shaer namespace. helpRequest marks a direct 49 // note as a ward's call for help (spec 5.2.1); ignorable by everyone else. 50 shaer: 'https://ns.klonkt.com/shaer#', 48 51 }, 49 52 ]; … … 259 262 : (post.to_actor ? [post.to_actor] : [PUBLIC]), 260 263 cc: post.visibility === 'direct' ? [] : [PUBLIC, `${meR}/followers`], 264 // FEP-633c 5.2.1: a ward's call for help. Only ever on direct notes. 265 'shaer:helpRequest': (post.visibility === 'direct' && post.help_request) ? true : undefined, 261 266 tag: [ 262 267 ...mentionTags(post.content), … … 1855 1860 } : null) 1856 1861 .filter(Boolean); 1857 const r = await deliverDirectNote(site, { recipients, text: plain, language: object.language || null, inReplyTo: typeof object.inReplyTo === 'string' ? object.inReplyTo : null, attachments: atts }); 1862 const help = object['shaer:helpRequest'] === true || object.helpRequest === true; 1863 const r = await deliverDirectNote(site, { recipients, text: plain, language: object.language || null, inReplyTo: typeof object.inReplyTo === 'string' ? object.inReplyTo : null, attachments: atts, helpRequest: help }); 1858 1864 if (!r || !r.id) return { status: 502, error: 'direct_failed' }; 1859 1865 return { status: 201, id: r.id, url: `${base}/ap/notes/${r.id}` }; … … 1971 1977 // The same S2S leg a Mastodon DM takes, so a guardian on any instance 1972 1978 // receives it as a private mention (the ward call-for-help path). 1973 export async function deliverDirectNote(site, { recipients, text, language, inReplyTo, attachments }) {1979 export async function deliverDirectNote(site, { recipients, text, language, inReplyTo, attachments, helpRequest }) { 1974 1980 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 1975 1981 const list = [...new Set((recipients || []).filter((u) => /^https?:\/\//i.test(String(u || ''))))].slice(0, 8); … … 1999 2005 .map((a) => ({ url: a.url, mediaType: String(a.mediaType), name: String(a.name || '').slice(0, 120) })); 2000 2006 const id = crypto.randomUUID(); 2001 db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, created_at)2002 VALUES (?,?,?,?,?,?,?,?,?,?,?,?, CURRENT_TIMESTAMP)`)2003 .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)) );2007 db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, created_at) 2008 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`) 2009 .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0); 2004 2010 const row = iStmts().getO.get(id); 2005 2011 const note = buildReplyNote(base, site, row); -
test/c2s-direct.test.js
re6c6e6f r155c24e 38 38 }); 39 39 40 test('a help request emits shaer:helpRequest (FEP-633c 5.2.1)', () => { 41 db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, visibility, to_actors, help_request, created_at) 42 VALUES ('d3','me','',NULL,NULL,'https://r.test/u/g','@g@r.test','<p>help</p>','direct','["https://r.test/u/g"]',1,CURRENT_TIMESTAMP)`).run(); 43 const row = db.prepare('SELECT * FROM ap_outbox WHERE id = ?').get('d3'); 44 const note = AP.buildReplyNote('https://test.example', site, row); 45 assert.equal(note['shaer:helpRequest'], true); 46 assert.deepEqual(note.cc, []); // still strictly direct 47 // and the namespace is declared, so the term is valid JSON-LD 48 const ctx = AP.AP_CONTEXT.find((p) => p && typeof p === 'object'); 49 assert.equal(ctx.shaer, 'https://ns.klonkt.com/shaer#'); 50 // a plain direct note (no flag) does NOT carry the term 51 const plainRow = db.prepare('SELECT * FROM ap_outbox WHERE id = ?').get('d1'); 52 assert.equal(AP.buildReplyNote('https://test.example', site, plainRow)['shaer:helpRequest'], undefined); 53 }); 54 40 55 test('direct without any real recipient is refused (400 no_recipients)', async () => { 41 56 const r = await AP.ingestOutboxActivity(site, user, {
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)