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 }