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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26