🚀 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
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}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue