/[sudobot]/branches/7.x/src/commands/moderation/SnipeCommand.ts
ViewVC logotype

Contents of /branches/7.x/src/commands/moderation/SnipeCommand.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: 7539 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 { formatDistanceToNowStrict } from "date-fns";
21 import { EmbedBuilder, Message, PartialMessage, PermissionsBitField, Snowflake } from "discord.js";
22 import Command, { AnyCommandContext, ArgumentType, CommandMessage, CommandReturn, ValidationRule } from "../../core/Command";
23 import { GatewayEventListener } from "../../decorators/GatewayEventListener";
24 import { HasEventListeners } from "../../types/HasEventListeners";
25 import { isTextableChannel } from "../../utils/utils";
26
27 interface MessageInfo<T extends boolean = false> extends Message<T> {
28 deletedAt?: Date;
29 }
30
31 export default class SnipeCommand extends Command implements HasEventListeners {
32 public readonly name = "snipe";
33 public readonly validationRules: ValidationRule[] = [
34 {
35 types: [ArgumentType.Integer],
36 optional: true,
37 errors: {
38 "type:invalid": "You must provide a valid message index/number between 1-10!",
39 "number:range": "You must provide a valid message index/number between 1-10!"
40 },
41 number: {
42 max: 11,
43 min: 1
44 },
45 default: null,
46 name: "index"
47 }
48 ];
49 public readonly permissions = [PermissionsBitField.Flags.ManageMessages];
50 public readonly argumentSyntaxes = ["[index=1]"];
51 public readonly aliases = ["clearsnipe", "cs", "delsnipe", "csnipe", "s", "ces", "ceditsnipe", "es", "editsnipe", "esnipe"];
52 public readonly since: string = "4.4.0";
53 protected readonly lastDeletedMessages = new Map<Snowflake, Array<MessageInfo<boolean>>>();
54 protected readonly lastEditedMessages = new Map<Snowflake, Array<[MessageInfo<boolean>, MessageInfo<boolean>]>>();
55
56 public readonly description = "Reposts the last deleted/edited message.";
57
58 @GatewayEventListener("messageDelete")
59 onMessageDelete(message: Message<boolean> | PartialMessage) {
60 if (message.author?.bot || !message.content || !isTextableChannel(message.channel)) {
61 return;
62 }
63
64 const deletedMessages = this.lastDeletedMessages.get(message.guildId!);
65
66 if (deletedMessages === undefined) {
67 this.lastDeletedMessages.set(message.guildId!, [{ ...message, deletedAt: new Date() } as MessageInfo]);
68 } else {
69 if (deletedMessages.length > 10) deletedMessages.pop();
70 deletedMessages.unshift({ ...message, deletedAt: new Date() } as MessageInfo);
71 }
72 }
73
74 @GatewayEventListener("messageUpdate")
75 onMessageUpdate(oldMessage: Message<boolean> | PartialMessage, newMessage: Message<boolean> | PartialMessage) {
76 if (
77 newMessage.author?.bot ||
78 !newMessage.content ||
79 !isTextableChannel(newMessage.channel) ||
80 oldMessage.content === newMessage.content
81 ) {
82 return;
83 }
84
85 const editedMessages = this.lastEditedMessages.get(newMessage.guildId!);
86
87 if (editedMessages === undefined) {
88 this.lastEditedMessages.set(newMessage.guildId!, [[oldMessage as MessageInfo, newMessage as MessageInfo]]);
89 } else {
90 if (editedMessages.length > 10) editedMessages.pop();
91 editedMessages.unshift([oldMessage as MessageInfo, newMessage as MessageInfo]);
92 }
93 }
94
95 async execute(message: CommandMessage, context: AnyCommandContext): Promise<CommandReturn> {
96 const index = context.isLegacy ? (context.parsedNamedArgs.index ?? 1) - 1 : 0;
97 const editSnipe = context.isLegacy && ["es", "editsnipe", "esnipe", "ces", "ceditsnipe"].includes(context.argv[0]);
98 const messages = (editSnipe ? this.lastEditedMessages : this.lastDeletedMessages).get(message.guildId!);
99 const lastMessage = editSnipe
100 ? (messages?.[index] as [MessageInfo, MessageInfo])?.[1]
101 : (messages?.[index] as MessageInfo);
102
103 if (messages?.length && index >= messages?.length) {
104 await this.error(
105 message,
106 `Invalid message index - only ${messages.length} ${editSnipe ? "edited" : "deleted"} message${
107 messages.length === 1 ? " is" : "s are"
108 } have been recorded so far.`
109 );
110 return;
111 }
112
113 if (!lastMessage) {
114 await this.error(message, `No ${editSnipe ? "edited" : "deleted"} message was recorded yet.`);
115 return;
116 }
117
118 if (context.isLegacy && ["clearsnipe", "cs", "delsnipe", "csnipe", "ces", "ceditsnipe"].includes(context.argv[0])) {
119 const hasValue = context.args[0] !== undefined;
120
121 if (!hasValue) {
122 messages?.splice(0, messages.length);
123 await this.success(message, "Cleared sniped messages for this server.");
124 } else {
125 messages?.splice(index, 1);
126 await this.success(message, "Removed the given sniped message for this server.");
127 }
128
129 return;
130 }
131
132 const date = editSnipe ? (messages?.[index] as [Message, Message])[1].editedAt! : lastMessage.deletedAt;
133
134 return {
135 __reply: true,
136 embeds: [
137 new EmbedBuilder({
138 author: {
139 name: lastMessage.author?.username ?? "Unknown",
140 iconURL: lastMessage.author?.displayAvatarURL()
141 },
142 color: Math.floor(Math.random() * 0xffffff),
143 footer: {
144 text: `Sniped${
145 date
146 ? ` • ${editSnipe ? "Edited" : "Deleted"} ${formatDistanceToNowStrict(date, { addSuffix: true })}`
147 : ""
148 } • ${messages?.length ?? 0} ${editSnipe ? "edited" : "deleted"} message${
149 messages?.length === 1 ? "" : "s"
150 } total`
151 },
152 ...(editSnipe
153 ? {
154 fields: [
155 {
156 name: "Before",
157 value: (messages?.[index] as [Message, Message])[0].content || "*No content*"
158 },
159 {
160 name: "After",
161 value: (messages?.[index] as [Message, Message])[1].content || "*No content*"
162 }
163 ]
164 }
165 : {
166 description: lastMessage.content!
167 })
168 })
169 ]
170 };
171 }
172 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26