mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-09 14:22:53 +00:00
Publish trusted mute lists with block updates
This commit is contained in:
1 parent
3d1a615ed3
commit
ca8bbcad9b
4 files changed
+361
-1
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user