mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Merge pull request #2150 from PR0M3TH3AN/claude/fix-dm-block-mute-awF1A
Add DM block/mute filtering to prevent messages from blocked users
This commit is contained in:
5 files changed
+246
-1
No files matched your search
@@ -628,6 +628,15 @@ class Application {
|
||||
this.authService.hydrateFromStorage();
|
||||
this.renderSavedProfiles();
|
||||
|
||||
if (
|
||||
this.nostrService &&
|
||||
typeof this.nostrService.setDmBlockChecker === "function"
|
||||
) {
|
||||
this.nostrService.setDmBlockChecker((pubkey) =>
|
||||
this.isAuthorBlocked(pubkey),
|
||||
);
|
||||
}
|
||||
|
||||
loadModerationOverridesFromStorage();
|
||||
loadModerationSettingsFromStorage();
|
||||
loadDmPrivacySettingsFromStorage();
|
||||
|
||||
@@ -635,6 +635,7 @@ export class NostrService {
|
||||
logger: userLogger,
|
||||
});
|
||||
this.dmPreviewCache = new LRUCache({ maxSize: DM_PREVIEW_CACHE_LIMIT });
|
||||
this._dmBlockChecker = null;
|
||||
try {
|
||||
if (this.moderationService && typeof this.moderationService.setNostrClient === "function") {
|
||||
this.moderationService.setNostrClient(this.nostrClient);
|
||||
@@ -644,6 +645,28 @@ export class NostrService {
|
||||
}
|
||||
}
|
||||
|
||||
setDmBlockChecker(checker) {
|
||||
this._dmBlockChecker =
|
||||
typeof checker === "function" ? checker : null;
|
||||
}
|
||||
|
||||
_isDmRemoteBlocked(message, actorPubkey) {
|
||||
if (typeof this._dmBlockChecker !== "function") {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const actor = normalizeHexPubkey(actorPubkey || this.dmActorPubkey || "");
|
||||
const remote = resolveDirectMessageRemotePubkey(message, actor);
|
||||
if (!remote) {
|
||||
return false;
|
||||
}
|
||||
return this._dmBlockChecker(remote);
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostrService] Block check failed for DM", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
try {
|
||||
this.logger(...args);
|
||||
@@ -810,6 +833,13 @@ export class NostrService {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._isDmRemoteBlocked(message, actorPubkey)) {
|
||||
devLogger.log("[nostrService] Skipping persist for blocked DM sender", {
|
||||
id: record.id,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
await writeMessages(record);
|
||||
const notificationResult = await this.dmNotificationManager.recordMessage({
|
||||
@@ -876,6 +906,11 @@ export class NostrService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isDmRemoteBlocked(normalized)) {
|
||||
devLogger.log("[nostrService] Dropping DM from blocked user", { eventId });
|
||||
return;
|
||||
}
|
||||
|
||||
this.dmMessageIndex.set(eventId, normalized);
|
||||
|
||||
const actor = normalizeHexPubkey(
|
||||
|
||||
@@ -798,8 +798,28 @@ export default class ApplicationBootstrap {
|
||||
try {
|
||||
const summaries =
|
||||
await app.nostrService.listDirectMessageConversationSummaries();
|
||||
const actorHex =
|
||||
typeof app.normalizeHexPubkey === "function" && app.pubkey
|
||||
? app.normalizeHexPubkey(app.pubkey)
|
||||
: "";
|
||||
const canCheckBlock =
|
||||
userBlocks && typeof userBlocks.isBlocked === "function";
|
||||
const totalUnseen = Array.isArray(summaries)
|
||||
? summaries.reduce((sum, summary) => {
|
||||
if (canCheckBlock && actorHex && summary?.conversation_id) {
|
||||
const parts = summary.conversation_id.split(":");
|
||||
const remote =
|
||||
parts.length === 3 && parts[0] === "dm"
|
||||
? parts[1] === actorHex
|
||||
? parts[2]
|
||||
: parts[2] === actorHex
|
||||
? parts[1]
|
||||
: ""
|
||||
: "";
|
||||
if (remote && userBlocks.isBlocked(remote)) {
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
const count = Number(summary?.unseen_count);
|
||||
return sum + (Number.isFinite(count) ? count : 0);
|
||||
}, 0)
|
||||
|
||||
@@ -4197,7 +4197,15 @@ export class ProfileModalController {
|
||||
};
|
||||
}
|
||||
|
||||
const threads = this.groupDirectMessages(messages, actor);
|
||||
const allThreads = this.groupDirectMessages(messages, actor);
|
||||
const blocksService = this.services.userBlocks;
|
||||
const isRemoteBlocked =
|
||||
blocksService && typeof blocksService.isBlocked === "function"
|
||||
? (pubkey) => blocksService.isBlocked(pubkey)
|
||||
: () => false;
|
||||
const threads = allThreads.filter(
|
||||
(thread) => !thread.remoteHex || !isRemoteBlocked(thread.remoteHex),
|
||||
);
|
||||
const remoteKeys = new Set();
|
||||
threads.forEach((thread) => {
|
||||
if (thread.remoteHex) {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import nostrService from "../js/services/nostrService.js";
|
||||
|
||||
const ACTOR_PUBKEY = "aaaa".repeat(16);
|
||||
const BLOCKED_PUBKEY = "bbbb".repeat(16);
|
||||
const ALLOWED_PUBKEY = "cccc".repeat(16);
|
||||
|
||||
function makeDmMessage({ senderPubkey, receiverPubkey, eventId, direction }) {
|
||||
return {
|
||||
ok: true,
|
||||
direction: direction || "incoming",
|
||||
sender: { pubkey: senderPubkey },
|
||||
recipients: [{ pubkey: receiverPubkey }],
|
||||
remotePubkey:
|
||||
direction === "outgoing" ? receiverPubkey : senderPubkey,
|
||||
actorPubkey: direction === "outgoing" ? senderPubkey : receiverPubkey,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
plaintext: "Hello",
|
||||
event: {
|
||||
id: eventId || `evt-${Math.random().toString(36).slice(2)}`,
|
||||
kind: 4,
|
||||
pubkey: senderPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [["p", receiverPubkey]],
|
||||
},
|
||||
message: {
|
||||
kind: 4,
|
||||
pubkey: senderPubkey,
|
||||
tags: [["p", receiverPubkey]],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("DM block/mute filtering", () => {
|
||||
beforeEach(() => {
|
||||
nostrService.dmMessages = [];
|
||||
nostrService.dmMessageIndex = new Map();
|
||||
nostrService.dmActorPubkey = ACTOR_PUBKEY;
|
||||
nostrService.dmHydratedFromSnapshot = false;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nostrService.setDmBlockChecker(null);
|
||||
nostrService.dmMessages = [];
|
||||
nostrService.dmMessageIndex = new Map();
|
||||
nostrService.dmActorPubkey = null;
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
describe("setDmBlockChecker", () => {
|
||||
it("should accept a function", () => {
|
||||
nostrService.setDmBlockChecker(() => false);
|
||||
assert.equal(typeof nostrService._dmBlockChecker, "function");
|
||||
});
|
||||
|
||||
it("should clear checker when passed null", () => {
|
||||
nostrService.setDmBlockChecker(() => true);
|
||||
nostrService.setDmBlockChecker(null);
|
||||
assert.equal(nostrService._dmBlockChecker, null);
|
||||
});
|
||||
|
||||
it("should clear checker when passed non-function", () => {
|
||||
nostrService.setDmBlockChecker(() => true);
|
||||
nostrService.setDmBlockChecker("not-a-function");
|
||||
assert.equal(nostrService._dmBlockChecker, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyDirectMessage with block checker", () => {
|
||||
it("should drop messages from blocked senders", () => {
|
||||
nostrService.setDmBlockChecker((pubkey) => pubkey === BLOCKED_PUBKEY);
|
||||
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: BLOCKED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
eventId: "blocked-msg-1",
|
||||
direction: "incoming",
|
||||
});
|
||||
|
||||
nostrService.applyDirectMessage(message, { reason: "test" });
|
||||
|
||||
assert.equal(nostrService.dmMessages.length, 0);
|
||||
assert.equal(nostrService.dmMessageIndex.has("blocked-msg-1"), false);
|
||||
});
|
||||
|
||||
it("should allow messages from non-blocked senders", () => {
|
||||
nostrService.setDmBlockChecker((pubkey) => pubkey === BLOCKED_PUBKEY);
|
||||
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: ALLOWED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
eventId: "allowed-msg-1",
|
||||
direction: "incoming",
|
||||
});
|
||||
|
||||
nostrService.applyDirectMessage(message, { reason: "test" });
|
||||
|
||||
assert.equal(nostrService.dmMessages.length, 1);
|
||||
});
|
||||
|
||||
it("should allow all messages when no block checker is set", () => {
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: BLOCKED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
eventId: "no-checker-msg-1",
|
||||
direction: "incoming",
|
||||
});
|
||||
|
||||
nostrService.applyDirectMessage(message, { reason: "test" });
|
||||
|
||||
assert.equal(nostrService.dmMessages.length, 1);
|
||||
});
|
||||
|
||||
it("should block outgoing messages to blocked recipients", () => {
|
||||
nostrService.setDmBlockChecker((pubkey) => pubkey === BLOCKED_PUBKEY);
|
||||
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: ACTOR_PUBKEY,
|
||||
receiverPubkey: BLOCKED_PUBKEY,
|
||||
eventId: "outgoing-blocked-1",
|
||||
direction: "outgoing",
|
||||
});
|
||||
|
||||
nostrService.applyDirectMessage(message, { reason: "test" });
|
||||
|
||||
assert.equal(nostrService.dmMessages.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_isDmRemoteBlocked", () => {
|
||||
it("should return false when no checker is set", () => {
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: BLOCKED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
direction: "incoming",
|
||||
});
|
||||
assert.equal(nostrService._isDmRemoteBlocked(message), false);
|
||||
});
|
||||
|
||||
it("should return true for blocked remote pubkey", () => {
|
||||
nostrService.setDmBlockChecker((pubkey) => pubkey === BLOCKED_PUBKEY);
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: BLOCKED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
direction: "incoming",
|
||||
});
|
||||
assert.equal(nostrService._isDmRemoteBlocked(message), true);
|
||||
});
|
||||
|
||||
it("should return false for allowed remote pubkey", () => {
|
||||
nostrService.setDmBlockChecker((pubkey) => pubkey === BLOCKED_PUBKEY);
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: ALLOWED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
direction: "incoming",
|
||||
});
|
||||
assert.equal(nostrService._isDmRemoteBlocked(message), false);
|
||||
});
|
||||
|
||||
it("should handle checker that throws", () => {
|
||||
nostrService.setDmBlockChecker(() => {
|
||||
throw new Error("checker error");
|
||||
});
|
||||
const message = makeDmMessage({
|
||||
senderPubkey: BLOCKED_PUBKEY,
|
||||
receiverPubkey: ACTOR_PUBKEY,
|
||||
direction: "incoming",
|
||||
});
|
||||
assert.equal(nostrService._isDmRemoteBlocked(message), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user