Skip to content

Commit

Permalink
Don't skip seen and unflagged for Slack
Browse files Browse the repository at this point in the history
  • Loading branch information
dcadenas committed May 2, 2024
1 parent dd27d49 commit bf005c2
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 15 deletions.
11 changes: 8 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`;

Expand All @@ -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;
}
Expand Down
31 changes: 24 additions & 7 deletions src/lib/duplicationHandling.js
Original file line number Diff line number Diff line change
@@ -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 },
Expand Down
29 changes: 24 additions & 5 deletions src/lib/nostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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}`
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
61 changes: 61 additions & 0 deletions test/moderationFunction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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: {
Expand Down

0 comments on commit bf005c2

Please sign in to comment.