/[sudobot]/branches/7.x/src/services/CommandManager.ts
ViewVC logotype

Contents of /branches/7.x/src/services/CommandManager.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: 9523 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 { GlobalUserBan } from "@prisma/client";
21 import { ChatInputCommandInteraction, Collection, ContextMenuCommandInteraction, Message, Snowflake, User } from "discord.js";
22 import { CommandMessage } from "../core/Command";
23 import Service from "../core/Service";
24 import { log, logError, logWarn } from "../utils/logger";
25 import { GuildConfig } from "./ConfigManager";
26
27 export const name = "commandManager";
28
29 export interface CommandContext {
30 isLegacy: boolean;
31 config: GuildConfig;
32 }
33
34 export interface LegacyCommandContext extends CommandContext {
35 isLegacy: true;
36 isContextMenu: false;
37 argv: string[];
38 args: string[];
39 parsedArgs: any[];
40 parsedNamedArgs: Record<string, any>;
41 prefix: string;
42 has(arg: string): boolean;
43 }
44
45 export interface ChatInputCommandContext extends CommandContext {
46 isLegacy: false;
47 isContextMenu: false;
48 options: ChatInputCommandInteraction["options"];
49 commandName: string;
50 }
51
52 export interface ContextMenuCommandContext extends CommandContext {
53 isLegacy: false;
54 isContextMenu: true;
55 options: ContextMenuCommandInteraction["options"];
56 commandName: string;
57 }
58
59 export default class CommandManager extends Service {
60 protected readonly userBans = new Collection<Snowflake, GlobalUserBan>();
61
62 async boot() {
63 const bans = await this.client.prisma.globalUserBan.findMany();
64
65 for (const ban of bans) {
66 this.userBans.set(ban.userId, ban);
67 }
68 }
69
70 getBan(userId: Snowflake) {
71 return this.userBans.get(userId);
72 }
73
74 isBanned(userId: Snowflake) {
75 return this.userBans.has(userId);
76 }
77
78 async addBan(userId: Snowflake, executorId: Snowflake, reason: string | null = null) {
79 if (this.isBanned(userId)) {
80 throw new Error("This user is already banned");
81 }
82
83 const ban = await this.client.prisma.globalUserBan.create({
84 data: {
85 userId,
86 reason,
87 executorId
88 }
89 });
90
91 this.userBans.set(ban.userId, ban);
92 return ban;
93 }
94
95 async removeBan(userId: Snowflake) {
96 const ban = this.getBan(userId);
97
98 if (!ban) {
99 return null;
100 }
101
102 const info = await this.client.prisma.globalUserBan.delete({
103 where: {
104 id: ban.id
105 }
106 });
107
108 this.userBans.delete(ban.userId);
109 return info;
110 }
111
112 async notifyBannedUser(user: User) {
113 const ban = this.getBan(user.id);
114
115 if (ban) {
116 const newBan = await this.client.prisma.globalUserBan.update({
117 where: {
118 id: ban.id
119 },
120 data: {
121 notified: true
122 }
123 });
124
125 this.userBans.set(ban.userId, newBan);
126
127 await user
128 .send({
129 embeds: [
130 {
131 author: {
132 icon_url: this.client.user?.displayAvatarURL(),
133 name: "You have been banned from using SudoBot"
134 },
135 description: `You won't be able to use SudoBot anymore, and your SudoBot account will be terminated. Please try not to violate the SudoBot [Terms of Service](https://docs.sudobot.org/legal/terms) before we take action on your account.`,
136 fields: [
137 {
138 name: "Reason",
139 value: ban.reason ?? "No reason provided"
140 }
141 ],
142 color: 0xf14a60,
143 timestamp: new Date().toISOString()
144 }
145 ]
146 })
147 .catch(logError);
148
149 return newBan;
150 }
151 }
152
153 public async runCommandFromMessage(message: Message, checkOnly = false, wait: boolean = false) {
154 if (!message.content) return;
155
156 const config = this.client.configManager.config[message.guildId!];
157
158 if (!config) {
159 logWarn("This guild is not configured: ", message.guildId!);
160 return;
161 }
162
163 const prefixes = [config.prefix];
164 let foundPrefix: string | undefined = undefined;
165
166 if (this.client.configManager.systemConfig.commands.mention_prefix && config.commands.mention_prefix) {
167 prefixes.push(`<@${this.client.user!.id}>`, `<@!${this.client.user!.id}>`);
168 }
169
170 for (const prefix of prefixes) {
171 if (message.content.startsWith(prefix)) {
172 foundPrefix = prefix;
173 break;
174 }
175 }
176
177 if (!foundPrefix) {
178 return;
179 }
180
181 const commandText = message.content.substring(foundPrefix.length).trimStart();
182 const [commandName, ...commandArguments] = commandText.split(/ +/);
183
184 const command = this.client.commands.get(commandName);
185
186 if (!command) {
187 log("Command not found, trying to find a snippet");
188 return await this.client.snippetManager.onMessageCreate(message, commandName);
189 }
190
191 if (!command.supportsLegacy) {
192 log("This command does not support legacy mode");
193 return;
194 }
195
196 const context = {
197 isLegacy: true,
198 argv: [commandName, ...commandArguments],
199 args: commandArguments,
200 config,
201 parsedArgs: [],
202 parsedNamedArgs: {},
203 isContextMenu: false,
204 prefix: foundPrefix,
205 has(arg: string) {
206 return this.args.includes(arg);
207 }
208 } satisfies LegacyCommandContext;
209
210 const handlerObject = {
211 _stopped: false,
212 stopCommandExecution() {
213 this._stopped = true;
214 }
215 };
216
217 await this.client.emitWaitLocal("command", command.name, handlerObject, command, message, context);
218 await Promise.resolve();
219
220 if (handlerObject._stopped) {
221 return;
222 }
223
224 return new Promise<boolean | null>((resolve, reject) => {
225 command
226 .run({
227 context,
228 checkOnly,
229 message,
230 onAbort: wait ? () => resolve(null) : undefined
231 })
232 .then(result => {
233 if (result && typeof result === "object" && "__reply" in result && result.__reply === true) {
234 message.reply(result as any).catch(console.error);
235 }
236 if (wait) {
237 resolve(true);
238 }
239 })
240 .catch(e => {
241 logError(e);
242 reject(e);
243 });
244
245 if (!wait) {
246 resolve(true);
247 }
248 });
249 }
250
251 public async runCommandFromCommandInteraction(interaction: Exclude<CommandMessage, Message>, checkOnly = false) {
252 const config = this.client.configManager.config[interaction.guildId!];
253
254 if (!config) {
255 logWarn("This guild is not configured: ", interaction.guildId!);
256 return;
257 }
258
259 const { commandName } = interaction;
260 const command = this.client.commands.get(commandName);
261
262 if (!command) {
263 return false;
264 }
265
266 if (!command.supportsInteractions) {
267 log("This command does not support application command mode");
268 return;
269 }
270
271 const context = {
272 isLegacy: false,
273 config,
274 options: interaction.options,
275 isContextMenu: interaction.isContextMenuCommand(),
276 commandName
277 } as ContextMenuCommandContext | ChatInputCommandContext;
278
279 const handlerObject = {
280 _stopped: false,
281 stopCommandExecution() {
282 this._stopped = true;
283 }
284 };
285
286 await this.client.emitWait("command", command.name, handlerObject, command, interaction, context);
287 await Promise.resolve();
288
289 if (handlerObject._stopped) {
290 return;
291 }
292
293 command
294 .run({
295 message: interaction,
296 context,
297 checkOnly
298 })
299 .then(result => {
300 if (result && typeof result === "object" && "__reply" in result && result.__reply === true) {
301 interaction.reply(result as any).catch(console.error);
302 }
303 })
304 .catch(logError);
305 }
306 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26