/[sudobot]/branches/6.x/src/services/WelcomerService.ts
ViewVC logotype

Contents of /branches/6.x/src/services/WelcomerService.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: 12189 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 { Mutex } from "async-mutex";
21 import { formatDistanceToNowStrict } from "date-fns";
22 import {
23 ActionRowBuilder,
24 ButtonBuilder,
25 ButtonStyle,
26 ColorResolvable,
27 ComponentEmojiResolvable,
28 EmbedBuilder,
29 GuildMember,
30 Interaction,
31 Snowflake,
32 time
33 } from "discord.js";
34 import { readFile } from "fs/promises";
35 import JSON5 from "json5";
36 import Service from "../core/Service";
37 import { GatewayEventListener } from "../decorators/GatewayEventListener";
38 import { NotUndefined } from "../types/NotUndefined";
39 import { log, logError, logWarn } from "../utils/logger";
40 import { pick, sudoPrefix } from "../utils/utils";
41 import { GuildConfig } from "./ConfigManager";
42
43 export const name = "welcomer";
44
45 export default class WelcomerService extends Service {
46 welcomeMessages: string[] = [];
47 mutexes: Record<Snowflake, Mutex | undefined> = {};
48
49 @GatewayEventListener("ready")
50 async onReady() {
51 log("Loading welcome messages...");
52 this.welcomeMessages = JSON5.parse(await readFile(sudoPrefix(`resources/welcome_messages.json`), { encoding: "utf-8" }));
53 }
54
55 @GatewayEventListener("guildMemberAdd")
56 async onGuildMemberAdd(member: GuildMember) {
57 if (member.user.bot) return;
58
59 const config = this.client.configManager.config[member.guild.id];
60
61 if (!config) return;
62
63 const { welcomer } = config;
64
65 if (!welcomer?.enabled) return;
66
67 const {
68 channel: channelId,
69 embed,
70 say_hi_button,
71 custom_message,
72 randomize,
73 mention,
74 say_hi_expire_after,
75 delete_messages
76 } = welcomer;
77
78 if (!custom_message && !randomize) return;
79
80 try {
81 const channel = member.guild.channels.cache.get(channelId) ?? (await member.guild.channels.fetch(channelId));
82
83 if (!channel) return;
84
85 if (!channel.isTextBased()) return;
86
87 const actionRow = say_hi_button
88 ? [
89 this.generateActionRow(member.user.id, {
90 say_hi_emoji: welcomer.say_hi_emoji,
91 say_hi_label: welcomer.say_hi_label
92 })
93 ]
94 : undefined;
95
96 const reply = await channel.send({
97 content: `${mention ? member.user.toString() + "\n" : ""}${!embed ? this.generateContent(member, welcomer) : ""}`,
98 embeds: embed ? [this.generatedEmbed(member, welcomer)] : undefined,
99 components: actionRow
100 });
101
102 if (delete_messages) {
103 setTimeout(() => {
104 reply.delete().catch(logError);
105 }, delete_messages);
106 }
107
108 if (actionRow && say_hi_button && say_hi_expire_after) {
109 setTimeout(() => {
110 const row = actionRow;
111
112 row[0].components[0].setDisabled(true);
113
114 reply
115 .edit({
116 components: row
117 })
118 .catch(logError);
119 }, say_hi_expire_after);
120 }
121 } catch (e) {
122 logError(e);
123 return;
124 }
125 }
126
127 @GatewayEventListener("interactionCreate")
128 async onInteractionCreate(interaction: Interaction) {
129 if (!interaction.isButton()) return;
130
131 const config = this.client.configManager.config[interaction.guild!.id];
132
133 if (!config) return;
134
135 if (!interaction.guild?.id || !config.welcomer?.say_hi_button || !interaction.customId.startsWith(`welcomer_say_hi__`))
136 return;
137
138 this.mutexes[interaction.guildId!] ??= new Mutex();
139 const wasLocked = this.mutexes[interaction.guildId!]!.isLocked();
140
141 if (!interaction.deferred && !interaction.replied) {
142 await interaction.deferUpdate();
143 }
144
145 const release = await this.mutexes[interaction.guildId!]!.acquire();
146 const saysHiToYou = ` says hi to you!`;
147
148 if (wasLocked) {
149 try {
150 interaction.customId = (await interaction.message.fetch(true)).components[0].components[0].customId!;
151 log("Refetched custom ID: ", interaction.customId);
152 } catch (e) {
153 release();
154 return;
155 }
156 }
157
158 const [, memberId, messageId] = interaction.customId.split("__");
159 let sayHiReply = this.client.configManager.config[interaction.guildId!]?.welcomer?.say_hi_reply;
160
161 if (typeof sayHiReply === "string" && !sayHiReply?.includes(":mentions:")) {
162 logWarn("config.welcomer.say_hi_reply does not include :mentions: placeholder, defaulting to the built in message");
163
164 sayHiReply = undefined;
165 }
166
167 try {
168 if (!messageId) {
169 const reply = await interaction[interaction.replied || interaction.deferred ? "followUp" : "reply"]({
170 content:
171 sayHiReply?.replace(/:mentions:/gi, `<@${interaction.user.id}>`) ??
172 `${interaction.user.id === memberId ? "__You__" : interaction.user.toString()}${
173 interaction.user.id === memberId ? " said hi to yourself!" : saysHiToYou
174 }`,
175 fetchReply: true
176 });
177
178 const newCustomId = `welcomer_say_hi__${memberId}__${reply.id}`;
179
180 const actionRow = this.generateActionRow(memberId, {
181 say_hi_emoji: config.welcomer?.say_hi_emoji!,
182 say_hi_label: config.welcomer?.say_hi_label!
183 });
184
185 actionRow.components[0].setCustomId(newCustomId);
186
187 await interaction.message.edit({
188 components: [actionRow]
189 });
190
191 if (config.welcomer.delete_messages) {
192 const time = interaction.message.createdAt.getTime() + config.welcomer.delete_messages - Date.now();
193
194 if (time > 1000) {
195 setTimeout(() => {
196 reply.delete().catch(logError);
197 }, time);
198 }
199 }
200 } else {
201 try {
202 if (!interaction.replied && !interaction.deferred) {
203 await interaction.deferUpdate();
204 }
205
206 const message =
207 interaction.channel?.messages.cache.get(messageId) ??
208 (await interaction.channel?.messages.fetch(messageId));
209
210 if (!message) {
211 release();
212 return;
213 }
214
215 const parts = sayHiReply?.split(/:mentions:/i);
216 let contentOrUsers: string | string[] = message.content;
217 let usersArray: string[] | undefined;
218
219 if (parts !== undefined) {
220 let content = message.content;
221
222 for (const part of parts) {
223 content = content.replace(part.trim(), "");
224 }
225
226 content = content.trim();
227
228 const users = content.split(/\s*,\s*/).filter((part, index, array) => array.lastIndexOf(part) === index);
229
230 console.log("DEBUG", users);
231 contentOrUsers = [...users];
232
233 if (!users.includes(`<@${interaction.user.id}>`)) {
234 users.push(`<@${interaction.user.id}>`);
235 }
236
237 usersArray = users;
238 }
239
240 if (
241 contentOrUsers.includes(`${interaction.user.toString()}`) ||
242 (interaction.user.id === memberId && contentOrUsers.includes("__You__"))
243 ) {
244 await interaction.followUp({
245 content: `You've already said hi to ${interaction.user.id === memberId ? "yourself!" : "the user!"}`,
246 ephemeral: true
247 });
248
249 release();
250 return;
251 }
252
253 await message.edit({
254 content:
255 sayHiReply === undefined
256 ? `${message.content.replace(saysHiToYou, "").replace(" said hi to yourself!", "").trim()}, ${
257 interaction.user.id === memberId ? "__You__" : interaction.user.toString()
258 }${saysHiToYou}`
259 : sayHiReply.replace(/:mentions:/gi, usersArray!.join(", "))
260 });
261 } catch (e) {
262 logError(e);
263 }
264 }
265 } catch (e) {
266 logError(e);
267 }
268
269 release();
270 }
271
272 generateActionRow(
273 memberId: string,
274 { say_hi_emoji, say_hi_label }: Pick<NotUndefined<GuildConfig["welcomer"]>, "say_hi_emoji" | "say_hi_label">
275 ) {
276 const emoji =
277 !say_hi_emoji || say_hi_emoji === "default"
278 ? "👋"
279 : this.client.emojis.cache.find(e => e.name === say_hi_emoji || e.identifier === say_hi_emoji);
280 const button = new ButtonBuilder()
281 .setCustomId(`welcomer_say_hi__${memberId}`)
282 .setLabel(say_hi_label ?? "Say Hi!")
283 .setStyle(ButtonStyle.Secondary);
284
285 if (emoji)
286 button.setEmoji(
287 typeof emoji === "string"
288 ? emoji
289 : pick(emoji as Exclude<ComponentEmojiResolvable, string>, ["id", "name", "animated"])
290 );
291
292 return new ActionRowBuilder<ButtonBuilder>().addComponents(button);
293 }
294
295 pickRandomWelcomeMessage() {
296 return this.welcomeMessages[Math.floor(Math.random() * this.welcomeMessages.length)];
297 }
298
299 replacePlaceholders(member: GuildMember, message: string) {
300 return message
301 .replace(/:tag:/gi, member.user.tag)
302 .replace(/:discriminator:/gi, member.user.discriminator)
303 .replace(/:createdAt:/gi, `${time(member.user.createdTimestamp)}`)
304 .replace(/:age:/gi, formatDistanceToNowStrict(member.user.createdTimestamp))
305 .replace(/:mention:/gi, member.user.toString())
306 .replace(/:guild:/gi, member.guild.name);
307 }
308
309 generateContent(member: GuildMember, { custom_message, randomize, mention }: NotUndefined<GuildConfig["welcomer"]>) {
310 const message = `${randomize ? `${this.pickRandomWelcomeMessage()}\n` : ""}${custom_message ? custom_message : ""}`;
311 return this.replacePlaceholders(member, message);
312 }
313
314 generatedEmbed(member: GuildMember, welcomer: NotUndefined<GuildConfig["welcomer"]>) {
315 return new EmbedBuilder({
316 author: {
317 name: member.user.username,
318 icon_url: member.displayAvatarURL()
319 },
320 description: this.generateContent(member, welcomer),
321 footer: {
322 text: "Welcome"
323 }
324 })
325 .setColor(welcomer.color as ColorResolvable)
326 .setTimestamp();
327 }
328 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26