diff --git a/src/policies/MuteListPolicy.test.ts b/src/policies/MuteListPolicy.test.ts new file mode 100644 index 00000000..69561f80 --- /dev/null +++ b/src/policies/MuteListPolicy.test.ts @@ -0,0 +1,72 @@ +import { MockRelay } from '@nostrify/nostrify/test'; + +import { assertEquals } from '@/deps-test.ts'; +import { UserStore } from '@/storages/UserStore.ts'; +import { MuteListPolicy } from '@/policies/MuteListPolicy.ts'; + +import userBlack from '~/fixtures/events/kind-0-black.json' with { type: 'json' }; +import userMe from '~/fixtures/events/event-0-makes-repost-with-quote-repost.json' with { type: 'json' }; +import blockEvent from '~/fixtures/events/kind-10000-black-blocks-user-me.json' with { type: 'json' }; +import event1authorUserMe from '~/fixtures/events/event-1-quote-repost-will-be-reposted.json' with { type: 'json' }; +import event1 from '~/fixtures/events/event-1.json' with { type: 'json' }; + +Deno.test('block event: muted user cannot post', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + const blockEventCopy = structuredClone(blockEvent); + const event1authorUserMeCopy = structuredClone(event1authorUserMe); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + const policy = new MuteListPolicy(userBlack.pubkey, db); + + await store.event(blockEventCopy); + await store.event(userBlackCopy); + await store.event(userMeCopy); + + const ok = await policy.call(event1authorUserMeCopy); + + assertEquals(ok, ['OK', event1authorUserMeCopy.id, false, 'You are banned in this server.']); +}); + +Deno.test('allow event: user is NOT muted because there is no muted event', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + const event1authorUserMeCopy = structuredClone(event1authorUserMe); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + const policy = new MuteListPolicy(userBlack.pubkey, db); + + await store.event(userBlackCopy); + await store.event(userMeCopy); + + const ok = await policy.call(event1authorUserMeCopy); + + assertEquals(ok, ['OK', event1authorUserMeCopy.id, true, '']); +}); + +Deno.test('allow event: user is NOT muted because he is not in mute event', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + const event1authorUserMeCopy = structuredClone(event1authorUserMe); + const blockEventCopy = structuredClone(blockEvent); + const event1copy = structuredClone(event1); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + const policy = new MuteListPolicy(userBlack.pubkey, db); + + await store.event(userBlackCopy); + await store.event(blockEventCopy); + await store.event(userMeCopy); + await store.event(event1copy); + await store.event(event1authorUserMeCopy); + + const ok = await policy.call(event1copy); + + assertEquals(ok, ['OK', event1.id, true, '']); +}); diff --git a/src/policies/MuteListPolicy.ts b/src/policies/MuteListPolicy.ts new file mode 100644 index 00000000..1db85566 --- /dev/null +++ b/src/policies/MuteListPolicy.ts @@ -0,0 +1,18 @@ +import { NostrEvent, NostrRelayOK, NPolicy, NStore } from '@nostrify/nostrify'; + +import { getTagSet } from '@/tags.ts'; + +export class MuteListPolicy implements NPolicy { + constructor(private pubkey: string, private store: NStore) {} + + async call(event: NostrEvent): Promise { + const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); + const pubkeys = getTagSet(muteList?.tags ?? [], 'p'); + + if (pubkeys.has(event.pubkey)) { + return ['OK', event.id, false, 'You are banned in this server.']; + } + + return ['OK', event.id, true, '']; + } +} diff --git a/src/storages/UserStore.test.ts b/src/storages/UserStore.test.ts index 11f96cbd..b1955bd9 100644 --- a/src/storages/UserStore.test.ts +++ b/src/storages/UserStore.test.ts @@ -8,7 +8,7 @@ import userMe from '~/fixtures/events/event-0-makes-repost-with-quote-repost.jso import blockEvent from '~/fixtures/events/kind-10000-black-blocks-user-me.json' with { type: 'json' }; import event1authorUserMe from '~/fixtures/events/event-1-quote-repost-will-be-reposted.json' with { type: 'json' }; -Deno.test('query events of users that are not blocked', async () => { +Deno.test('query events of users that are not muted', async () => { const userBlackCopy = structuredClone(userBlack); const userMeCopy = structuredClone(userMe); const blockEventCopy = structuredClone(blockEvent); @@ -25,3 +25,17 @@ Deno.test('query events of users that are not blocked', async () => { assertEquals(await store.query([{ kinds: [1] }], { limit: 1 }), []); }); + +Deno.test('user never muted anyone', async () => { + const userBlackCopy = structuredClone(userBlack); + const userMeCopy = structuredClone(userMe); + + const db = new MockRelay(); + + const store = new UserStore(userBlackCopy.pubkey, db); + + await store.event(userBlackCopy); + await store.event(userMeCopy); + + assertEquals(await store.query([{ kinds: [0], authors: [userMeCopy.pubkey] }], { limit: 1 }), [userMeCopy]); +}); diff --git a/src/storages/UserStore.ts b/src/storages/UserStore.ts index a3f0726c..1c7aaee2 100644 --- a/src/storages/UserStore.ts +++ b/src/storages/UserStore.ts @@ -1,4 +1,5 @@ import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify'; + import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { getTagSet } from '@/tags.ts'; @@ -16,17 +17,13 @@ export class UserStore implements NStore { } /** - * Query events that `pubkey` did not block + * Query events that `pubkey` did not mute * https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists */ async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise { const allEvents = await this.store.query(filters, opts); - const mutedPubkeysEvent = await this.getMuteList(); - if (!mutedPubkeysEvent) { - return allEvents; - } - const mutedPubkeys = getTagSet(mutedPubkeysEvent.tags, 'p'); + const mutedPubkeys = await this.getMutedPubkeys(); return allEvents.filter((event) => { return event.kind === 0 || mutedPubkeys.has(event.pubkey) === false; @@ -37,4 +34,12 @@ export class UserStore implements NStore { const [muteList] = await this.store.query([{ authors: [this.pubkey], kinds: [10000], limit: 1 }]); return muteList; } + + private async getMutedPubkeys(): Promise> { + const mutedPubkeysEvent = await this.getMuteList(); + if (!mutedPubkeysEvent) { + return new Set(); + } + return getTagSet(mutedPubkeysEvent.tags, 'p'); + } }