Changeset 3778ddb in Klonkt for src/services/ActivityPubService.js
- Timestamp:
- 07/17/2026 12:20:48 AM (8 weeks ago)
- Branches:
- main
- Children:
- 67c1f24
- Parents:
- 9dbb175
- git-author:
- Robin <roboburr@…> (07/17/2026 12:20:29 AM)
- git-committer:
- Robin <roboburr@…> (07/17/2026 12:20:48 AM)
- File:
-
- 1 edited
-
src/services/ActivityPubService.js (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/ActivityPubService.js
r9dbb175 r3778ddb 596 596 // ── inbound interactions store (replies / likes / boosts) + our outbound replies ── 597 597 let _insI, _delLA, _delReply, _listI, _getI, _insO, _listO, _getO; 598 // AP addressing → visibility: 'public' | 'unlisted' | 'followers' | 'direct'. 599 // Mastodon-conventie: Public in `to` = public, Public in `cc` = unlisted, een 600 // followers-collectie zonder Public = followers-only, anders direct (DM). Public 601 // kan als volledige URI, 'as:Public' of 'Public' voorkomen (JSON-LD shorthands). 602 export function noteVisibility(o) { 603 const arr = (v) => (Array.isArray(v) ? v : (v ? [v] : [])); 604 const isPub = (u) => u === PUBLIC || u === 'as:Public' || u === 'Public'; 605 const to = arr(o && o.to).map(String); 606 const cc = arr(o && o.cc).map(String); 607 if (to.some(isPub)) return 'public'; 608 if (cc.some(isPub)) return 'unlisted'; 609 if ([...to, ...cc].some((u) => /\/followers\/?$/.test(u))) return 'followers'; 610 return 'direct'; 611 } 612 598 613 function iStmts() { 599 614 if (!_insI) { 600 _insI = db.prepare('INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, parent_uri, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');615 _insI = db.prepare('INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, parent_uri, visibility, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)'); 601 616 _delLA = db.prepare('DELETE FROM ap_interactions WHERE kind = ? AND post_id = ? AND actor_uri = ?'); 602 617 _delReply = db.prepare("DELETE FROM ap_interactions WHERE kind = 'reply' AND object_uri = ?"); 603 _listI = db.prepare('SELECT id, kind, object_uri, parent_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at, acted_boost, acted_like FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC');618 _listI = db.prepare('SELECT id, kind, object_uri, parent_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at, acted_boost, acted_like, visibility FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC'); 604 619 _getI = db.prepare('SELECT * FROM ap_interactions WHERE id = ?'); 605 620 _insO = db.prepare('INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, created_at) VALUES (?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)'); … … 678 693 export function getInteractions(postId, base, site) { 679 694 const s = iStmts(); 680 const rows = s.list.all(postId); 695 // Privacy: a followers-only or direct (DM) reply is addressed to people, not to the 696 // public web, so it must NOT render in the public thread. It still reaches the owner 697 // via notifications (post context + reference included there). Legacy rows without a 698 // visibility value are treated as public. Likes/boosts stay counted (count-only). 699 const rows = s.list.all(postId).filter((r) => 700 r.kind !== 'reply' || !(r.visibility === 'followers' || r.visibility === 'direct')); 681 701 const baseClean = (base || process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 682 702 const postNoteId = baseClean ? `${baseClean}/ap/notes/${postId}` : null; … … 1145 1165 const ai = actorInfo(await resolveActor(actorUri), actorUri); 1146 1166 const html = HtmlSanitizerService.sanitize(o.content || ''); 1147 iStmts().ins.run('reply', tgt.post_id, o.id || '', actorUri, ai.name, ai.handle, ai.url, ai.icon, html, o.published || null, tgt.parent_uri );1167 iStmts().ins.run('reply', tgt.post_id, o.id || '', actorUri, ai.name, ai.handle, ai.url, ai.icon, html, o.published || null, tgt.parent_uri, noteVisibility(o)); 1148 1168 console.log('[AP] reply', actorUri, '→', tgt.post_id); 1149 1169 return 202; … … 1235 1255 if (pid && actorUri && !isLocalActor && localPostExists(pid)) { 1236 1256 const ai = actorInfo(await resolveActor(actorUri), actorUri); 1237 iStmts().ins.run(type.toLowerCase(), pid, '', actorUri, ai.name, ai.handle, ai.url, ai.icon, null, null, null );1257 iStmts().ins.run(type.toLowerCase(), pid, '', actorUri, ai.name, ai.handle, ai.url, ai.icon, null, null, null, noteVisibility(act)); 1238 1258 console.log('[AP]', type === 'Like' ? 'like' : 'boost', actorUri, '→', pid); 1239 1259 } else if (type === 'Announce' && objUrl && actorUri && !isLocalActor) { … … 2137 2157 const html = HtmlSanitizerService.sanitize(child.content || ''); 2138 2158 // The child replies to `note` by construction (it's in note's replies collection). 2139 try { iStmts().ins.run('reply', postId, child.id, actorUri, ai.name, ai.handle, ai.url, ai.icon, html, child.published || null, note.id || noteUri ); added++; } catch { /* ignore */ }2159 try { iStmts().ins.run('reply', postId, child.id, actorUri, ai.name, ai.handle, ai.url, ai.icon, html, child.published || null, note.id || noteUri, noteVisibility(child)); added++; } catch { /* ignore */ } 2140 2160 nextFrontier.push(child.id); // expand this reply's own replies next depth 2141 2161 } … … 2524 2544 getReplyUris, markNotificationsSeen, countUnseenNotifications, hasPlayableAudio, 2525 2545 linkifyBody, bakePostContent, bakePostContentWithMentions, listFollowers, removeFollower, listConnections, 2546 noteVisibility, 2526 2547 };
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)