fix: resolve role selection limits, uncached message crashes, and optimize settings caching
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 19s
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 19s
This commit is contained in:
@@ -144,3 +144,6 @@ if (process.env.AUTO_DEPLOY !== 'false') {
|
|||||||
2. **Foreign Keys:** Always enable `foreign_keys = ON` to maintain data integrity.
|
2. **Foreign Keys:** Always enable `foreign_keys = ON` to maintain data integrity.
|
||||||
3. **Type Safety:** Use `ExtendedClient` instead of `any` for the client instance.
|
3. **Type Safety:** Use `ExtendedClient` instead of `any` for the client instance.
|
||||||
4. **Webhook Reuse:** Map `WebhookClient` instances to their monitor keys to prevent leaks.
|
4. **Webhook Reuse:** Map `WebhookClient` instances to their monitor keys to prevent leaks.
|
||||||
|
5. **Partial Message Handling:** In `messageDelete` and `messageUpdate` events, always check for null `author` property to avoid runtime crashes on uncached partial messages.
|
||||||
|
6. **Centralized Settings Cache:** Always use `client.DB.getSettings(guildId)` instead of raw SQLite select queries for `guild_settings` to leverage central caching and automatic default generation.
|
||||||
|
7. **Category Role Selection Limits:** Do not count general guild roles when checking role selection limits; filter the user's role list using only the specific role IDs registered under that category.
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import { ExtendedClient } from '../../structures/ExtendedClient.js';
|
|||||||
|
|
||||||
async function sendModerationLog(client: ExtendedClient, guildId: string, event: string, embed: EmbedBuilder) {
|
async function sendModerationLog(client: ExtendedClient, guildId: string, event: string, embed: EmbedBuilder) {
|
||||||
const DB = client.DB;
|
const DB = client.DB;
|
||||||
const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId);
|
const settings = DB.getSettings(guildId);
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
if (!settings?.log_events) return;
|
if (!settings.log_events) return;
|
||||||
const activeEvents = settings.log_events.split(',').filter(Boolean);
|
const activeEvents = settings.log_events.split(',').filter(Boolean);
|
||||||
if (!activeEvents.includes(event)) return;
|
if (!activeEvents.includes(event)) return;
|
||||||
|
|
||||||
const logSettings: any = DB.get('SELECT log_channel FROM guild_settings WHERE guild_id = ?', guildId);
|
if (!settings.log_channel) return;
|
||||||
if (!logSettings?.log_channel) return;
|
|
||||||
|
|
||||||
const channel = client.guilds.cache.get(guildId)?.channels.cache.get(logSettings.log_channel);
|
const channel = client.guilds.cache.get(guildId)?.channels.cache.get(settings.log_channel);
|
||||||
if (!channel || !channel.isTextBased()) return;
|
if (!channel || !channel.isTextBased()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -227,11 +227,7 @@ const command: Command = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get or create guild settings
|
// Get or create guild settings
|
||||||
let settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId);
|
const settings = DB.getSettings(guildId);
|
||||||
if (!settings) {
|
|
||||||
DB.run('INSERT INTO guild_settings (guild_id) VALUES (?)', guildId);
|
|
||||||
settings = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subcommand === 'kick') {
|
if (subcommand === 'kick') {
|
||||||
const target = interaction.options.getMember('target') as GuildMember;
|
const target = interaction.options.getMember('target') as GuildMember;
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ const command: Command = {
|
|||||||
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers);
|
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers);
|
||||||
const DB = interaction.client.DB;
|
const DB = interaction.client.DB;
|
||||||
|
|
||||||
// Ensure guild settings exist
|
|
||||||
DB.run('INSERT OR IGNORE INTO guild_settings (guild_id) VALUES (?)', guildId);
|
|
||||||
|
|
||||||
if (subcommand === 'help') {
|
if (subcommand === 'help') {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
@@ -99,7 +97,7 @@ const command: Command = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand === 'list') {
|
if (subcommand === 'list') {
|
||||||
const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId);
|
const settings = DB.getSettings(guildId);
|
||||||
|
|
||||||
const channel = settings?.log_channel
|
const channel = settings?.log_channel
|
||||||
? `<#${settings.log_channel}>`
|
? `<#${settings.log_channel}>`
|
||||||
@@ -128,7 +126,7 @@ const command: Command = {
|
|||||||
|
|
||||||
if (subcommand === 'enable') {
|
if (subcommand === 'enable') {
|
||||||
const event = interaction.options.getString('event')!;
|
const event = interaction.options.getString('event')!;
|
||||||
const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId);
|
const settings = DB.getSettings(guildId);
|
||||||
|
|
||||||
const currentEvents = (settings?.log_events || '').split(',').filter(Boolean);
|
const currentEvents = (settings?.log_events || '').split(',').filter(Boolean);
|
||||||
|
|
||||||
@@ -151,7 +149,7 @@ const command: Command = {
|
|||||||
|
|
||||||
if (subcommand === 'disable') {
|
if (subcommand === 'disable') {
|
||||||
const event = interaction.options.getString('event')!;
|
const event = interaction.options.getString('event')!;
|
||||||
const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId);
|
const settings = DB.getSettings(guildId);
|
||||||
|
|
||||||
const currentEvents = (settings?.log_events || '').split(',').filter(Boolean);
|
const currentEvents = (settings?.log_events || '').split(',').filter(Boolean);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, ChannelType, ComponentType, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from 'discord.js';
|
import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, ChannelType, ComponentType, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from 'discord.js';
|
||||||
import { Command } from '../../structures/Command.js';
|
import { Command } from '../../structures/Command.js';
|
||||||
|
|
||||||
async function updateRoleMessage(client: any, category: any) {
|
export async function updateRoleMessage(client: any, category: any) {
|
||||||
try {
|
try {
|
||||||
const channel = client.guilds.cache.get(category.guild_id)?.channels.cache.get(category.channel_id);
|
const channel = client.guilds.cache.get(category.guild_id)?.channels.cache.get(category.channel_id);
|
||||||
if (!channel) return;
|
if (!channel) return;
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ const command: Command = {
|
|||||||
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers);
|
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers);
|
||||||
const DB = interaction.client.DB;
|
const DB = interaction.client.DB;
|
||||||
|
|
||||||
// Ensure guild settings exist
|
|
||||||
DB.run('INSERT OR IGNORE INTO guild_settings (guild_id) VALUES (?)', guildId);
|
|
||||||
|
|
||||||
if (subcommand === 'help') {
|
if (subcommand === 'help') {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
@@ -80,7 +78,7 @@ const command: Command = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand === 'list') {
|
if (subcommand === 'list') {
|
||||||
const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId);
|
const settings = DB.getSettings(guildId);
|
||||||
|
|
||||||
const channel = settings?.welcome_channel
|
const channel = settings?.welcome_channel
|
||||||
? `<#${settings.welcome_channel}>`
|
? `<#${settings.welcome_channel}>`
|
||||||
|
|||||||
@@ -99,8 +99,9 @@ export default {
|
|||||||
ephemeral: true
|
ephemeral: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const userRoles = member.roles.cache.filter((r: any) => !r.managed && r.id !== guild.id).size;
|
const options: any[] = client.DB.all('SELECT * FROM role_options WHERE category_id = ?', category.id);
|
||||||
if (category.max_roles > 0 && userRoles >= category.max_roles) {
|
const userCategoryRoles = options.filter((opt: any) => member.roles.cache.has(opt.role_id)).length;
|
||||||
|
if (category.max_roles > 0 && userCategoryRoles >= category.max_roles) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: `Du kannst maximal ${category.max_roles} Rollen aus dieser Kategorie haben.`,
|
content: `Du kannst maximal ${category.max_roles} Rollen aus dieser Kategorie haben.`,
|
||||||
ephemeral: true
|
ephemeral: true
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async function sendLog(client: any, guildId: string, event: string, embed: Embed
|
|||||||
export default {
|
export default {
|
||||||
name: Events.MessageDelete,
|
name: Events.MessageDelete,
|
||||||
async execute(message: Message) {
|
async execute(message: Message) {
|
||||||
if (!message.guild || message.author.bot) return;
|
if (!message.guild || !message.author || message.author.bot) return;
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('🗑️ Nachricht gelöscht')
|
.setTitle('🗑️ Nachricht gelöscht')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async function sendLog(client: any, guildId: string, event: string, embed: Embed
|
|||||||
export default {
|
export default {
|
||||||
name: Events.MessageUpdate,
|
name: Events.MessageUpdate,
|
||||||
async execute(oldMessage: Message, newMessage: Message) {
|
async execute(oldMessage: Message, newMessage: Message) {
|
||||||
if (!newMessage.guild || newMessage.author?.bot) return;
|
if (!newMessage.guild || !newMessage.author || newMessage.author.bot) return;
|
||||||
if (!oldMessage.content || !newMessage.content) return;
|
if (!oldMessage.content || !newMessage.content) return;
|
||||||
if (oldMessage.content === newMessage.content) return;
|
if (oldMessage.content === newMessage.content) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Events } from 'discord.js';
|
import { Events } from 'discord.js';
|
||||||
import { TwitchMonitor } from '../structures/TwitchMonitor.js';
|
import { TwitchMonitor } from '../structures/TwitchMonitor.js';
|
||||||
import { ExtendedClient } from '../structures/ExtendedClient.js';
|
import { ExtendedClient } from '../structures/ExtendedClient.js';
|
||||||
|
import { updateRoleMessage } from '../commands/utility/rolesetup.js';
|
||||||
|
|
||||||
async function validateRoles(client: ExtendedClient) {
|
async function validateRoles(client: ExtendedClient) {
|
||||||
const categories: any[] = client.DB.all('SELECT * FROM role_categories');
|
const categories: any[] = client.DB.all('SELECT * FROM role_categories');
|
||||||
@@ -12,6 +13,7 @@ async function validateRoles(client: ExtendedClient) {
|
|||||||
|
|
||||||
const options: any[] = client.DB.all('SELECT * FROM role_options WHERE category_id = ?', category.id);
|
const options: any[] = client.DB.all('SELECT * FROM role_options WHERE category_id = ?', category.id);
|
||||||
const validOptions: any[] = [];
|
const validOptions: any[] = [];
|
||||||
|
let categoryUpdated = false;
|
||||||
|
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
const roleExists = guild.roles.cache.has(option.role_id);
|
const roleExists = guild.roles.cache.has(option.role_id);
|
||||||
@@ -24,10 +26,16 @@ async function validateRoles(client: ExtendedClient) {
|
|||||||
await member.roles.remove(option.role_id).catch(() => null);
|
await member.roles.remove(option.role_id).catch(() => null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
client.DB.run('DELETE FROM role_options WHERE id = ?', option.id);
|
||||||
|
categoryUpdated = true;
|
||||||
removedCount++;
|
removedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (categoryUpdated) {
|
||||||
|
await updateRoleMessage(client, category);
|
||||||
|
}
|
||||||
|
|
||||||
if (validOptions.length === 0 && options.length > 0) {
|
if (validOptions.length === 0 && options.length > 0) {
|
||||||
console.log(`[ROLESETUP] Category "${category.category_name}" has no valid roles left. Consider deleting it.`);
|
console.log(`[ROLESETUP] Category "${category.category_name}" has no valid roles left. Consider deleting it.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,14 +172,18 @@ export class DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimized getter for guild settings with caching
|
* Optimized getter for guild settings with caching (automatically creates settings if missing)
|
||||||
*/
|
*/
|
||||||
static getSettings(guildId: string): any {
|
static getSettings(guildId: string): any {
|
||||||
if (this.settingsCache.has(guildId)) {
|
if (this.settingsCache.has(guildId)) {
|
||||||
return this.settingsCache.get(guildId);
|
return this.settingsCache.get(guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = db.prepare('SELECT * FROM guild_settings WHERE guild_id = ?').get(guildId);
|
let settings = db.prepare('SELECT * FROM guild_settings WHERE guild_id = ?').get(guildId);
|
||||||
|
if (!settings) {
|
||||||
|
db.prepare('INSERT OR IGNORE INTO guild_settings (guild_id) VALUES (?)').run(guildId);
|
||||||
|
settings = db.prepare('SELECT * FROM guild_settings WHERE guild_id = ?').get(guildId);
|
||||||
|
}
|
||||||
if (settings) {
|
if (settings) {
|
||||||
this.settingsCache.set(guildId, settings);
|
this.settingsCache.set(guildId, settings);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user