🚀 v0.1.0-alpha – Protection System, Anti-Raid & Core Stability (#39)
* chore(branch/dev): now have a dev branch to test my update * feat(logs/disable): adding the options to disable the logs - Adding the option to the logs command * feat(logs/disable): adding the options to disable the logs - Adding the option to the logs command * feat(logs/disable): finishing the disable command for logs - suppressing all the data stored in database (concerning the logs) - suppressing all the channels on the logs categories * feat(logs/bot): Sending a message when ready to all the buyer - The message is send just the first time after it's just an edit - The message is an embed * style(logs/bot): embed for ready updated - Color is now turned to green (no color saved in database for all the bot) * feat(logs/bot): Sending a message when ready to all the buyer - The message is send just the first time after it's just an edit - The message is an embed * style(logs/bot): embed for ready updated - Color is now turned to green (no color saved in database for all the bot) * style(ready/msg): now embed use timestamp to give the last restart * style(ready/msg): norming stuff to be coherent with the code - Give the backline like the other option * feat(logs/collect): The collector is now timed out - If the user pass more than 1 minutes to select the roles the message will be updated and the collector disable. * feat(logs/guildCreate): now joining a server is sending a message - All the buyer will recieve a message when the bot is added to a new guild. - The information give by the message is invitation / member count / owner id * feat(logs/guildDelete): adding the private message when leaving a server - An embed with the owner id / guild name / member count * 🧪 Alpha v0.0.3 – Buyer Logs, Guild Events & UX Polish 💌 Buyer Notifications – Now Real-Time • 🟢 On Bot Ready → Sends a DM to all buyers when the bot boots up. → Reuses the same message for future startups by editing the last one. → Includes a timestamp ⏱️ to track the latest restart. → Fresh new green embed style for clarity ✅ • ➕ On Guild Join → Buyers are notified when the bot joins a new server. → Embed includes: • Server name & ID • Member count 👥 • Owner ID 👑 • Instant invite link 🔗 • ➖ On Guild Leave → Buyers also receive a DM when the bot is removed from a server. → Embed includes: • Guild name • Owner ID • Member count at time of removal ⸻ 🔐 Interaction UX Improvements • 🕒 Timed Role Collector for /logs → The role selection menu now times out after 1 minute. → Message is updated to reflect the timeout, and the collector is disabled to avoid confusion. ⸻ 🎨 Style & Code Coherence • ✅ Ready embeds now use consistent layout (backline spacing, timestamps). • 🎨 Unified color scheme: green embeds for success/startup (no per-bot color in DB yet). • 🧼 Normalization pass for embed construction and messaging consistency. ⸻ 📌 This alpha lays the foundation for clean bot lifecycle monitoring, robust buyer communication, and better UI behavior for commands. Next in the pipe? Maybe logging settings persistence or webhook dispatches 👀 * feat(moderation/nuke): adding the command to nuke channel textual - The command have to goal to delete and recreate a clone of the channel * fix(moderation/nuke): selecting the people who can nuke channels - Now only people with the whitelisted status (or more) can renew / nuke the channel * fix(moderation/nuke): selecting the people who can nuke channels - Now only people with the whitelisted status (or more) can renew / nuke the channel feat(logs/guildUpdate): now guildUpdate event is catch and send a message to guildlogs - logging the diff between: - premiumTier - contentFilter - locale - name - afk * fix(moderation/nuke): selecting the people who can nuke channels - Now only people with the whitelisted status (or more) can renew / nuke the channel feat(logs/guildUpdate): now guildUpdate event is catch and send a message to guildlogs - logging the diff between: - premiumTier - contentFilter - locale - name - afk * fix(logs/guildCreate): fixing typo on the final console log - The console logs was pasted from the index.ts and now is updating to use the interaction.guild instead the counter of the database * style(logs/guildUpdate): updating the messages instead using integer - now the verification level will send the same message wrote in the rules - the explicit content level will send a message instead just a single index * refractor(logs/guildUpdate): adding the subfolder 'guild' in event * refractor(admin/deletecat): adding the deleteCategories in administration part * refractor(event/guildUpdate): typing the function parameters - Adding the update for guild update event, (oldGuild / newGuild) * refactor(event/guildDelete): typing the parameter of the function - Typing the execute function of the event guildDelete (in user cat) * refactor(event/interaction): adding the type on interaction create * feat(lib/perm): now isWhitelisted can be import on commands - The command is exportable including the lib/perm.ts. Starting to have really utils (and so function) on this project * refactor(client/ready): eslint is now respecting - The eslint is now respect. * docs(utils/perm): Adding documentation for utils - Doxygen will be now required for all the utils * core(bun/package): update all the package to latest version - There is now a warning when running the bot cause by an electron update waiting to discord.js * refactor(event/ready): format following the eslint - The code is now setup like the eslint is configurated * style(event/guildDelete): adding footer to delete guild - The footer by default for the server is now added when suppressed from the server * refactor(commands): starting to adding type on the command - Now with the lsp configurated is easier to see the type error (interger instead number) / Guild -> GuildPrisma * fix(moderation/nuke): correcting the type guildText - The type was not correct * fix(client/guildCreate): typing many thing to respect the Type - The type was not correct * fix(client/guildUpdate): typing many thing to respect the Type - The type was not correct * fix(client/interactino): typing many thing to respect the Type - The type was not correct * fix(lib/permission): typing many thing to respect the Type - The type was not correct * feat(event/messages): adding the MessageDelete logs * core: updating packages * fix(lib/perm): correcting the type integer to number * fix(internal/deploycommand): adding type to command* variable * feat(events/messagesDelete): adding type and now sending the log message * feat(events/messagesBulkDelete): adding the event management * fix(event/client): adding types on guildCreate - Adding prisma types * fix(event/client): adding types on guildDelete - Adding prisma types * fix(cmd/administration): adding types on deletecat - Adding prisma types * fix(command/rank): adding type on whitelist * fix(command/rank): import CommandInteraction on whiltelist * feat(event/messages): adding the MessageCreate event - No logging stuff but a level system from scratch * style(event/message): adding the pipe like in other messages. * style(action/bun): removing the space * feat(flake/tmux): adding tmux project configuration - Adding the tmux-setup command how load my own tmux configuration to work on this project * fix(flake/tmux): patching the zplug issues when loading to many time - just adding a exec zsh * feat(event/messages): adding title on MessageBulkDelete * feat(event/messages): adding title on MessageDelete * feat(event/messages): adding logs for MessageUpdate * fix(event/messages): Fixing the prisma call - Using the old variable name message become oldMessage / newMessage * style(event/messages): Adding Italic style if content cannot be load * feat(cmd/admin): adding protect command * feat(events/channel): adding channelCreate event - The event contains: - logs for user whitelisted / Owner / Buyer - Antiraid for others * feat(events/channel): adding channelDelete event - The event contains: - logs for user whitelisted / Owner / Buyer - Antiraid for others * feat(events/channel): adding channeUpdate event - The event contains: - logs for users * feat(src/lib): adding the a function to choose the correction mention
This commit is contained in:
parent
13cb14cba3
commit
610e5ea946
26 changed files with 1156 additions and 92 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { MessageFlags, ChannelType, SlashCommandBuilder } from 'discord.js';
|
||||
import { MessageFlags, ChannelType, SlashCommandBuilder, CommandInteraction } from 'discord.js';
|
||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||
import { prisma } from '../../lib/prisma.ts';
|
||||
import { User as UserPrisma } from '@prisma/client';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
|
|
@ -14,7 +15,7 @@ export default {
|
|||
.addChannelTypes(ChannelType.GuildCategory),
|
||||
),
|
||||
async execute(interaction: CommandInteraction) {
|
||||
let userData: User;
|
||||
let userData: UserPrisma;
|
||||
try {
|
||||
userData = await prisma.user.findUnique({
|
||||
where: {
|
||||
|
|
@ -40,7 +41,7 @@ export default {
|
|||
});
|
||||
return;
|
||||
}
|
||||
const category: GuildCategory = interaction.options.getChannel(
|
||||
const category: ChannelType.GuildCategory = interaction.options.getChannel(
|
||||
'category',
|
||||
true,
|
||||
);
|
||||
|
|
@ -10,8 +10,8 @@ import {
|
|||
StringSelectMenuOptionBuilder,
|
||||
SlashCommandBuilder,
|
||||
MessageFlags,
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
CommandInteraction,
|
||||
} from 'discord.js';
|
||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||
|
||||
|
|
@ -72,11 +72,6 @@ export default {
|
|||
}
|
||||
catch (err) {
|
||||
throw `\t⚠️ | Cannot get the database connection!\n\t\t(${err}).`;
|
||||
await interaction.reply({
|
||||
content: `${emoji.answer.error} | Cannot connect to the database`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const choice: string = interaction.options.getString('action');
|
||||
switch (choice) {
|
||||
|
|
@ -150,7 +145,7 @@ export default {
|
|||
),
|
||||
);
|
||||
|
||||
const roleSelection =
|
||||
const roleSelection: ActionRowBuilder<StringSelectMenuBuilder> =
|
||||
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(menu);
|
||||
|
||||
const permSelector: EmbedBuilder = new EmbedBuilder()
|
||||
|
|
@ -170,6 +165,15 @@ export default {
|
|||
time: 60_000,
|
||||
max: 25,
|
||||
});
|
||||
collector.on('end', async (collected) => {
|
||||
if (collected.size === 0) {
|
||||
await interaction.editReply({
|
||||
content: '⏰ | Too many time to select roles allowed to see the logs',
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
collector.on(
|
||||
'collect',
|
||||
async (selectInteraction: StringSelectMenuInteraction) => {
|
||||
|
|
|
|||
184
src/commands/administration/protect.ts
Normal file
184
src/commands/administration/protect.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
ActionRowBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
EmbedBuilder,
|
||||
ComponentType,
|
||||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
} from 'discord.js';
|
||||
import { prisma } from '../../lib/prisma';
|
||||
import { Guild as GuildPrisma } from '@prisma/client';
|
||||
|
||||
const modules = {
|
||||
'anti-channel': 'Block channel creation/deletion',
|
||||
'anti-rank': 'Block dangerous rank modifications',
|
||||
'anti-perm': 'Block dangerous permissions on roles',
|
||||
'anti-massban': 'Prevent mass bans',
|
||||
'anti-mass-mention': 'Prevent mass mentions',
|
||||
'anti-bot': 'Prevent unauthorized bots',
|
||||
};
|
||||
|
||||
function camel(str: string): string {
|
||||
return str
|
||||
.split('-')
|
||||
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function getEmbed(guildData: GuildPrisma): EmbedBuilder {
|
||||
let description: string = '\n';
|
||||
for (const [key, label] of Object.entries(modules)) {
|
||||
const field = `protect${camel(key)}`;
|
||||
const enabled = guildData[field as keyof typeof guildData] as boolean;
|
||||
description += `- **${label}**: ${enabled ? '✅' : '❌'}\n`;
|
||||
}
|
||||
const baseEmbed = new EmbedBuilder()
|
||||
.setTitle('🛡️ | Protection Manager')
|
||||
.setDescription(description)
|
||||
.setFooter({
|
||||
text: guildData.footer,
|
||||
})
|
||||
.setColor(guildData.color);
|
||||
return baseEmbed;
|
||||
}
|
||||
|
||||
function getButton(selected: string, active: boolean): ActionRowBuilder<ButtonBuilder> {
|
||||
const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`enable_${selected}`)
|
||||
.setLabel('Enable')
|
||||
.setEmoji('✅')
|
||||
.setDisabled(!active)
|
||||
.setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`disable_${selected}`)
|
||||
.setLabel('Disable')
|
||||
.setEmoji('❌')
|
||||
.setDisabled(!active)
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
new ButtonBuilder()
|
||||
.setCustomId('return')
|
||||
.setLabel('Return')
|
||||
.setEmoji('↩️')
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
return button;
|
||||
}
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('protect')
|
||||
.setDescription('Manage guild protections interactively'),
|
||||
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const guildId: string | null = interaction.guildId!;
|
||||
let guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||
where: {
|
||||
id: guildId,
|
||||
},
|
||||
});
|
||||
if (!guildData) {
|
||||
guildData = await prisma.guild.create({
|
||||
data: {
|
||||
id: guildId,
|
||||
},
|
||||
});
|
||||
}
|
||||
const menu: StringSelectMenuBuilder = new StringSelectMenuBuilder()
|
||||
.setCustomId('select_protect')
|
||||
.setPlaceholder('Select a module')
|
||||
.addOptions(
|
||||
Object.entries(modules).map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
})),
|
||||
);
|
||||
const msg = await interaction.reply({
|
||||
embeds: [getEmbed(guildData)],
|
||||
components: [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(menu)],
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
const collector = msg.createMessageComponentCollector({
|
||||
componentType: ComponentType.StringSelect,
|
||||
time: 5 * 60 * 1000,
|
||||
});
|
||||
collector.on('collect', async (selectInteraction) => {
|
||||
if (selectInteraction.user.id !== interaction.user.id) {
|
||||
return selectInteraction.reply({
|
||||
content: '❌ You cannot use this menu.',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
const selected: string = selectInteraction.values[0] as keyof typeof modules;
|
||||
const enabled = guildData![`protect${camel(selected)}`];
|
||||
const moduleEmbed = new EmbedBuilder()
|
||||
.setTitle(`⚙️ | Manage ${modules[selected]}`)
|
||||
.setFooter({
|
||||
text: guildData.footer,
|
||||
})
|
||||
.setDescription(
|
||||
`This module is currently: **${enabled ? 'Enabled ✅' : 'Disabled ❌'}**`,
|
||||
)
|
||||
.setColor(enabled ? '#00ff00' : '#ff0000');
|
||||
await selectInteraction.update({
|
||||
embeds: [moduleEmbed],
|
||||
components: [getButton(selected, true)],
|
||||
});
|
||||
});
|
||||
const buttonCollector = msg.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
time: 5 * 60 * 1000,
|
||||
});
|
||||
buttonCollector.on('collect', async (btnInteraction) => {
|
||||
if (btnInteraction.user.id !== interaction.user.id) {
|
||||
return btnInteraction.reply({
|
||||
content: '❌ | You cannot use these buttons.',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
if (btnInteraction.customId === 'return') {
|
||||
return btnInteraction.update({
|
||||
embeds: [getEmbed(guildData)],
|
||||
components: [
|
||||
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(menu),
|
||||
],
|
||||
});
|
||||
}
|
||||
const [action, moduleName] = btnInteraction.customId.split('_');
|
||||
const field = `protect${camel(moduleName)}`;
|
||||
await prisma.guild.update({
|
||||
where: {
|
||||
id: guildId,
|
||||
},
|
||||
data: {
|
||||
[field]: action === 'enable',
|
||||
},
|
||||
});
|
||||
guildData = await prisma.guild.findUnique({
|
||||
where: {
|
||||
id: guildId,
|
||||
},
|
||||
});
|
||||
await btnInteraction.update({
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setTitle(`⚙️ | Manage ${modules[moduleName as keyof typeof modules]}`)
|
||||
.setFooter({
|
||||
text: guildData.footer,
|
||||
})
|
||||
.setDescription(
|
||||
`This module is now: **${
|
||||
action === 'enable' ? '✅ Enabled' : '❌ Disabled'
|
||||
}**`,
|
||||
)
|
||||
.setColor(action === 'enable' ? '#00ff00' : '#ff0000'),
|
||||
],
|
||||
components: [getButton(null, false)],
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -4,8 +4,10 @@ import {
|
|||
PresenceUpdateStatus,
|
||||
MessageFlags,
|
||||
SlashCommandBuilder,
|
||||
CommandInteraction,
|
||||
} from 'discord.js';
|
||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||
import { User as UserPrisma } from '@prisma/client';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
|
|
@ -108,7 +110,7 @@ export default {
|
|||
),
|
||||
),
|
||||
async execute(interaction: CommandInteraction) {
|
||||
let userData: User;
|
||||
let userData: UserPrisma | null;
|
||||
try {
|
||||
userData = await prisma.user.findUnique({
|
||||
where: {
|
||||
|
|
|
|||
44
src/commands/moderation/nuke.ts
Normal file
44
src/commands/moderation/nuke.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { MessageFlags, SlashCommandBuilder, ChannelType, CommandInteraction } from 'discord.js';
|
||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||
import { isWhitelisted } from '../../lib/perm.ts';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('nuke')
|
||||
.setDescription('Allow to delete and recreate a channel')
|
||||
.addChannelOption((opt) =>
|
||||
opt
|
||||
.setName('channel')
|
||||
.setDescription('Choose the channel you want to renew')
|
||||
.addChannelTypes(ChannelType.GuildText),
|
||||
),
|
||||
async execute(interaction: CommandInteraction) {
|
||||
if (!(await isWhitelisted(interaction.user.id, interaction.guild.id))) {
|
||||
interaction.reply({
|
||||
content: `${emoji.answer.no} | You're not whitelisted on this server`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const oldChannel : ChannelType.GuildText = interaction.options.getChannel(
|
||||
'channel',
|
||||
) || interaction.channel;
|
||||
const pos: number = oldChannel.position;
|
||||
|
||||
oldChannel.clone().then((newchannel) => {
|
||||
newchannel.setPosition(pos);
|
||||
interaction.client.channels.fetch(newchannel.id).then(
|
||||
channel => channel.send({
|
||||
content: `${emoji.answer.yes} | ${newchannel} has been nuked by \`${interaction.user.username}\``,
|
||||
ephermal: true,
|
||||
}),
|
||||
);
|
||||
try {
|
||||
oldChannel.delete();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`⚠️ | Error when suppressing the channel\n\t${err}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import { EmbedBuilder, MessageFlags, SlashCommandBuilder } from 'discord.js';
|
||||
import { CommandInteraction, EmbedBuilder, MessageFlags, SlashCommandBuilder } from 'discord.js';
|
||||
import { prisma } from '../../lib/prisma.ts';
|
||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||
import { User as UserPrisma } from '@prisma/client';
|
||||
import { Guild as GuildPrisma } from '@prisma/client';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
|
|
@ -33,7 +35,7 @@ export default {
|
|||
),
|
||||
async execute(interaction: CommandInteraction) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
let userData: User;
|
||||
let userData: UserPrisma;
|
||||
try {
|
||||
userData = await prisma.user.findUnique({
|
||||
where: {
|
||||
|
|
@ -51,7 +53,7 @@ export default {
|
|||
});
|
||||
return;
|
||||
}
|
||||
let guildData: Guild;
|
||||
let guildData: GuildPrisma;
|
||||
try {
|
||||
guildData = await prisma.guild.findUnique({
|
||||
where: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue