1 diff --git a/src/protections/NsfwProtection.ts b/src/protections/NsfwProtection.ts
2 deleted file mode 100644
4 --- a/src/protections/NsfwProtection.ts
8 -Copyright 2024 The Matrix.org Foundation C.I.C.
10 -Licensed under the Apache License, Version 2.0 (the "License");
11 -you may not use this file except in compliance with the License.
12 -You may obtain a copy of the License at
14 - http://www.apache.org/licenses/LICENSE-2.0
16 -Unless required by applicable law or agreed to in writing, software
17 -distributed under the License is distributed on an "AS IS" BASIS,
18 -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 -See the License for the specific language governing permissions and
20 -limitations under the License.
23 -import { Protection } from "./IProtection";
24 -import { Mjolnir } from "../Mjolnir";
25 -import * as nsfw from 'nsfwjs';
26 -import {LogLevel} from "@vector-im/matrix-bot-sdk";
27 -import { node } from '@tensorflow/tfjs-node';
30 -export class NsfwProtection extends Protection {
39 - async initialize() {
40 - this.model = await nsfw.load();
43 - public get name(): string {
44 - return 'NsfwProtection';
47 - public get description(): string {
48 - return "Scans all images sent into a protected room to determine if the image is " +
49 - "NSFW. If it is, the image will automatically be redacted.";
52 - public async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<any> {
53 - if (event['type'] === 'm.room.message') {
54 - let content = JSON.stringify(event['content']);
55 - if (!content.toLowerCase().includes("mxc")) {
58 - // try and grab a human-readable alias for more helpful management room output
59 - const maybeAlias = await mjolnir.client.getPublishedAlias(roomId)
60 - const room = maybeAlias ? maybeAlias : roomId
62 - const mxcs = content.match(/(mxc?:\/\/[^\s'"]+)/gim);
64 - //something's gone wrong with the regex
65 - await mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "NSFWProtection", `Unable to find any mxcs in ${event["event_id"]} in ${room}`);
69 - // @ts-ignore - see null check immediately above
70 - for (const mxc of mxcs) {
71 - const image = await mjolnir.client.downloadContent(mxc);
72 - const decodedImage = await node.decodeImage(image.data, 3);
73 - const predictions = await this.model.classify(decodedImage);
76 - for (const prediction of predictions) {
77 - if (["Hentai", "Porn"].includes(prediction["className"])) {
78 - if (prediction["probability"] > mjolnir.config.nsfwSensitivity) {
80 - await mjolnir.client.redactEvent(roomId, event["event_id"]);
82 - await mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "NSFWProtection", `There was an error redacting ${event["event_id"]} in ${room}: ${err}`);
84 - let eventId = event["event_id"]
85 - let body = `Redacted an image in ${room} ${eventId}`
86 - let formatted_body = `<details>
87 - <summary>Redacted an image in ${room}</summary>
88 - <pre>${eventId}</pre> <pre></pre>${room}</pre>
91 - msgtype: "m.notice",
93 - format: "org.matrix.custom.html",
94 - formatted_body: formatted_body
96 - await mjolnir.client.sendMessage(mjolnir.managementRoomId, msg);
101 - decodedImage.dispose();
106 \ No newline at end of file
107 diff --git a/src/protections/ProtectionManager.ts b/src/protections/ProtectionManager.ts
108 index 9b84318..67f10dc 100644
109 --- a/src/protections/ProtectionManager.ts
110 +++ b/src/protections/ProtectionManager.ts
111 @@ -31,7 +31,6 @@ import { htmlEscape } from "../utils";
112 import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
113 import { RoomUpdateError } from "../models/RoomUpdateError";
114 import { LocalAbuseReports } from "./LocalAbuseReports";
115 -import {NsfwProtection} from "./NsfwProtection";
116 import { MentionSpam } from "./MentionSpam";
118 const PROTECTIONS: Protection[] = [
119 @@ -44,7 +43,6 @@ const PROTECTIONS: Protection[] = [
120 new DetectFederationLag(),
121 new JoinWaveShortCircuit(),
122 new LocalAbuseReports(),
123 - new NsfwProtection(),
127 @@ -104,9 +102,6 @@ export class ProtectionManager {
128 protection.settings[key].setValue(value);
130 if (protection.enabled) {
131 - if (protection.name === "NsfwProtection") {
132 - (protection as NsfwProtection).initialize();
134 for (let roomId of this.mjolnir.protectedRoomsTracker.getProtectedRooms()) {
135 await protection.startProtectingRoom(this.mjolnir, roomId);
137 diff --git a/test/integration/nsfwProtectionTest.ts b/test/integration/nsfwProtectionTest.ts
138 deleted file mode 100644
139 index c86fd38..0000000
140 --- a/test/integration/nsfwProtectionTest.ts
143 -import {newTestUser} from "./clientHelper";
145 -import {MatrixClient} from "@vector-im/matrix-bot-sdk";
146 -import {getFirstReaction} from "./commands/commandUtils";
147 -import {strict as assert} from "assert";
148 -import { readFileSync } from 'fs';
150 -describe("Test: NSFW protection", function () {
151 - let client: MatrixClient;
153 - this.beforeEach(async function () {
154 - client = await newTestUser(this.config.homeserverUrl, {name: {contains: "nsfw-protection"}});
155 - await client.start();
156 - const mjolnirId = await this.mjolnir.client.getUserId();
157 - room = await client.createRoom({ invite: [mjolnirId] });
158 - await client.joinRoom(room);
159 - await client.joinRoom(this.config.managementRoom);
160 - await client.setUserPowerLevel(mjolnirId, room, 100);
162 - this.afterEach(async function () {
163 - await client.stop();
166 - function delay(ms: number) {
167 - return new Promise(resolve => setTimeout(resolve, ms));
171 - it("Nsfw protection doesn't redact sfw images", async function() {
172 - this.timeout(20000);
174 - await client.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir rooms add ${room}` });
175 - await getFirstReaction(client, this.mjolnir.managementRoomId, '✅', async () => {
176 - return await client.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir enable NsfwProtection` });
179 - const data = readFileSync('test_tree.jpg');
180 - const mxc = await client.uploadContent(data, 'image/png');
181 - let content = {"msgtype": "m.image", "body": "test.jpeg", "url": mxc};
182 - let imageMessage = await client.sendMessage(room, content);
185 - let processedImage = await client.getEvent(room, imageMessage);
186 - assert.equal(Object.keys(processedImage.content).length, 3, "This event should not have been redacted");
189 - it("Nsfw protection redacts nsfw images", async function() {
190 - this.timeout(20000);
191 - // dial the sensitivity on the protection way up so that all images are flagged as NSFW
192 - this.mjolnir.config.nsfwSensitivity = 0.0;
194 - await client.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir rooms add ${room}` });
195 - await getFirstReaction(client, this.mjolnir.managementRoomId, '✅', async () => {
196 - return await client.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir enable NsfwProtection` });
199 - const data = readFileSync('test_tree.jpg');
200 - const mxc = await client.uploadContent(data, 'image/png');
201 - let content = {"msgtype": "m.image", "body": "test.jpeg", "url": mxc};
202 - let imageMessage = await client.sendMessage(room, content);
204 - let formatted_body = `<img src=${mxc} />`
205 - let htmlContent = {
206 - msgtype: "m.image",
207 - body: formatted_body,
208 - format: "org.matrix.custom.html",
209 - formatted_body: formatted_body
211 - let htmlMessage = await client.sendMessage(room, htmlContent)
214 - let processedImage = await client.getEvent(room, imageMessage);
215 - assert.equal(Object.keys(processedImage.content).length, 0, "This event should have been redacted");
217 - let processedHtml = await client.getEvent(room, htmlMessage)
218 - assert.equal(Object.keys(processedHtml.content).length, 0, "This html image event should have been redacted")
221 \ No newline at end of file