From bf005c271b1678cd46784ed10685d310c533c187 Mon Sep 17 00:00:00 2001 From: Daniel Cadenas Date: Thu, 2 May 2024 19:28:15 -0300 Subject: [PATCH] Don't skip seen and unflagged for Slack --- index.js | 11 ++++-- src/lib/duplicationHandling.js | 31 +++++++++++++---- src/lib/nostr.js | 29 +++++++++++++--- src/lib/slack.js | 2 ++ test/moderationFunction.test.js | 61 +++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 5d8a5d4..60ecb54 100644 --- a/index.js +++ b/index.js @@ -23,8 +23,14 @@ functions.cloudEvent("nostrEventsPubSub", async (cloudEvent) => { await RateLimiting.handleRateLimit(async function () { await DuplicationHandling.processIfNotDuplicate( + reportRequest.canBeManualVerified(), nostrEvent, - async (event) => { + async (event, onlySlack) => { + if (onlySlack) { + await Slack.postManualVerification(reportRequest); + return; + } + let eventToModerate = event; let skipMessage = `Nostr Event ${event.id} passed moderation. Skipping`; @@ -43,12 +49,11 @@ functions.cloudEvent("nostrEventsPubSub", async (cloudEvent) => { ); if (!moderation) { - if (!reportRequest.reporterPubkey) { + if (!reportRequest.canBeManualVerified()) { console.log(skipMessage); return; } - await Nostr.maybeFetchNip05(reportRequest); await Slack.postManualVerification(reportRequest); return; } diff --git a/src/lib/duplicationHandling.js b/src/lib/duplicationHandling.js index 3c67fed..0df8c8f 100644 --- a/src/lib/duplicationHandling.js +++ b/src/lib/duplicationHandling.js @@ -1,28 +1,45 @@ -import { Datastore } from '@google-cloud/datastore'; +import { Datastore } from "@google-cloud/datastore"; +import Nostr from "./nostr.js"; + const datastore = new Datastore(); // If an event is already processed, we don't want to process it again. We use a Datastore entity to keep track of which events have already been processed. export default class DuplicationHandling { - static async processIfNotDuplicate(event, processingFunction) { + static async processIfNotDuplicate( + wantsManualVerification, + event, + processingFunction + ) { const isEventAlreadyProcessed = await this.isEventAlreadyProcessed(event); + let onlySlack = false; if (isEventAlreadyProcessed) { - console.log(`Event ${event.id} already processed. Skipping`); - return; + if (!wantsManualVerification) { + console.log(`Event ${event.id} already processed. Skipping`); + return; + } + + if (await Nostr.isAlreadyFlagged(event.id)) { + console.log(`Event ${event.id} already flagged. Skipping`); + return; + } + + // Event was already seen and not flagged but now the user requests manual verification + onlySlack = true; } - await processingFunction(event); + await processingFunction(event, onlySlack); await this.markEventAsProcessed(event); } static async isEventAlreadyProcessed(event) { - const key = datastore.key(['moderatedNostrEvents', event?.id]); + const key = datastore.key(["moderatedNostrEvents", event?.id]); const [entity] = await datastore.get(key); return !!entity; } static async markEventAsProcessed(event) { - const key = datastore.key(['moderatedNostrEvents', event?.id]); + const key = datastore.key(["moderatedNostrEvents", event?.id]); const data = { key: key, data: { seen: true }, diff --git a/src/lib/nostr.js b/src/lib/nostr.js index 0308c1c..7f2c72a 100644 --- a/src/lib/nostr.js +++ b/src/lib/nostr.js @@ -31,6 +31,7 @@ export const REPORT_KIND = 1984; export default class Nostr { static async updateNjump(reportRequest, hexpubkey, fieldToUpdate) { + await connectedPromise; const user = ndk.getUser({ hexpubkey }); const profile = await user.fetchProfile(); if (profile?.nip05) { @@ -61,12 +62,12 @@ export default class Nostr { const user = await userPromise; let moderationEvent; - moderationEvent = await Nostr.createReportEvent( + moderationEvent = await this.createReportEvent( moderatedNostrEvent, moderation ); - await Nostr.publishNostrEvent(moderationEvent); + await this.publishNostrEvent(moderationEvent); console.log( `Published moderation event ${moderationEvent.id} for event ${moderatedNostrEvent.id}` @@ -135,10 +136,28 @@ export default class Nostr { static async getReportedNostrEvent(reportNostrEvent) { const reportedNostrEventId = reportNostrEvent.tagValue("e"); - const reportedNostrEvent = await ndk.fetchEvent({ - ids: [reportedNostrEventId], + return await this.getEvent(reportedNostrEventId); + } + + static async getEvent(id) { + await connectedPromise; + const event = await ndk.fetchEvent({ + ids: [id], }); - return reportedNostrEvent; + + return event; + } + + static async isAlreadyFlagged(id) { + await connectedPromise; + const user = await userPromise; + const event = await ndk.fetchEvent({ + "#e": [id], + kinds: [REPORT_KIND], + authors: [user.pubkey], + }); + + return !!event; } static inferReportType(moderation) { diff --git a/src/lib/slack.js b/src/lib/slack.js index a4a18e1..4115716 100644 --- a/src/lib/slack.js +++ b/src/lib/slack.js @@ -16,6 +16,8 @@ export default class Slack { // Check https://app.slack.com/block-kit-builder static async postManualVerification(reportRequest) { try { + await Nostr.maybeFetchNip05(reportRequest); + const messagePayload = this.createSlackMessagePayload(reportRequest); await web.chat.postMessage(messagePayload); diff --git a/test/moderationFunction.test.js b/test/moderationFunction.test.js index 69c4ece..acf7390 100644 --- a/test/moderationFunction.test.js +++ b/test/moderationFunction.test.js @@ -7,6 +7,7 @@ import OpenAI from "openai"; import Nostr from "../src/lib/nostr.js"; import Slack from "../src/lib/slack.js"; import { Datastore } from "@google-cloud/datastore"; +import DuplicationHandling from "../src/lib/duplicationHandling.js"; import "../index.js"; @@ -59,6 +60,7 @@ describe("Moderation Cloud Function", async () => { sinon.spy(console, "error"); sinon.spy(console, "log"); sinon.stub(Nostr, "publishNostrEvent").returns(Promise.resolve()); + sinon.stub(Nostr, "updateNjump").returns(Promise.resolve()); sinon.stub(Slack, "postManualVerification").returns(Promise.resolve()); sinon.stub(Datastore.prototype, "get").resolves([]); sinon.stub(Datastore.prototype, "save").resolves(); @@ -176,6 +178,65 @@ describe("Moderation Cloud Function", async () => { sinon.assert.called(Slack.postManualVerification); }); + it("should send to Slack a valid event that is not flagged that was already processed sent from the reportinator server", async () => { + sinon.stub(DuplicationHandling, "isEventAlreadyProcessed").resolves(true); + sinon.stub(Nostr, "isAlreadyFlagged").resolves(false); + sinon.stub(OpenAI.Moderations.prototype, "create").resolves({ + results: [ + { + flagged: false, + categories: { + sexual: false, + hate: false, + harassment: false, + "self-harm": false, + "sexual/minors": false, + "hate/threatening": false, + "violence/graphic": false, + "self-harm/intent": false, + "self-harm/instructions": false, + "harassment/threatening": false, + violence: false, + }, + category_scores: { + sexual: 0.0008905100985430181, + hate: 0.0, + harassment: 0.0, + "self-harm": 0.000020246614440111443, + "sexual/minors": 0.000046280372771434486, + "hate/threatening": 0.000006213878805283457, + "violence/graphic": 0.000014815827853453811, + "self-harm/intent": 0.00004021823042421602, + "self-harm/instructions": 0.000009193716323352419, + "harassment/threatening": 0.0007776615675538778, + violence: 0.00004086320041096769, + }, + }, + ], + }); + sinon.stub(Nostr, "publishModeration"); + sinon.stub(Nostr, "maybeFetchNip05"); + const cloudEvent = { + data: { + message: { + data: Buffer.from( + JSON.stringify({ + reportedEvent: nostrEvent, + reporterPubkey: + "npub1a8ekuuuwdsrnq68s0vv9rdqxletn2j2s0hwrctqq0wggac3mh4fqth5p88", + }) + ).toString("base64"), + }, + }, + }; + + const nostrEventsPubSub = getFunction("nostrEventsPubSub"); + await nostrEventsPubSub(cloudEvent); + + sinon.assert.notCalled(Nostr.publishNostrEvent); + sinon.assert.called(Slack.postManualVerification); + }); + it("should publish a report event for a valid event that is flagged coming from reportinator server", async () => { const cloudEvent = { data: {