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

Annotation of /branches/6.x/src/automod/Antispam.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (hide annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 8743 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 rakinar2 577 /**
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, Snowflake, 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     interface SimilarMessageSpamInfo {
32     content?: string;
33     count: number;
34     timeout?: NodeJS.Timeout;
35     }
36    
37     export const name = "antispam";
38    
39     export default class Antispam extends Service {
40     protected readonly map: Record<`${Snowflake}_${Snowflake}`, SpamUserInfo | undefined> = {};
41     protected readonly similarMessageSpamMap: Record<`${Snowflake}_${Snowflake}`, SimilarMessageSpamInfo | undefined> = {};
42    
43     async muteUser(message: Message, antispam: GuildConfig["antispam"]) {
44     this.client.infractionManager
45     .createMemberMute(message.member as GuildMember, {
46     guild: message.guild!,
47     moderator: this.client.user!,
48     bulkDeleteReason: "The system has detected spam messages from this user",
49     duration: antispam?.mute_duration && antispam?.mute_duration > 0 ? antispam?.mute_duration : 1000 * 60 * 60,
50     messageChannel:
51     antispam?.action === "mute_clear" || antispam?.action === "auto"
52     ? (message.channel! as TextChannel)
53     : undefined,
54     notifyUser: true,
55     reason: "Spam detected",
56     sendLog: true,
57     autoRemoveQueue: true
58     })
59     .catch(logError);
60     }
61    
62     async warnUser(message: Message, antispam: GuildConfig["antispam"]) {
63     this.client.infractionManager
64     .createMemberWarn(message.member as GuildMember, {
65     guild: message.guild!,
66     moderator: this.client.user!,
67     notifyUser: true,
68     reason: `Spam detected.${
69     antispam?.action === "auto" ? " If you continue to send spam messages, you might get muted." : ""
70     }`,
71     sendLog: true
72     })
73     .catch(logError);
74     }
75    
76     async verballyWarnUser(message: Message) {
77     await message.channel
78     .send({
79     content: `Hey ${message.author.toString()}, don't spam here!`
80     })
81     .catch(logError);
82     }
83    
84     async takeAction(message: Message) {
85     log("Triggered");
86    
87     const config = this.client.configManager.config[message.guildId!];
88    
89     if (!config) return;
90    
91     const { antispam } = config;
92    
93     if (antispam?.action === "mute_clear" || antispam?.action === "mute") {
94     await this.muteUser(message, antispam);
95     } else if (antispam?.action === "warn") {
96     await this.warnUser(message, antispam);
97     } else if (antispam?.action === "verbal_warn") {
98     await this.verballyWarnUser(message);
99     } else if (antispam?.action === "auto") {
100     let record = await this.client.prisma.spamRecord.findFirst({
101     where: {
102     guild_id: message.guildId!,
103     user_id: message.author.id
104     }
105     });
106    
107     if (!record) {
108     record = await this.client.prisma.spamRecord.create({
109     data: {
110     guild_id: message.guildId!,
111     user_id: message.author.id,
112     level: 1
113     }
114     });
115     } else {
116     await this.client.prisma.spamRecord.update({
117     data: {
118     level: {
119     increment: 1
120     }
121     },
122     where: {
123     id: record.id
124     }
125     });
126     }
127    
128     if (record.level === 1) {
129     await this.verballyWarnUser(message);
130     } else if (record.level === 2) {
131     await this.warnUser(message, antispam);
132     } else {
133     await this.muteUser(message, antispam);
134     }
135     }
136     }
137    
138     async checkForSimilarMessages(message: Message, config: GuildConfig) {
139     if (
140     !config.antispam?.similar_messages?.max ||
141     config.antispam?.similar_messages?.max < 0 ||
142     message.content.trim() === ""
143     ) {
144     return false;
145     }
146    
147     const channels = config.antispam?.similar_messages?.channels;
148    
149     if (typeof channels === "boolean" && !channels) {
150     return false;
151     }
152    
153     if (channels !== true && !channels?.includes(message.channelId)) {
154     return false;
155     }
156    
157     const lastMessageInfo = this.similarMessageSpamMap[`${message.guildId!}_${message.author.id}`];
158    
159     if (!lastMessageInfo) {
160     this.similarMessageSpamMap[`${message.guildId!}_${message.author.id}`] = {
161     count: 0,
162     content: message.content,
163     timeout: setTimeout(() => {
164     const lastMessageInfo = this.similarMessageSpamMap[`${message.guildId!}_${message.author.id}`];
165     const max = config.antispam?.similar_messages?.max;
166    
167     if (lastMessageInfo && max && lastMessageInfo.count >= max) {
168     lastMessageInfo.count = 0;
169     this.takeAction(message).catch(console.error);
170     }
171    
172     this.similarMessageSpamMap[`${message.guildId!}_${message.author.id}`] = undefined;
173     }, config.antispam.similar_messages?.timeframe ?? config.antispam.timeframe)
174     };
175    
176     return false;
177     }
178    
179     if (message.content === lastMessageInfo.content) {
180     log("Similar message found");
181     lastMessageInfo.count++;
182     } else {
183     log("Similar message count reset");
184     lastMessageInfo.count = 0;
185     }
186    
187     this.similarMessageSpamMap[`${message.guildId!}_${message.author.id}`] = lastMessageInfo;
188     return false;
189     }
190    
191     async onMessageCreate(message: Message) {
192     if (!isTextableChannel(message.channel)) return;
193    
194     const config = this.client.configManager.config[message.guildId!];
195    
196     if (
197     !config?.antispam?.enabled ||
198     !config?.antispam.limit ||
199     !config?.antispam.timeframe ||
200     config.antispam.limit < 1 ||
201     config.antispam.timeframe < 1 ||
202     config.antispam.disabled_channels.includes(message.channelId!)
203     ) {
204     return;
205     }
206    
207     if (await isImmuneToAutoMod(this.client, message.member!, PermissionFlagsBits.ManageMessages)) {
208     return;
209     }
210    
211     const result = await this.checkForSimilarMessages(message, config);
212    
213     if (result) {
214     return;
215     }
216    
217     const info = this.map[`${message.guildId!}_${message.author.id}`] ?? ({} as SpamUserInfo);
218    
219     info.timestamps ??= [];
220     info.timestamps.push(Date.now());
221    
222     log("Pushed");
223    
224     if (!info.timeout) {
225     log("Timeout set");
226    
227     info.timeout = setTimeout(() => {
228     const delayedInfo = this.map[`${message.guildId!}_${message.author.id}`] ?? ({} as SpamUserInfo);
229     const timestamps = delayedInfo.timestamps.filter(
230     timestamp => config.antispam?.timeframe! + timestamp >= Date.now()
231     );
232    
233     if (timestamps.length >= config.antispam?.limit!) {
234     this.takeAction(message).catch(console.error);
235     }
236    
237     this.map[`${message.guildId!}_${message.author.id}`] = undefined;
238     log("Popped");
239     }, config.antispam.timeframe);
240     }
241    
242     this.map[`${message.guildId!}_${message.author.id}`] = info;
243     }
244     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26