aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-15 13:15:45 -0700
committerAdam Soltys <[email protected]>2026-08-15 13:15:45 -0700
commite075c24af05e7e3d17277d5fbce32995026f38b6 (patch)
tree7486dffd483b0a244935f510748d6f7566a7210c
parent0b7ae7dbcbfe9688708e5939c94dd01594f43184 (diff)
downloadcoinosv3-e075c24af05e7e3d17277d5fbce32995026f38b6.tar.gz
coinosv3-e075c24af05e7e3d17277d5fbce32995026f38b6.zip
Welcome DMs stop echoing: sent-copies thread under their recipient whichever identity authored them, the registrar keeps no sender copy at all, and the old self-thread residue sweeps itself
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
-rw-r--r--names/server.js7
-rw-r--r--src/features/messages.js24
2 files changed, 25 insertions, 6 deletions
diff --git a/names/server.js b/names/server.js
index 1b1f4bc..a969bde 100644
--- a/names/server.js
+++ b/names/server.js
@@ -162,11 +162,12 @@ async function sendWelcome(pubkey) {
state.welcomed[pubkey] = Date.now();
persist();
try {
- // wraps for [sender-copy, recipient], each to its owner's inbox
- const [toSelf, toPeer] = wrapManyEvents(WELCOME.sk, [{ publicKey: pubkey }], WELCOME.message);
+ // wrapManyEvents returns [sender-copy, recipient] — publish ONLY the
+ // recipient's wrap. A sender copy per registrant flooded Adam's own
+ // clients with an endless stream of his own welcome message.
+ const [, toPeer] = wrapManyEvents(WELCOME.sk, [{ publicKey: pubkey }], WELCOME.message);
const inbox = await inboxRelaysOf(pubkey);
await Promise.allSettled(pool.publish([...new Set([...inbox, ...LNURL_RELAYS])], toPeer));
- await Promise.allSettled(pool.publish(LNURL_RELAYS, toSelf));
log(`welcome DM sent to ${pubkey.slice(0, 12)}`);
} catch (e) {
log('welcome DM failed: ' + e.message);
diff --git a/src/features/messages.js b/src/features/messages.js
index 4b11019..c8f2792 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -910,7 +910,14 @@ export function messagesFeature(ctx) {
const got = await unwrapDM(wrap, d).catch(() => null);
if (!got) continue;
if (got.rumor.kind === 14) {
- noteDM(got.peer, got.rumor, isMe(got.author));
+ // unwrapDM judges "mine" against the key that DECRYPTED, but this
+ // wallet can hold several identities (wallet key + nostr login). A
+ // sent-copy authored by any of them must thread under the RECIPIENT,
+ // or every welcome DM the registrar sends lands in a self-thread.
+ const mine = isMe(got.author);
+ const to = got.rumor.tags?.find((x) => x[0] === 'p')?.[1];
+ const peer = mine ? (to || got.peer || got.author) : got.author;
+ noteDM(peer, got.rumor, mine);
persistDms();
} else if (got.rumor.kind === 3313 && !isMe(got.author)) {
try {
@@ -938,11 +945,22 @@ export function messagesFeature(ctx) {
const pks = myPubkeys();
if (!pks.length) return;
dmStarted = true;
- // warm threads from the local cache
+ // warm threads from the local cache. A self-thread of our own messages is
+ // the residue of the old sent-copy misthreading (each welcome DM landed
+ // under our own key) — sweep it rather than resurrect it; the cached
+ // rows carry no tags, so they cannot be re-threaded to their recipients.
const s = st();
- for (const [peer, list] of Object.entries(s.dms))
+ let swept = false;
+ for (const [peer, list] of Object.entries(s.dms)) {
+ if (isMe(peer) && list.every((m) => isMe(m.from))) {
+ delete s.dms[peer];
+ swept = true;
+ continue;
+ }
for (const m of list)
threadOf(peer).set(m.id, { rumor: { id: m.id, pubkey: m.from, content: m.text, created_at: m.t, kind: 14 }, mine: isMe(m.from) });
+ }
+ if (swept) save(s);
allUnsubs.push(subscribeOn(DM_RELAYS, { kinds: [1059], '#p': pks, limit: 400 }, (wrap) => {
handleInboxWrap(wrap).catch(() => {});
}));