/[sudobot]/branches/5.x/src/automod/Antispam.ts
ViewVC logotype

Contents of /branches/5.x/src/automod/Antispam.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (show annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 6284 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 /**
2 * This file is part of SudoBot.
3 *
4 * Copyright (C) 2021-2023 OSN Developers.
5 *
6 * SudoBot is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * SudoBot is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 import { GuildMember, Message, PermissionFlagsBits, TextChannel } from "discord.js";
21 import Service from "../core/Service";
22 import { GuildConfig } from "../types/GuildConfigSchema";
23 import { log, logError } from "../utils/logger";
24 import { isImmuneToAutoMod, isTextableChannel } from "../utils/utils";
25
26 interface SpamUserInfo {
27 timestamps: number[];
28 timeout?: NodeJS.Timeout;
29 }
30
31 export const name = "antispam";
32
33 export default class Antispam extends Service {
34 protected readonly map: Record<string, Record<string, SpamUserInfo | undefined>> = {};
35
36 boot() {
37 for (const guild in this.client.configManager.config) {
38 this.map[guild] = {};
39 }
40 }
41
42 async muteUser(message: Message, antispam: GuildConfig["antispam"]) {
43 this.client.infractionManager
44 .createMemberMute(message.member as GuildMember, {
45 guild: message.guild!,
46 moderator: this.client.user!,
47 bulkDeleteReason: "The system has detected spam messages from this user",
48 duration: antispam?.mute_duration && antispam?.mute_duration > 0 ? antispam?.mute_duration : 1000 * 60 * 60,
49 messageChannel: antispam?.action === "mute_clear" || antispam?.action === "auto" ? (message.channel! as TextChannel) : undefined,
50 notifyUser: true,
51 reason: "Spam detected",
52 sendLog: true,
53 autoRemoveQueue: true
54 })
55 .catch(logError);
56 }
57
58 async warnUser(message: Message, antispam: GuildConfig["antispam"]) {
59 this.client.infractionManager
60 .createMemberWarn(message.member as GuildMember, {
61 guild: message.guild!,
62 moderator: this.client.user!,
63 notifyUser: true,
64 reason: `Spam detected.${antispam?.action === "auto" ? " If you continue to send spam messages, you might get muted." : ""}`,
65 sendLog: true
66 })
67 .catch(logError);
68 }
69
70 async verballyWarnUser(message: Message) {
71 await message.channel
72 .send({
73 content: `Hey ${message.author.toString()}, don't spam here!`
74 })
75 .catch(logError);
76 }
77
78 async takeAction(message: Message) {
79 log("Triggered");
80
81 const config = this.client.configManager.config[message.guildId!];
82
83 if (!config) return;
84
85 const { antispam } = config;
86
87 if (antispam?.action === "mute_clear" || antispam?.action === "mute") {
88 await this.muteUser(message, antispam);
89 } else if (antispam?.action === "warn") {
90 await this.warnUser(message, antispam);
91 } else if (antispam?.action === "verbal_warn") {
92 await this.verballyWarnUser(message);
93 } else if (antispam?.action === "auto") {
94 let record = await this.client.prisma.spamRecord.findFirst({
95 where: {
96 guild_id: message.guildId!,
97 user_id: message.author.id
98 }
99 });
100
101 if (!record) {
102 record = await this.client.prisma.spamRecord.create({
103 data: {
104 guild_id: message.guildId!,
105 user_id: message.author.id,
106 level: 1
107 }
108 });
109 } else {
110 await this.client.prisma.spamRecord.update({
111 data: {
112 level: {
113 increment: 1
114 }
115 },
116 where: {
117 id: record.id
118 }
119 });
120 }
121
122 if (record.level === 1) {
123 await this.verballyWarnUser(message);
124 } else if (record.level === 2) {
125 await this.warnUser(message, antispam);
126 } else {
127 await this.muteUser(message, antispam);
128 }
129 }
130 }
131
132 async onMessageCreate(message: Message) {
133 if (!isTextableChannel(message.channel)) return;
134
135 const config = this.client.configManager.config[message.guildId!];
136
137 if (
138 !config?.antispam?.enabled ||
139 !config?.antispam.limit ||
140 !config?.antispam.timeframe ||
141 config.antispam.limit < 1 ||
142 config.antispam.timeframe < 1
143 ) {
144 return;
145 }
146
147 if (isImmuneToAutoMod(this.client, message.member!, PermissionFlagsBits.ManageMessages)) {
148 return;
149 }
150
151 const info = this.map[message.guildId!][message.author.id] ?? ({} as SpamUserInfo);
152
153 info.timestamps ??= [];
154 info.timestamps.push(Date.now());
155
156 log("Pushed");
157
158 if (!info.timeout) {
159 log("Timeout set");
160
161 info.timeout = setTimeout(() => {
162 const delayedInfo = this.map[message.guildId!][message.author.id] ?? ({} as SpamUserInfo);
163 const timestamps = delayedInfo.timestamps.filter(timestamp => config.antispam?.timeframe! + timestamp >= Date.now());
164
165 if (timestamps.length >= config.antispam?.limit!) {
166 this.takeAction(message).catch(console.error);
167 }
168
169 this.map[message.guildId!][message.author.id] = undefined;
170 log("Popped");
171 }, config.antispam.timeframe);
172 }
173
174 this.map[message.guildId!][message.author.id] = info;
175 }
176 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26