A powerful and extendable Discord bot, with it's own module system :3
thevoid.cafe/projects/voidy
1import {
2 ActionRowBuilder,
3 ButtonBuilder,
4 ButtonStyle,
5 EmbedBuilder,
6 Events,
7 type GuildMember,
8 type TextChannel,
9} from "discord.js";
10import { Event } from "@voidy/framework";
11import type { IGuildConfig } from "../schemas/GuildConfig";
12
13const TWO_DAYS = 2 * 24 * 60 * 60 * 1000;
14const TWO_WEEKS = 14 * 24 * 60 * 60 * 1000;
15const TWO_MONTHS = 60 * 24 * 60 * 60 * 1000;
16
17function getAccountRisk(createdAt: Date): { level: string; color: number } {
18 const age = Date.now() - createdAt.getTime();
19
20 if (age < TWO_DAYS) return { level: "Extreme", color: 0xe21e1e };
21 if (age < TWO_WEEKS) return { level: "High", color: 0xe24d1e };
22 if (age < TWO_MONTHS) return { level: "Medium", color: 0xe2bb1e };
23 return { level: "Fairly Safe", color: 0x74e21e };
24}
25
26export default new Event({
27 id: "guildMemberAdd",
28 name: Events.GuildMemberAdd,
29
30 execute: async (ctx, member: unknown) => {
31 const m = member as GuildMember;
32 const { client, buttonId } = ctx;
33
34 const cache = client.data.get("guildConfig") as Map<string, IGuildConfig> | undefined;
35 const config = cache?.get(m.guild.id);
36 if (!config) return;
37
38 // Auto-role assignment
39 const roleId = m.user.bot ? config.autoRoles?.botRoleId : config.autoRoles?.memberRoleId;
40 if (roleId) {
41 try {
42 await m.roles.add(roleId);
43 } catch (err) {
44 console.error(`[Guild] Failed to assign auto-role to ${m.user.tag}:`, err);
45 }
46 }
47
48 // Welcome message
49 if (config.welcome?.channelId) {
50 const welcomeChannel = m.guild.channels.cache.get(config.welcome.channelId) as
51 | TextChannel
52 | undefined;
53
54 if (welcomeChannel) {
55 const embed = new EmbedBuilder()
56 .setColor(m.user.accentColor ?? 0x5865f2)
57 .setThumbnail(m.user.displayAvatarURL({ size: 256 }))
58 .setDescription(`Welcome to **${m.guild.name}**, ${m}!`)
59 .setTimestamp();
60
61 await welcomeChannel.send({ embeds: [embed] });
62 }
63 }
64
65 // Member logging
66 if (config.logging?.channelId) {
67 const logChannel = m.guild.channels.cache.get(config.logging.channelId) as
68 | TextChannel
69 | undefined;
70
71 if (!logChannel) return;
72
73 const risk = getAccountRisk(m.user.createdAt);
74 const createdTimestamp = Math.floor(m.user.createdTimestamp / 1000);
75
76 const embed = new EmbedBuilder()
77 .setColor(risk.color)
78 .setAuthor({
79 name: `${m.user.tag} joined`,
80 iconURL: m.user.displayAvatarURL(),
81 })
82 .addFields(
83 { name: "User", value: `${m}`, inline: true },
84 { name: "Account Created", value: `<t:${createdTimestamp}:R>`, inline: true },
85 { name: "Risk Level", value: risk.level, inline: true },
86 )
87 .setFooter({ text: `ID: ${m.id}` })
88 .setTimestamp();
89
90 if (roleId) {
91 embed.addFields({ name: "Auto-Role", value: `<@&${roleId}>`, inline: true });
92 }
93
94 const components: ActionRowBuilder<ButtonBuilder>[] = [];
95
96 if (risk.level === "High" || risk.level === "Extreme") {
97 const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
98 new ButtonBuilder()
99 .setCustomId(buttonId("MemberLogging", "Kick", m.id))
100 .setLabel("Kick")
101 .setStyle(ButtonStyle.Danger),
102 new ButtonBuilder()
103 .setCustomId(buttonId("MemberLogging", "Ban", m.id))
104 .setLabel("Ban")
105 .setStyle(ButtonStyle.Danger),
106 );
107 components.push(row);
108 }
109
110 await logChannel.send({ embeds: [embed], components });
111 }
112 },
113});