Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/commands/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export async function handleCommand(roomId: string, event: { content: { body: st
"!mjolnir msc4284_set <room alias/ID> unset - Unset/disable the policy server in a specific room. Same constraints as the room ID/alias set command.\n" +
"!mjolnir msc4284_set * <policy server name> - Set the policy server for all Mjolnir-protected rooms.\n" +
"!mjolnir msc4284_set * unset - Unset/disable the policy server for all Mjolnir-protected rooms\n" +
"!mjolnir synapse_ext policy_server_spammy <true|false> - Enable/disable receiving events flagged as spammy by policy servers. Requires compatible server.\n" +
""; // empty segment to reduce diff noise

const botMenu =
Expand Down
45 changes: 45 additions & 0 deletions src/commands/SynapseExtensionsCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright 2025 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { Mjolnir } from "../Mjolnir";

type SynapseAdminConfig = {
return_soft_failed_events?: boolean;
return_policy_server_spammy_events?: boolean;
};

const SYNAPSE_ADMIN_ACCOUNT_DATA_TYPE = "io.element.synapse.admin_client_config";

// !mjolnir synapse_ext <'policy_server_spammy'> <true|false>
export async function execSynapseExtensionsCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const extension = parts[2];

if (extension === "policy_server_spammy") {
let currentConfig: SynapseAdminConfig = {};
try {
currentConfig = await mjolnir.client.getAccountData(SYNAPSE_ADMIN_ACCOUNT_DATA_TYPE);
} catch (e) {
// assume unset
}
await mjolnir.client.setAccountData(SYNAPSE_ADMIN_ACCOUNT_DATA_TYPE, {
return_soft_failed_events: currentConfig.return_soft_failed_events ?? false,
return_policy_server_spammy_events: parts[3] === "true",
});
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event["event_id"], "✅");
} else {
await mjolnir.client.replyNotice(roomId, event, "Unknown extension, please check the help for more information.");
}
}
2 changes: 2 additions & 0 deletions src/protections/ProtectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { NsfwProtection } from "./NsfwProtection";
import { MentionSpam } from "./MentionSpam";
import { MessageIsVideo } from "./MessageIsVideo";
import { FirstMessageIsLink } from "./FirstMessageIsLink";
import {RedactPolicyServerFlaggedEvents} from "./RedactPolicyServerFlaggedEvents";

const PROTECTIONS: Protection[] = [
new FirstMessageIsImage(),
Expand All @@ -50,6 +51,7 @@ const PROTECTIONS: Protection[] = [
new MentionSpam(),
new MessageIsVideo(),
new FirstMessageIsLink(),
new RedactPolicyServerFlaggedEvents(),
];

const ENABLED_PROTECTIONS_EVENT_TYPE = "org.matrix.mjolnir.enabled_protections";
Expand Down
37 changes: 37 additions & 0 deletions src/protections/RedactPolicyServerFlaggedEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2025 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";

export class RedactPolicyServerFlaggedEvents extends Protection {
settings = {};

public get name(): string {
return "RedactPolicyServerFlaggedEvents";
}

public get description(): string {
return "Redacts events that are flagged by the policy server as probable spam. Requires enabling the policy_server_spammy Synapse extension (see help command for info).";
}

public async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<any> {
if (event["unsigned"]?.["io.element.synapse.policy_server_spammy"] === true) {
console.log(`Redacting ${event["event_id"]} in ${roomId} due to policy server flagging it as spam.`);
await mjolnir.client.redactEvent(roomId, event["event_id"], "Probable spam");
}
}
}
Loading