Changeset 3778ddb in Klonkt
- 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)
- Files:
-
- 1 added
- 5 edited
-
CHANGELOG.de.md (modified) (1 diff)
-
CHANGELOG.md (modified) (1 diff)
-
CHANGELOG.nl.md (modified) (1 diff)
-
src/config/database.js (modified) (1 diff)
-
src/services/ActivityPubService.js (modified) (6 diffs)
-
test/reply-visibility.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
CHANGELOG.de.md
r9dbb175 r3778ddb 15 15 16 16 ### Behoben 17 - **Private Antworten erscheinen nicht mehr auf der öffentlichen Beitragsseite.** 18 Eine Nur-Follower- oder Direkt-(DM-)Antwort auf deinen Beitrag wurde für alle 19 im öffentlichen Thread gezeigt. Eingehende Antworten speichern jetzt ihre 20 Fediverse-Adressierung; der öffentliche Thread zeigt nur öffentliche und 21 ungelistete Antworten. Private erreichen dich weiterhin über Meldungen, mit 22 dem zugehörigen Beitrag. 17 23 - **Nackte Video/Audio-Embeds laufen nicht mehr über die Spalte hinaus.** Ein 18 24 `.webm` / `.mp4` / `.mp3`-Player passt jetzt in die Inhaltsbreite wie die -
CHANGELOG.md
r9dbb175 r3778ddb 15 15 16 16 ### Fixed 17 - **Private replies no longer show on the public post page.** A followers-only 18 or direct (DM) reply to your post was rendered in the public thread for 19 everyone. Incoming replies now record their fediverse addressing; the public 20 thread only shows public and unlisted replies. Private ones still reach you in 21 notifications, with the post they belong to. 17 22 - **Bare video/audio embeds no longer overflow their column.** A `.webm` / 18 23 `.mp4` / `.mp3` player now fits the content width like the iframe embeds do; -
CHANGELOG.nl.md
r9dbb175 r3778ddb 15 15 16 16 ### Opgelost 17 - **Privéreacties staan niet meer op de publieke postpagina.** Een followers-only 18 of directe (DM-)reactie op je post werd voor iedereen in de publieke thread 19 getoond. Inkomende reacties slaan nu hun fediverse-adressering op; de publieke 20 thread toont alleen publieke en unlisted reacties. Privéreacties bereiken je 21 nog steeds via meldingen, mét de post waar ze bij horen. 17 22 - **Kale video/audio-embeds lopen niet meer buiten de kolom.** Een `.webm` / 18 23 `.mp4` / `.mp3`-speler past nu netjes in de kolombreedte, net als de -
src/config/database.js
r9dbb175 r3778ddb 392 392 // re-rendering. NULL on old posts → the render route bakes on the fly as a fallback. 393 393 ensureColumn('posts', 'content_rendered', 'TEXT'); 394 395 // AP addressing of an incoming interaction: 'public' | 'unlisted' | 'followers' | 'direct', 396 // derived from the note's to/cc at ingest. The public post page only renders public/unlisted 397 // replies; followers/direct replies surface in notifications (and later Messages) with post 398 // context instead. Existing rows default to 'public' (historically almost all were). 399 ensureColumn('ap_interactions', 'visibility', "TEXT DEFAULT 'public'"); 394 400 } 395 401 -
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)