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

Annotation of /branches/6.x/src/services/WelcomerService.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: 12189 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 { 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