matrix-spam-ml

git clone git://archive.git.mtrnord.blog/MTRNord/matrix-spam-ml.git
Log | Files | Refs | Submodules | README | LICENSE

reactionHandler.ts (11519B)


      1 import { LogService, MatrixClient } from "matrix-bot-sdk";
      2 import BanListHandler from "../banlist/banlist";
      3 import { ReactionEvent, ReactionEventContent } from "../events/ReactionEvent";
      4 import { Config } from "../index";
      5 
      6 export const BAN_REACTION = "🚨 Ban User";
      7 export const KICK_REACTION = "⚠️ Kick User";
      8 export const FALSE_POSITIVE_REACTION = "✅ False positive";
      9 
     10 
     11 export function getReactionHandler<T extends ReactionEventContent>(roomId: string, event: ReactionEvent<T>, client: MatrixClient, config: Config, policyRoomHandler: BanListHandler): ReactionHandler<T> {
     12     switch (event.reaction) {
     13         case BAN_REACTION:
     14             return new BanReactionHandler(roomId, event, client, config, policyRoomHandler);
     15         case KICK_REACTION:
     16             return new KickReactionHandler(roomId, event, client, config, policyRoomHandler);
     17         case FALSE_POSITIVE_REACTION:
     18             return new FalsePositiveReactionHandler(roomId, event, client, config, policyRoomHandler);
     19         default:
     20             throw new Error("Reaction not supported");
     21     }
     22 }
     23 
     24 export abstract class ReactionHandler<T extends ReactionEventContent> {
     25     constructor(protected readonly roomId: string, protected readonly event: ReactionEvent<T>, protected readonly client: MatrixClient, protected readonly config: Config, protected readonly policyRoomHandler: BanListHandler) { }
     26 
     27     public abstract handleReaction(): Promise<void>;
     28 }
     29 
     30 
     31 class BanReactionHandler<T extends ReactionEventContent> extends ReactionHandler<T> {
     32     public async handleReaction(): Promise<void> {
     33         // Check if reaction was issued by an admin that can do this action
     34         // Get users in the admin room
     35         const users = await this.client.getJoinedRoomMembers(this.config.adminRoom);
     36 
     37         // Get power levels in the admin room
     38         // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
     39         const powerLevels = await this.client.getRoomStateEvent(this.config.adminRoom, "m.room.power_levels", "");
     40 
     41         // Map users to their power level
     42         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
     43         const admins = users.filter(u => powerLevels.content.users[u] >= powerLevels.content.ban);
     44 
     45         // Check if sender is an admin
     46         if (!admins.includes(this.event.sender)) {
     47             return;
     48         }
     49 
     50 
     51 
     52         let reactedToEvent: { content: { "space.midnightthoughts.sending_user": string; "space.midnightthoughts.previous_action": string; } } | undefined;
     53         try {
     54             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
     55             reactedToEvent = await this.client.getEvent(this.roomId, this.event.targetEventId ?? "");
     56 
     57         } catch (e) {
     58             LogService.error("index", "Could not get event", e);
     59             return;
     60         }
     61 
     62         // Making typescript happy
     63         if (reactedToEvent == undefined) {
     64             LogService.error("index", "Could not get event");
     65             return;
     66         }
     67 
     68         if (process.env["DEBUG"] == "true") {
     69             LogService.info("BanReactionHandler", `Admin selected ban for ${reactedToEvent.content["space.midnightthoughts.sending_user"]} on ${this.event.targetEventId ?? "unknown"}`);
     70             return;
     71         }
     72 
     73         // We dont need to issue this twice for the same user
     74         // TODO: check if the user is already banned
     75         if (reactedToEvent.content["space.midnightthoughts.previous_action"] !== "ban") {
     76             await this.policyRoomHandler.banUser(reactedToEvent.content["space.midnightthoughts.sending_user"], "Banned by admin");
     77 
     78             // Get the message we sent in the warning room to update it
     79             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
     80             const warningMessage = await this.client.getEvent(this.roomId, this.event.targetEventId ?? "");
     81             // Update the event to reflect the action
     82             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
     83             warningMessage.content["space.midnightthoughts.previous_action"] = "ban";
     84             // Add relates_to to the event
     85             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
     86             warningMessage.content["m.relates_to"] = {
     87                 "rel_type": "m.replace",
     88                 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
     89                 "event_id": this.event.targetEventId ?? ""
     90             };
     91             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
     92             warningMessage.content["m.new_content"] = {
     93                 "msgtype": "m.text",
     94                 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
     95                 "body": warningMessage.content.body,
     96                 "format": "org.matrix.custom.html",
     97                 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
     98                 "formatted_body": warningMessage.content.formatted_body
     99             }
    100             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    101             await this.client.sendEvent(this.roomId, "m.room.message", warningMessage.content)
    102         }
    103     }
    104 }
    105 
    106 class KickReactionHandler<T extends ReactionEventContent> extends ReactionHandler<T> {
    107     public async handleReaction(): Promise<void> {
    108         // Check if reaction was issued by an admin that can do this action
    109         // Get users in the admin room
    110         const users = await this.client.getJoinedRoomMembers(this.config.adminRoom);
    111 
    112         // Get power levels in the admin room
    113         // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    114         const powerLevels = await this.client.getRoomStateEvent(this.config.adminRoom, "m.room.power_levels", "");
    115 
    116         // Map users to their power level
    117         // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
    118         const admins = users.filter(u => powerLevels.content.users[u] >= powerLevels.content.kick);
    119 
    120         // Check if sender is an admin
    121         if (!admins.includes(this.event.sender)) {
    122             return;
    123         }
    124 
    125         let reactedToEvent: { content: { "space.midnightthoughts.sending_user": string; "space.midnightthoughts.previous_action": string; } } | undefined;
    126         try {
    127             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    128             reactedToEvent = await this.client.getEvent(this.roomId, this.event.targetEventId ?? "");
    129 
    130         } catch (e) {
    131             LogService.error("index", "Could not get event", e);
    132             return;
    133         }
    134 
    135         // Making typescript happy
    136         if (reactedToEvent == undefined) {
    137             LogService.error("index", "Could not get event");
    138             return;
    139         }
    140 
    141         if (process.env["DEBUG"]?.toLowerCase() == "true" || process.env["DEBUG"] == "1") {
    142             LogService.info("BanReactionHandler", `Admin selected kick for ${reactedToEvent.content["space.midnightthoughts.sending_user"]} on ${this.event.targetEventId ?? "unknown"}`);
    143             return;
    144         }
    145 
    146         await this.policyRoomHandler.kickUser(reactedToEvent.content["space.midnightthoughts.sending_user"], "Kicked by admin");
    147         // Get the message we sent in the warning room to update it
    148         // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    149         const warningMessage = await this.client.getEvent(this.roomId, this.event.targetEventId ?? "");
    150         // Update the event to reflect the action
    151         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    152         warningMessage.content["space.midnightthoughts.previous_action"] = "kick";
    153         // Add relates_to to the event
    154         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    155         warningMessage.content["m.relates_to"] = {
    156             "rel_type": "m.replace",
    157             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    158             "event_id": this.event.targetEventId ?? ""
    159         };
    160         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    161         warningMessage.content["m.new_content"] = {
    162             "msgtype": "m.text",
    163             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
    164             "body": warningMessage.content.body,
    165             "format": "org.matrix.custom.html",
    166             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
    167             "formatted_body": warningMessage.content.formatted_body
    168         }
    169         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    170         await this.client.sendEvent(this.roomId, "m.room.message", warningMessage.content)
    171     }
    172 }
    173 
    174 class FalsePositiveReactionHandler<T extends ReactionEventContent> extends ReactionHandler<T> {
    175     public async handleReaction(): Promise<void> {
    176         let reactedToEvent: { content: { "space.midnightthoughts.sending_user": string; "space.midnightthoughts.previous_action": string; } } | undefined;
    177         try {
    178             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    179             reactedToEvent = await this.client.getEvent(this.roomId, this.event.targetEventId ?? "");
    180 
    181         } catch (e) {
    182             LogService.error("index", "Could not get event", e);
    183             return;
    184         }
    185 
    186         // Making typescript happy
    187         if (reactedToEvent == undefined) {
    188             LogService.error("index", "Could not get event");
    189             return;
    190         }
    191 
    192         LogService.info("BanReactionHandler", `Admin selected false positive for ${reactedToEvent.content["space.midnightthoughts.sending_user"]} on ${this.event.targetEventId ?? "unknown"}`);
    193 
    194         // Get the message we sent in the warning room to update it
    195         // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    196         const warningMessage = await this.client.getEvent(this.roomId, this.event.targetEventId ?? "");
    197         // Update the event to reflect the action
    198         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    199         warningMessage.content["space.midnightthoughts.previous_action"] = "false_positive";
    200         // Add relates_to to the event
    201         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    202         warningMessage.content["m.relates_to"] = {
    203             "rel_type": "m.replace",
    204             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    205             "event_id": this.event.targetEventId ?? ""
    206         };
    207         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
    208         warningMessage.content["m.new_content"] = {
    209             "msgtype": "m.text",
    210             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
    211             "body": warningMessage.content.body,
    212             "format": "org.matrix.custom.html",
    213             // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
    214             "formatted_body": warningMessage.content.formatted_body
    215         }
    216         // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    217         await this.client.sendEvent(this.roomId, "m.room.message", warningMessage.content)
    218     }
    219 }