Publish trusted mute lists with block updates

This commit is contained in:
thePR0M3TH3AN committed 2025-10-25 15:22:52 -04:00
1 parent 3d1a615ed3
commit ca8bbcad9b
4 files changed
+361 -1

No files matched your search

+148
View File
@@ -246,6 +246,8 @@ class UserBlockListManager {
this.blockEventId = null;
this.blockEventCreatedAt = null;
this.lastPublishedCreatedAt = null;
this.muteEventId = null;
this.muteEventCreatedAt = null;
this.loaded = false;
this.emitter = new TinyEventEmitter();
this.seedStateCache = new Map();
@@ -256,6 +258,8 @@ class UserBlockListManager {
this.blockEventId = null;
this.blockEventCreatedAt = null;
this.lastPublishedCreatedAt = null;
this.muteEventId = null;
this.muteEventCreatedAt = null;
this.loaded = false;
}
@@ -941,6 +945,137 @@ class UserBlockListManager {
}
}
async publishMuteListSnapshot({
signer,
ownerPubkey,
blockedPubkeys = [],
createdAt,
plaintext = "",
onStatus,
} = {}) {
const owner = normalizeHex(ownerPubkey);
if (!owner || !signer || typeof signer.signEvent !== "function") {
return null;
}
const tagSet = new Set();
const tags = [];
const iterateBlocked = (input) => {
if (Array.isArray(input)) {
return input;
}
if (input instanceof Set) {
return Array.from(input);
}
if (input && typeof input[Symbol.iterator] === "function") {
return Array.from(input);
}
return [];
};
for (const value of iterateBlocked(blockedPubkeys)) {
const normalized = normalizeHex(value);
if (!normalized || normalized === owner || tagSet.has(normalized)) {
continue;
}
tagSet.add(normalized);
tags.push(["p", normalized]);
}
const timestamp = Number.isFinite(createdAt)
? Math.max(0, Math.floor(createdAt))
: Math.floor(Date.now() / 1000);
const event = {
kind: 10000,
pubkey: owner,
created_at: timestamp,
tags,
content: "",
};
const payloadText = typeof plaintext === "string" ? plaintext : "";
let encryptedContent = "";
let encryptionTagValue = "";
if (payloadText) {
const encryptionCandidates = [];
if (typeof signer.nip44Encrypt === "function") {
encryptionCandidates.push({ tag: "nip44_v2", encrypt: signer.nip44Encrypt });
}
if (typeof signer.nip04Encrypt === "function") {
encryptionCandidates.push({ tag: "nip04", encrypt: signer.nip04Encrypt });
}
for (const candidate of encryptionCandidates) {
try {
const encrypted = await candidate.encrypt(owner, payloadText);
if (typeof encrypted === "string" && encrypted) {
encryptedContent = encrypted;
encryptionTagValue = candidate.tag;
break;
}
} catch (error) {
// Ignore encryption failures and fall back to the next candidate.
}
}
}
if (encryptedContent) {
event.content = encryptedContent;
if (encryptionTagValue) {
event.tags.push(["encrypted", encryptionTagValue]);
}
}
onStatus?.({ status: "mute-publishing" });
let signedEvent;
try {
signedEvent = await signer.signEvent(event);
} catch (error) {
const wrapped =
error instanceof Error ? error : new Error(String(error || "mute-signature-failed"));
wrapped.code = wrapped.code || "mute-signature-failed";
throw wrapped;
}
const publishResults = await publishEventToRelays(
nostrClient.pool,
nostrClient.relays,
signedEvent,
);
const summary = assertAnyRelayAccepted(publishResults, { context: "mute list" });
if (summary.failed.length) {
summary.failed.forEach(({ url, error: relayError }) => {
const reason =
relayError instanceof Error
? relayError.message
: relayError
? String(relayError)
: "publish failed";
userLogger.warn(
`[UserBlockList] Mute list not accepted by ${url}: ${reason}`,
relayError,
);
});
}
this.muteEventId = signedEvent.id;
this.muteEventCreatedAt = Number.isFinite(signedEvent?.created_at)
? signedEvent.created_at
: event.created_at;
onStatus?.({ status: "mute-published", event: signedEvent });
return signedEvent;
}
async publishBlockList(userPubkey, options = {}) {
const onStatus =
options && typeof options.onStatus === "function" ? options.onStatus : null;
@@ -1062,6 +1197,19 @@ class UserBlockListManager {
this.lastPublishedCreatedAt = this.blockEventCreatedAt;
onStatus?.({ status: "published", event: signedEvent });
try {
await this.publishMuteListSnapshot({
signer,
ownerPubkey: normalized,
blockedPubkeys: payload.blockedPubkeys,
createdAt: event.created_at,
plaintext,
onStatus,
});
} catch (muteError) {
userLogger.warn("[UserBlockList] Failed to publish mute list", muteError);
}
try {
await this.loadBlocks(normalized, {
since: this.lastPublishedCreatedAt ?? undefined,