🚀 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
25
flake.nix
25
flake.nix
|
|
@ -9,9 +9,27 @@
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs { inherit system; };
|
||||||
inherit system;
|
tmux-setup = pkgs.writeShellScriptBin "tmux-setup" ''
|
||||||
};
|
#!/usr/bin/env sh
|
||||||
|
SESSION="TTY"
|
||||||
|
DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
||||||
|
if ! tmux has-session -t $SESSION 2>/dev/null; then
|
||||||
|
tmux new-session -d -s $SESSION -c "$DIR" -n dev
|
||||||
|
tmux send-keys -t $SESSION:0 'vim' C-m
|
||||||
|
tmux split-window -h -p 30 -t $SESSION:0 -c "$DIR"
|
||||||
|
tmux send-keys -t $SESSION:0.2 'exec zsh' C-m
|
||||||
|
tmux split-window -v -p 30 -t $SESSION:0.1 -c "$DIR"
|
||||||
|
tmux send-keys -t $SESSION:0.2 'watch -n0.5 bunx eslint ./src' C-m
|
||||||
|
tmux split-window -v -p 50 -t $SESSION:0.2 -c "$DIR"
|
||||||
|
tmux send-keys -t $SESSION:0.3 'bunx prisma studio' C-m
|
||||||
|
tmux new-window -t $SESSION:1 -n git -c "$DIR"
|
||||||
|
tmux send-keys -t $SESSION:1 'lazygit' C-m
|
||||||
|
fi
|
||||||
|
tmux select-window -t $SESSION:0
|
||||||
|
tmux select-pane -t $SESSION:0.0
|
||||||
|
tmux attach -t $SESSION
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
name = "discord-bot-bun-ts";
|
name = "discord-bot-bun-ts";
|
||||||
|
|
@ -22,6 +40,7 @@
|
||||||
wget
|
wget
|
||||||
nodejs_latest
|
nodejs_latest
|
||||||
mariadb
|
mariadb
|
||||||
|
tmux-setup
|
||||||
];
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export NIX_SHOW_STATS=0
|
export NIX_SHOW_STATS=0
|
||||||
|
|
|
||||||
14
package.json
14
package.json
|
|
@ -15,22 +15,22 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.36.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.1.5",
|
"lint-staged": "^16.2.3",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prisma": "^6.14.0",
|
"prisma": "^6.16.2",
|
||||||
"typescript-eslint": "^8.39.1"
|
"typescript-eslint": "^8.45.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.14.0",
|
"@prisma/client": "^6.16.2",
|
||||||
"discord.js": "^14.21.0",
|
"discord.js": "^14.22.1",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.2",
|
||||||
"mariadb": "^3.4.5"
|
"mariadb": "^3.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,14 @@ model Guild {
|
||||||
logMsg String?
|
logMsg String?
|
||||||
logServer String?
|
logServer String?
|
||||||
|
|
||||||
|
protectEnabled Boolean @default(false)
|
||||||
|
protectAntiChannel Boolean @default(false)
|
||||||
|
protectAntiRank Boolean @default(false)
|
||||||
|
protectAntiPerm Boolean @default(false)
|
||||||
|
protectAntiMassban Boolean @default(false)
|
||||||
|
protectAntiMassMention Boolean @default(false)
|
||||||
|
protectAntiBot Boolean @default(false)
|
||||||
|
|
||||||
footer String @default("© EniumTeam ~ 2025")
|
footer String @default("© EniumTeam ~ 2025")
|
||||||
color String @default("#000000")
|
color String @default("#000000")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||||
import { prisma } from '../../lib/prisma.ts';
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { User as UserPrisma } from '@prisma/client';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
|
@ -14,7 +15,7 @@ export default {
|
||||||
.addChannelTypes(ChannelType.GuildCategory),
|
.addChannelTypes(ChannelType.GuildCategory),
|
||||||
),
|
),
|
||||||
async execute(interaction: CommandInteraction) {
|
async execute(interaction: CommandInteraction) {
|
||||||
let userData: User;
|
let userData: UserPrisma;
|
||||||
try {
|
try {
|
||||||
userData = await prisma.user.findUnique({
|
userData = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -40,7 +41,7 @@ export default {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const category: GuildCategory = interaction.options.getChannel(
|
const category: ChannelType.GuildCategory = interaction.options.getChannel(
|
||||||
'category',
|
'category',
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
@ -10,8 +10,8 @@ import {
|
||||||
StringSelectMenuOptionBuilder,
|
StringSelectMenuOptionBuilder,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
MessageFlags,
|
MessageFlags,
|
||||||
SlashCommandBuilder,
|
|
||||||
EmbedBuilder,
|
EmbedBuilder,
|
||||||
|
CommandInteraction,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
|
@ -72,11 +72,6 @@ export default {
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
throw `\t⚠️ | Cannot get the database connection!\n\t\t(${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');
|
const choice: string = interaction.options.getString('action');
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
|
|
@ -150,7 +145,7 @@ export default {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const roleSelection =
|
const roleSelection: ActionRowBuilder<StringSelectMenuBuilder> =
|
||||||
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(menu);
|
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(menu);
|
||||||
|
|
||||||
const permSelector: EmbedBuilder = new EmbedBuilder()
|
const permSelector: EmbedBuilder = new EmbedBuilder()
|
||||||
|
|
@ -170,6 +165,15 @@ export default {
|
||||||
time: 60_000,
|
time: 60_000,
|
||||||
max: 25,
|
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(
|
collector.on(
|
||||||
'collect',
|
'collect',
|
||||||
async (selectInteraction: StringSelectMenuInteraction) => {
|
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,
|
PresenceUpdateStatus,
|
||||||
MessageFlags,
|
MessageFlags,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
|
CommandInteraction,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||||
|
import { User as UserPrisma } from '@prisma/client';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
|
@ -108,7 +110,7 @@ export default {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
async execute(interaction: CommandInteraction) {
|
async execute(interaction: CommandInteraction) {
|
||||||
let userData: User;
|
let userData: UserPrisma | null;
|
||||||
try {
|
try {
|
||||||
userData = await prisma.user.findUnique({
|
userData = await prisma.user.findUnique({
|
||||||
where: {
|
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 { prisma } from '../../lib/prisma.ts';
|
||||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
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 {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
|
@ -33,7 +35,7 @@ export default {
|
||||||
),
|
),
|
||||||
async execute(interaction: CommandInteraction) {
|
async execute(interaction: CommandInteraction) {
|
||||||
const subcommand = interaction.options.getSubcommand();
|
const subcommand = interaction.options.getSubcommand();
|
||||||
let userData: User;
|
let userData: UserPrisma;
|
||||||
try {
|
try {
|
||||||
userData = await prisma.user.findUnique({
|
userData = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -51,7 +53,7 @@ export default {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let guildData: Guild;
|
let guildData: GuildPrisma;
|
||||||
try {
|
try {
|
||||||
guildData = await prisma.guild.findUnique({
|
guildData = await prisma.guild.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|
|
||||||
75
src/events/channel/channelCreate.ts
Normal file
75
src/events/channel/channelCreate.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Events, AuditLogEvent, TextChannel, EmbedBuilder, Channel } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
import { isWhitelisted } from '../../lib/perm.ts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.ChannelCreate,
|
||||||
|
async execute(channel: Channel) {
|
||||||
|
if (!channel.guild) return;
|
||||||
|
try {
|
||||||
|
const auditLogs = await channel.guild.fetchAuditLogs({
|
||||||
|
type: AuditLogEvent.ChannelCreate,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
const entry = auditLogs.entries.first();
|
||||||
|
if (!entry) return;
|
||||||
|
const executor = entry.executor;
|
||||||
|
if (!executor) return;
|
||||||
|
const guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: channel.guild.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!guildData) return;
|
||||||
|
if (!isWhitelisted(executor.id, channel.guild.id)) {
|
||||||
|
await channel.delete(`Unauthorized channel creation by ${executor.tag}`);
|
||||||
|
const member = await channel.guild.members.fetch(executor.id).catch(() => null);
|
||||||
|
if (member) {
|
||||||
|
const rolesToRemove = member.roles.cache.filter(r => r.id !== channel.guild.id);
|
||||||
|
for (const [id] of rolesToRemove) {
|
||||||
|
await member.roles.remove(id, 'Unauthorized channel creation [TTY AntiRaid]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (guildData.logMod) {
|
||||||
|
const logChannel = await channel.guild.channels.fetch(guildData.logMod).catch(() => null);
|
||||||
|
if (logChannel?.isTextBased()) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('⚠️ | Anti-Channel Protection')
|
||||||
|
.setDescription(
|
||||||
|
`**${channel.name}** created by <@${executor.id}> is now **deleted**.\n__Sanction:__ Unranked.`,
|
||||||
|
)
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
});
|
||||||
|
(logChannel as TextChannel).send({
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guildData.logChannel) {
|
||||||
|
const logChannel = await channel.guild.channels.fetch(guildData.logChannel).catch(() => null);
|
||||||
|
if (logChannel?.isTextBased()) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('📢 Channel Created')
|
||||||
|
.setDescription(`Channel **${channel.name}** has been created by <@${executor.id}>.`)
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
});
|
||||||
|
(logChannel as TextChannel).send({
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(`⚠️ | ChannelCreate protection error: ${err}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
77
src/events/channel/channelDelete.ts
Normal file
77
src/events/channel/channelDelete.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { Events, AuditLogEvent, TextChannel, EmbedBuilder, Channel } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
import { isWhitelisted } from '../../lib/perm.ts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.ChannelDelete,
|
||||||
|
async execute(channel: Channel) {
|
||||||
|
if (!channel.guild) return;
|
||||||
|
try {
|
||||||
|
const auditLogs = await channel.guild.fetchAuditLogs({
|
||||||
|
type: AuditLogEvent.ChannelDelete,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
const entry = auditLogs.entries.first();
|
||||||
|
if (!entry) return;
|
||||||
|
const executor = entry.executor;
|
||||||
|
if (!executor) return;
|
||||||
|
const guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: channel.guild.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!guildData) return;
|
||||||
|
if (!(await isWhitelisted(executor.id, channel.guild.id))) {
|
||||||
|
const member = await channel.guild.members.fetch(executor.id).catch(() => null);
|
||||||
|
if (member) {
|
||||||
|
const rolesToRemove = member.roles.cache.filter(r => r.id !== channel.guild.id);
|
||||||
|
for (const [id] of rolesToRemove) {
|
||||||
|
await member.roles.remove(id, 'Unauthorized channel deletion [TTY AntiRaid]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.clone().then((newchannel) => {
|
||||||
|
newchannel.setPosition(channel.position);
|
||||||
|
});
|
||||||
|
if (guildData.logMod) {
|
||||||
|
const logChannel = await channel.guild.channels.fetch(guildData.logMod).catch(() => null);
|
||||||
|
if (logChannel instanceof TextChannel) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('⚠️ | Anti-Channel Protection')
|
||||||
|
.setDescription(
|
||||||
|
`**${channel.name}** deleted by <@${executor.id}> is now **recreated**.\n__Sanction:__ Unranked.`,
|
||||||
|
)
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
});
|
||||||
|
(logChannel as TextChannel).send({
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guildData.logChannels) {
|
||||||
|
const logChannel = await channel.guild.channels.fetch(guildData.logChannel).catch(() => null);
|
||||||
|
if (logChannel instanceof TextChannel) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('🗑️ | Channel Deleted')
|
||||||
|
.setDescription(`A channel was deleted by <@${executor.id}>.`)
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
});
|
||||||
|
(logChannel as TextChannel).send({
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(`⚠️ | ChannelDelete protection error: ${err}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
84
src/events/channel/channelUpdate.ts
Normal file
84
src/events/channel/channelUpdate.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import {
|
||||||
|
Events,
|
||||||
|
AuditLogEvent,
|
||||||
|
TextChannel,
|
||||||
|
EmbedBuilder,
|
||||||
|
Channel,
|
||||||
|
GuildChannel,
|
||||||
|
} from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
import { getCorrectMention } from '../../lib/mention';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.ChannelUpdate,
|
||||||
|
async execute(oldChannel: Channel, newChannel: Channel) {
|
||||||
|
if (!newChannel.guild) return;
|
||||||
|
try {
|
||||||
|
const logs = await newChannel.guild.fetchAuditLogs({
|
||||||
|
type: AuditLogEvent.ChannelUpdate | AuditLogEvent.ChannelOverwriteCreate | AuditLogEvent.ChannelOverwriteDelete | AuditLogEvent.ChannelOverwriteUpdate,
|
||||||
|
limit: 5,
|
||||||
|
});
|
||||||
|
const entry = [...logs.entries.values()]
|
||||||
|
.filter(e => (e.target as GuildChannel)?.id === newChannel.id)
|
||||||
|
.sort((a, b) => b.createdTimestamp - a.createdTimestamp)[0];
|
||||||
|
const executor = entry?.executor;
|
||||||
|
const guildData: GuildPrisma | null = await prisma.guild.findUnique({
|
||||||
|
where: { id: newChannel.guild.id },
|
||||||
|
});
|
||||||
|
if (!guildData) return;
|
||||||
|
const changes: string[] = [];
|
||||||
|
if (oldChannel.name !== newChannel.name) {
|
||||||
|
changes.push(`**Name:** \`${oldChannel.name}\` → \`${newChannel.name}\``);
|
||||||
|
}
|
||||||
|
if ('topic' in oldChannel && 'topic' in newChannel) {
|
||||||
|
if (oldChannel.topic !== newChannel.topic) {
|
||||||
|
changes.push(
|
||||||
|
`**Topic:** \`${oldChannel.topic ?? 'None'}\` → \`${newChannel.topic ?? 'None'}\``,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const oldPerms = oldChannel.permissionOverwrites.cache;
|
||||||
|
const newPerms = newChannel.permissionOverwrites.cache;
|
||||||
|
newPerms.forEach((overwrite, id) => {
|
||||||
|
const old = oldPerms.get(id);
|
||||||
|
if (!old) {
|
||||||
|
changes.push(`New overwrite added for ${getCorrectMention(oldChannel.guild, id)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
overwrite.allow.bitfield !== old.allow.bitfield ||
|
||||||
|
overwrite.deny.bitfield !== old.deny.bitfield
|
||||||
|
) {
|
||||||
|
changes.push(`Overwrite changed for <@&${id}> / <@${id}>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
oldPerms.forEach((overwrite, id) => {
|
||||||
|
if (!newPerms.has(id)) {
|
||||||
|
changes.push(`Overwrite removed for <@&${id}> / <@${id}>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (guildData.logChannels) {
|
||||||
|
const logChannel = await newChannel.guild.channels
|
||||||
|
.fetch(guildData.logChannels)
|
||||||
|
.catch(() => null);
|
||||||
|
if (logChannel instanceof TextChannel) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('✏️ Channel Updated')
|
||||||
|
.setDescription(
|
||||||
|
`Channel **${newChannel.name}** ${
|
||||||
|
executor ? `was updated by <@${executor.id}>` : 'was updated'
|
||||||
|
}.\n\n${changes.join('\n') || 'No details'}`,
|
||||||
|
)
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({ text: guildData.footer });
|
||||||
|
await logChannel.send({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(`⚠️ | ChannelUpdate log error: ${err}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,39 @@
|
||||||
import { Events } from 'discord.js';
|
import { Events, EmbedBuilder, Guild } from 'discord.js';
|
||||||
import { prisma } from '../../lib/prisma.ts';
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { Bot as BotPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
async function getGuildInvite(guild: Guild): Promise<string> {
|
||||||
|
try {
|
||||||
|
if (guild.vanityURLCode) {
|
||||||
|
return `https://discord.gg/${guild.vanityURLCode}`;
|
||||||
|
}
|
||||||
|
const channel : GuildChannel = guild.channels.cache
|
||||||
|
.filter(
|
||||||
|
(ch): ch is GuildChannel =>
|
||||||
|
ch.isTextBased() &&
|
||||||
|
!!ch.permissionsFor(guild.members.me!)?.has('CreateInstantInvite'),
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
if (!channel) {
|
||||||
|
return 'No invite available';
|
||||||
|
}
|
||||||
|
const invite: Invite = await channel.createInvite({
|
||||||
|
maxAge: 0,
|
||||||
|
maxUses: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return invite.url;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.warn(`⚠️ Impossible de créer une invitation pour ${guild.id} : ${err}`);
|
||||||
|
return 'No invite available';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: Events.GuildCreate,
|
name: Events.GuildCreate,
|
||||||
async execute(guild) {
|
async execute(guild: Guild) {
|
||||||
|
try {
|
||||||
await prisma.guild.upsert({
|
await prisma.guild.upsert({
|
||||||
where: {
|
where: {
|
||||||
id: guild.id,
|
id: guild.id,
|
||||||
|
|
@ -39,10 +69,55 @@ export default {
|
||||||
guildId: guild.id,
|
guildId: guild.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(
|
||||||
|
`\t⚠️ | Cannot get the database connection!\n\t\t(${err}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const botData: BotPrisma = await prisma.bot.findUnique({
|
||||||
|
where: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
buyers: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const buyerNotification: EmbedBuilder = new EmbedBuilder()
|
||||||
|
.setTitle(`${guild.client.user.username} joined a new server`)
|
||||||
|
.setColor('#663399')
|
||||||
|
.setDescription(`
|
||||||
|
Name: ${guild.name}
|
||||||
|
Owner id: ${guild.ownerId}
|
||||||
|
Invite: ${guild.vanityURLCode || await getGuildInvite(guild)}
|
||||||
|
Member: ${guild.memberCount}
|
||||||
|
`)
|
||||||
|
.setTimestamp();
|
||||||
|
await Promise.all(
|
||||||
|
botData.buyers.map(async (buyer) => {
|
||||||
|
try {
|
||||||
|
const user = await guild.client.users.fetch(buyer.id);
|
||||||
|
const dm = await user.createDM();
|
||||||
|
await dm.send ({
|
||||||
|
embeds: [
|
||||||
|
buyerNotification,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await new Promise(res => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.warn(`⚠️ | ${buyer.id} : ${err}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
console.log(
|
console.log(
|
||||||
`✅ | Guild ${guild.name} synchronisée avec ${members.size} membres.`,
|
`✅ | Guild ${guild.name} synchronisée avec ${guild.memberCount} membres.`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
51
src/events/client/guildDelete.ts
Normal file
51
src/events/client/guildDelete.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Events, EmbedBuilder, Guild } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { Bot as BotPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.GuildDelete,
|
||||||
|
async execute(guild: Guild) {
|
||||||
|
const botData: BotPrisma = await prisma.bot.findUnique({
|
||||||
|
where: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
buyers: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const buyerNotification: EmbedBuilder = new EmbedBuilder()
|
||||||
|
.setTitle(`${guild.client.user.username} leaved a server`)
|
||||||
|
.setColor('#cd5c5c')
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
})
|
||||||
|
.setDescription(`
|
||||||
|
Name: ${guild.name}
|
||||||
|
Owner id: ${guild.ownerId}
|
||||||
|
Member: ${guild.memberCount}
|
||||||
|
`)
|
||||||
|
.setTimestamp();
|
||||||
|
await Promise.all(
|
||||||
|
botData.buyers.map(async (buyer) => {
|
||||||
|
try {
|
||||||
|
const user = await guild.client.users.fetch(buyer.id);
|
||||||
|
const dm = await user.createDM();
|
||||||
|
await dm.send ({
|
||||||
|
embeds: [
|
||||||
|
buyerNotification,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await new Promise(res => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.warn(`⚠️ | ${buyer.id} : ${err}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
55
src/events/client/guildUpdate.ts
Normal file
55
src/events/client/guildUpdate.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Events, Guild, EmbedBuilder, channelMention } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.GuildUpdate,
|
||||||
|
async execute(oldGuild, newGuild) {
|
||||||
|
const guildData: Guild = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: newGuild.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (guildData.logServer) {
|
||||||
|
let toPrint: string = 'The update of the guild had changes theses thing\n';
|
||||||
|
const logChannel = await newGuild.client.channels
|
||||||
|
.fetch(guildData.logServer)
|
||||||
|
.catch(() => null);
|
||||||
|
if (!logChannel || !logChannel.isTextBased()) {return;}
|
||||||
|
if (oldGuild.name !== newGuild.name) {
|
||||||
|
toPrint += `- Name:\n\`${oldGuild.name}\` => \`${newGuild.name}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.description !== newGuild.description) {
|
||||||
|
toPrint += `- Description:\n\`${oldGuild.description}\` => \`${newGuild.description}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.afkChannelId !== newGuild.afkChannelId) {
|
||||||
|
toPrint += `- AfkChannel:\n${oldGuild.afkChannelId ? channelMention(oldGuild.afkChannelId) : 'Not defined'} => ${newGuild.afkChannelId ? channelMention(newGuild.afkChannelId) : 'Not defined'}\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.afkTimeout !== newGuild.afkTimeout) {
|
||||||
|
toPrint += `- Timeout:\n\`${oldGuild.afkTimeout / 60}m\` => \`${newGuild.afkTimeout / 60}m\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.preferredLocale !== newGuild.preferredLocale) {
|
||||||
|
toPrint += `- Language:\n\`${oldGuild.preferredLocale}\` => \`${newGuild.preferredLocale}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.verificationLevel !== newGuild.verificationLevel) {
|
||||||
|
toPrint += `- Verification:\n\`${oldGuild.verificationLevel}\` => \`${newGuild.verificationLevel}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.explicitContentFilter !== newGuild.explicitContentFilter) {
|
||||||
|
toPrint += `- Filter:\n\`${oldGuild.explicitContentFilter}\` => \`${newGuild.explicitContentFilter}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.premiumTier !== newGuild.premiumTier) {
|
||||||
|
toPrint += `- Filter:\n\`${oldGuild.premiumTier}\` => \`${newGuild.premiumTier}\`\n`;
|
||||||
|
}
|
||||||
|
const toRep = new EmbedBuilder()
|
||||||
|
.setColor(`${guildData.color}`)
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
})
|
||||||
|
.setDescription(`${toPrint}`);
|
||||||
|
logChannel.send({
|
||||||
|
embeds: [
|
||||||
|
toRep,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,15 +1,27 @@
|
||||||
import { ActivityType, PresenceUpdateStatus, Events } from 'discord.js';
|
import {
|
||||||
|
ActivityType,
|
||||||
|
EmbedBuilder,
|
||||||
|
PresenceUpdateStatus,
|
||||||
|
Events,
|
||||||
|
} from 'discord.js';
|
||||||
import { prisma } from '../../lib/prisma.ts';
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: Events.ClientReady,
|
name: Events.ClientReady,
|
||||||
once: true,
|
once: true,
|
||||||
async execute(client) {
|
async execute(client: User) {
|
||||||
try {
|
try {
|
||||||
const botData: Bot = await prisma.bot.findUnique({
|
const botData: Bot = await prisma.bot.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
buyers: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const newStatus: string = botData.status;
|
const newStatus: string = botData.status;
|
||||||
const tmpType: string = botData.type;
|
const tmpType: string = botData.type;
|
||||||
|
|
@ -30,6 +42,8 @@ export default {
|
||||||
case 'comptet':
|
case 'comptet':
|
||||||
newType = ActivityType.Competing;
|
newType = ActivityType.Competing;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const tmpPresence: string = botData.presence;
|
const tmpPresence: string = botData.presence;
|
||||||
let newPresence: PresenceUpdateStatus;
|
let newPresence: PresenceUpdateStatus;
|
||||||
|
|
@ -70,6 +84,45 @@ export default {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const buyerNotification: EmbedBuilder = new EmbedBuilder()
|
||||||
|
.setTitle(`${client.user.username} running`)
|
||||||
|
.setColor('#008000')
|
||||||
|
.setDescription(`
|
||||||
|
**On:** ${client.guilds.cache.size} guild${client.guilds.cache.size > 1 ? 's' : ''}
|
||||||
|
**With:** ${client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)} users
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
botData.buyers.map(async (buyer) => {
|
||||||
|
try {
|
||||||
|
const user = await client.users.fetch(buyer.id);
|
||||||
|
const dm = await user.createDM();
|
||||||
|
const messages = await dm.messages.fetch({
|
||||||
|
limit: 20,
|
||||||
|
});
|
||||||
|
const lastBotMsg = messages.find(
|
||||||
|
(m) => m.author.id === client.user!.id,
|
||||||
|
);
|
||||||
|
if (!lastBotMsg) {
|
||||||
|
await lastBotMsg.edit({
|
||||||
|
content: 'This message is will be updated',
|
||||||
|
embeds: [buyerNotification],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await lastBotMsg.edit({
|
||||||
|
content: '',
|
||||||
|
embeds: [buyerNotification],
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.warn(`⚠️ | ${buyer.id} : ${err}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
|
||||||
70
src/events/guild/guildUpdate.ts
Normal file
70
src/events/guild/guildUpdate.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Events, Guild, EmbedBuilder, channelMention, Channel } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
const verificationLevels: string[] = [
|
||||||
|
'Unrestricted',
|
||||||
|
'Low - must have a verified email',
|
||||||
|
'Medium - must be registered for 5 minutes',
|
||||||
|
'High - 10 minutes of membership required',
|
||||||
|
'Highest - verified phone required',
|
||||||
|
];
|
||||||
|
|
||||||
|
const explicitContentLevels: string[] = [
|
||||||
|
'No Scanning Enabled',
|
||||||
|
'Scanning content from members without a role',
|
||||||
|
'Scanning content from all members',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.GuildUpdate,
|
||||||
|
async execute(oldGuild: Guild, newGuild: Guild) {
|
||||||
|
const guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: newGuild.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (guildData.logServer) {
|
||||||
|
let toPrint: string = 'The update of the guild had changes theses thing\n';
|
||||||
|
const logChannel : Channel = await newGuild.client.channels
|
||||||
|
.fetch(guildData.logServer)
|
||||||
|
.catch(() => null);
|
||||||
|
if (!logChannel || !logChannel.isTextBased()) {return;}
|
||||||
|
if (oldGuild.name !== newGuild.name) {
|
||||||
|
toPrint += `- Name:\n\`${oldGuild.name}\` => \`${newGuild.name}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.description !== newGuild.description) {
|
||||||
|
toPrint += `- Description:\n\`${oldGuild.description}\` => \`${newGuild.description}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.afkChannelId !== newGuild.afkChannelId) {
|
||||||
|
toPrint += `- AfkChannel:\n${oldGuild.afkChannelId ? channelMention(oldGuild.afkChannelId) : 'Not defined'} => ${newGuild.afkChannelId ? channelMention(newGuild.afkChannelId) : 'Not defined'}\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.afkTimeout !== newGuild.afkTimeout) {
|
||||||
|
toPrint += `- Timeout:\n\`${oldGuild.afkTimeout / 60}m\` => \`${newGuild.afkTimeout / 60}m\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.preferredLocale !== newGuild.preferredLocale) {
|
||||||
|
toPrint += `- Language:\n\`${oldGuild.preferredLocale}\` => \`${newGuild.preferredLocale}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.verificationLevel !== newGuild.verificationLevel) {
|
||||||
|
toPrint += `- Verification:\n\`${verificationLevels[oldGuild.verificationLevel]}\` => \`${verificationLevels[newGuild.verificationLevel]}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.explicitContentFilter !== newGuild.explicitContentFilter) {
|
||||||
|
toPrint += `- Filter:\n\`${explicitContentLevels[oldGuild.explicitContentFilter]}\` => \`${explicitContentLevels[newGuild.explicitContentFilter]}\`\n`;
|
||||||
|
}
|
||||||
|
if (oldGuild.premiumTier !== newGuild.premiumTier) {
|
||||||
|
toPrint += `- Filter:\n\`${oldGuild.premiumTier}\` => \`${newGuild.premiumTier}\`\n`;
|
||||||
|
}
|
||||||
|
const toRep = new EmbedBuilder()
|
||||||
|
.setColor(`${guildData.color}`)
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
})
|
||||||
|
.setDescription(`${toPrint}`);
|
||||||
|
logChannel.send({
|
||||||
|
embeds: [
|
||||||
|
toRep,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Events, MessageFlags } from 'discord.js';
|
import { CommandInteraction, Events, Interaction, MessageFlags } from 'discord.js';
|
||||||
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
import emoji from '../../../assets/emoji.json' assert { type: 'json' };
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: Events.InteractionCreate,
|
name: Events.InteractionCreate,
|
||||||
async execute(interaction) {
|
async execute(interaction: Interaction) {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (!interaction.isChatInputCommand()) return;
|
||||||
const command = interaction.client.commands.get(interaction.commandName);
|
const command: CommandInteraction = interaction.client.commands.get(interaction.commandName);
|
||||||
if (!command) {
|
if (!command) {
|
||||||
console.error(`⚠️ | Can't execute ${interaction.commandName}`);
|
console.error(`⚠️ | Can't execute ${interaction.commandName}`);
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
|
|
|
||||||
60
src/events/messages/messageBulkDelete.ts
Normal file
60
src/events/messages/messageBulkDelete.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { Events, EmbedBuilder, Message, Channel, Collection, Snowflake, PartialMessage } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.MessageBulkDelete,
|
||||||
|
async execute(messages: Collection<Snowflake, Message | PartialMessage>) {
|
||||||
|
const message: Message = messages.first();
|
||||||
|
const guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: message.guildId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let description: string = '';
|
||||||
|
for (const [, msg] of messages) {
|
||||||
|
let fullMsg = msg;
|
||||||
|
if (msg.partial) {
|
||||||
|
try {
|
||||||
|
fullMsg = await msg.fetch();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
console.warn('BulkDelete cannot load a message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
description += `**${fullMsg.author?.username ?? 'Unknown'}**: ${fullMsg.content || '[no content]'}\n`;
|
||||||
|
}
|
||||||
|
if (guildData.logMsg) {
|
||||||
|
const log = new EmbedBuilder()
|
||||||
|
.setAuthor({
|
||||||
|
name: `${message.author.tag} (${message.author.id})`,
|
||||||
|
iconURL: message.author.displayAvatarURL({
|
||||||
|
size: 2048,
|
||||||
|
extension: 'png',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.setTitle('🚯 | Message Cleared')
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
})
|
||||||
|
.setDescription(`
|
||||||
|
__Channel:__ ${message.channel}
|
||||||
|
__Number:__ ${messages.size}
|
||||||
|
__Content:__
|
||||||
|
${description}
|
||||||
|
`);
|
||||||
|
const logChannel: Promise<Channel | null> = await message.guild.client.channels
|
||||||
|
.fetch(guildData.logMsg)
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
if (logChannel) {
|
||||||
|
logChannel.send({
|
||||||
|
embeds: [
|
||||||
|
log,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
73
src/events/messages/messageCreate.ts
Normal file
73
src/events/messages/messageCreate.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { Events, Message } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { User as UserPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
const xpCooldown: Map<string, number> = new Map<string, number>();
|
||||||
|
|
||||||
|
function canGainXp(userId: string): boolean {
|
||||||
|
const now: number = Date.now();
|
||||||
|
const last: number = xpCooldown.get(userId) ?? 0;
|
||||||
|
if (now - last < 60_000) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
xpCooldown.set(userId, now);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.MessageCreate,
|
||||||
|
async execute(message: Message) {
|
||||||
|
if (message.author.bot || !message.guildId || !canGainXp(message.author.id)) return;
|
||||||
|
const Author: UserPrisma = await prisma.user.findUnique({
|
||||||
|
where: { id: message.author.id },
|
||||||
|
});
|
||||||
|
if (!Author) {
|
||||||
|
await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
id: message.author.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let guildUser = await prisma.guildUser.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_guildId: {
|
||||||
|
userId: message.author.id,
|
||||||
|
guildId: message.guildId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!guildUser) {
|
||||||
|
guildUser = await prisma.guildUser.create({
|
||||||
|
data: {
|
||||||
|
userId: message.author.id,
|
||||||
|
guildId: message.guildId,
|
||||||
|
xp: 0,
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const gainXp: number = Math.abs(message.content.length - Math.round(Math.random() * 13)) % 7;
|
||||||
|
const newXp: number = guildUser.xp + gainXp;
|
||||||
|
let newLevel: number = guildUser.level;
|
||||||
|
const requiredXp: number = 5 * (newLevel ** 2) + 50 * newLevel + 100;
|
||||||
|
if (newXp >= requiredXp) {
|
||||||
|
newLevel++;
|
||||||
|
await message.channel.send(
|
||||||
|
`🎉 | Félicitations ${message.author}, tu es maintenant niveau **${newLevel}** !`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(`${message.author.username} | ${newLevel} -> ${newXp} [${requiredXp}]`);
|
||||||
|
await prisma.guildUser.update({
|
||||||
|
where: {
|
||||||
|
userId_guildId: {
|
||||||
|
userId: message.author.id,
|
||||||
|
guildId: message.guildId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
xp: newXp,
|
||||||
|
level: newLevel,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
43
src/events/messages/messageDelete.ts
Normal file
43
src/events/messages/messageDelete.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Events, EmbedBuilder, Message, Channel } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.MessageDelete,
|
||||||
|
async execute(message: Message) {
|
||||||
|
const guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: message.guildId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (guildData.logMsg) {
|
||||||
|
const log = new EmbedBuilder()
|
||||||
|
.setAuthor({
|
||||||
|
name: `${message.author.tag} (${message.author.id})`,
|
||||||
|
iconURL: message.author.displayAvatarURL({
|
||||||
|
size: 2048,
|
||||||
|
extension: 'png',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.setTitle('🗑️ | Message Deleted')
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
})
|
||||||
|
.setDescription(`
|
||||||
|
Channel: ${message.channel}
|
||||||
|
Content: ${message.content ? message.content : '*enable to load the content*'}
|
||||||
|
`);
|
||||||
|
const logChannel: Promise<Channel | null> = await message.guild.client.channels
|
||||||
|
.fetch(guildData.logMsg)
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
if (logChannel) {
|
||||||
|
logChannel.send({
|
||||||
|
embeds: [
|
||||||
|
log,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
44
src/events/messages/messageUpdate.ts
Normal file
44
src/events/messages/messageUpdate.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Events, EmbedBuilder, Message, Channel } from 'discord.js';
|
||||||
|
import { prisma } from '../../lib/prisma.ts';
|
||||||
|
import { Guild as GuildPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: Events.MessageUpdate,
|
||||||
|
async execute(oldMessage:Message, newMessage: Message) {
|
||||||
|
const guildData: GuildPrisma = await prisma.guild.findUnique({
|
||||||
|
where: {
|
||||||
|
id: oldMessage.guildId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (guildData.logMsg) {
|
||||||
|
const log = new EmbedBuilder()
|
||||||
|
.setAuthor({
|
||||||
|
name: `${newMessage.author.tag} (${newMessage.author.id})`,
|
||||||
|
iconURL: newMessage.author.displayAvatarURL({
|
||||||
|
size: 2048,
|
||||||
|
extension: 'png',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.setTitle('✏️ | Message Edited')
|
||||||
|
.setColor(guildData.color)
|
||||||
|
.setFooter({
|
||||||
|
text: guildData.footer,
|
||||||
|
})
|
||||||
|
.setDescription(`
|
||||||
|
Channel: ${newMessage.channel}
|
||||||
|
Before: ${oldMessage.content}
|
||||||
|
After: ${newMessage.content}
|
||||||
|
`);
|
||||||
|
const logChannel: Promise<Channel | null> = await newMessage.guild.client.channels
|
||||||
|
.fetch(guildData.logMsg)
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
if (logChannel) {
|
||||||
|
logChannel.send({
|
||||||
|
embeds: [
|
||||||
|
log,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -22,15 +22,13 @@ for (const folder of commandFolders) {
|
||||||
.filter((file) => file.endsWith('.ts') || file.endsWith('.js'));
|
.filter((file) => file.endsWith('.ts') || file.endsWith('.js'));
|
||||||
for (const file of commandFiles) {
|
for (const file of commandFiles) {
|
||||||
const filesPath = path.join(commandsPath, file);
|
const filesPath = path.join(commandsPath, file);
|
||||||
const commandModule = await import(filesPath);
|
const commandModule: unknown = await import(filesPath);
|
||||||
const command = commandModule.default || commandModule;
|
const command: unknown = commandModule.default || commandModule;
|
||||||
if ('data' in command && 'execute' in command) {
|
try {
|
||||||
commands.push(command.data.toJSON());
|
commands.push(command.data.toJSON());
|
||||||
}
|
}
|
||||||
else {
|
catch (err) {
|
||||||
console.log(
|
console.error(err);
|
||||||
'⚠️ | A Command is missing a required "data" or "execute" property.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
src/lib/mention.ts
Normal file
16
src/lib/mention.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
export function getCorrectMention(guild: Guild, id: string): string {
|
||||||
|
if (id === guild.id) {
|
||||||
|
return '@everyone';
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = guild.roles.cache.get(id);
|
||||||
|
if (role) {
|
||||||
|
return `<@&${id}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = guild.members.cache.get(id);
|
||||||
|
if (member) {
|
||||||
|
return `<@${id}>`;
|
||||||
|
}
|
||||||
|
return `Unknown (${id})`;
|
||||||
|
}
|
||||||
26
src/lib/perm.ts
Normal file
26
src/lib/perm.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { prisma } from '../lib/prisma';
|
||||||
|
import { User as UserPrisma } from '@prisma/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userId - Discord identifier for the user
|
||||||
|
* @param guildId - Discord identifier for the guild
|
||||||
|
* @returns true if the user is whitelisted flase overwise
|
||||||
|
*/
|
||||||
|
export async function isWhitelisted(userId: string, guildId: string): Promise<boolean> {
|
||||||
|
const userData: UserPrisma = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const count: number = await prisma.user.count({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
WhitelistedGuilds: {
|
||||||
|
some: {
|
||||||
|
id: guildId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (userData.isOwner || userData.isBuyer || count != 0);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue