[deploy] Automod logging reasons und Modul-Fix
All checks were successful
Deploy Discord Bot / deploy (push) Successful in 38s
All checks were successful
Deploy Discord Bot / deploy (push) Successful in 38s
This commit is contained in:
@@ -23,7 +23,7 @@ const command: SlashCommand = {
|
|||||||
|
|
||||||
await member.ban({ reason }).catch(() => null);
|
await member.ban({ reason }).catch(() => null);
|
||||||
await interaction.reply({ content: `${user.tag} wurde gebannt. Grund: ${reason}` });
|
await interaction.reply({ content: `${user.tag} wurde gebannt. Grund: ${reason}` });
|
||||||
context.logging.logAction(user, 'Ban', reason);
|
context.logging.logAction(user, 'Ban', reason, interaction.guild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const command: SlashCommand = {
|
|||||||
}
|
}
|
||||||
await member.kick(reason);
|
await member.kick(reason);
|
||||||
await interaction.reply({ content: `${user.tag} wurde gekickt.` });
|
await interaction.reply({ content: `${user.tag} wurde gekickt.` });
|
||||||
context.logging.logAction(user, 'Kick', reason);
|
context.logging.logAction(user, 'Kick', reason, interaction.guild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const command: SlashCommand = {
|
|||||||
}
|
}
|
||||||
await member.timeout(minutes * 60 * 1000, reason).catch(() => null);
|
await member.timeout(minutes * 60 * 1000, reason).catch(() => null);
|
||||||
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten gemutet.` });
|
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten gemutet.` });
|
||||||
context.logging.logAction(user, 'Mute', reason);
|
context.logging.logAction(user, 'Mute', reason, interaction.guild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const command: SlashCommand = {
|
|||||||
|
|
||||||
await member.ban({ reason: `${reason} | ${minutes} Minuten` });
|
await member.ban({ reason: `${reason} | ${minutes} Minuten` });
|
||||||
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten gebannt.` });
|
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten gebannt.` });
|
||||||
context.logging.logAction(user, 'Tempban', reason);
|
context.logging.logAction(user, 'Tempban', reason, interaction.guild);
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await interaction.guild?.members.unban(user.id, 'Tempban abgelaufen').catch(() => null);
|
await interaction.guild?.members.unban(user.id, 'Tempban abgelaufen').catch(() => null);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const command: SlashCommand = {
|
|||||||
}
|
}
|
||||||
await member.timeout(minutes * 60 * 1000, reason).catch(() => null);
|
await member.timeout(minutes * 60 * 1000, reason).catch(() => null);
|
||||||
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten in Timeout gesetzt.` });
|
await interaction.reply({ content: `${user.tag} wurde für ${minutes} Minuten in Timeout gesetzt.` });
|
||||||
context.logging.logAction(user, 'Timeout', reason);
|
context.logging.logAction(user, 'Timeout', reason, interaction.guild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const command: SlashCommand = {
|
|||||||
}
|
}
|
||||||
await member.timeout(null).catch(() => null);
|
await member.timeout(null).catch(() => null);
|
||||||
await interaction.reply({ content: `${user.tag} ist nun entmuted.` });
|
await interaction.reply({ content: `${user.tag} ist nun entmuted.` });
|
||||||
context.logging.logAction(user, 'Unmute');
|
context.logging.logAction(user, 'Unmute', undefined, interaction.guild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ import { KnowledgeBaseService } from '../services/knowledgeBaseService';
|
|||||||
import { RegisterService } from '../services/registerService';
|
import { RegisterService } from '../services/registerService';
|
||||||
import { StatsService } from '../services/statsService';
|
import { StatsService } from '../services/statsService';
|
||||||
|
|
||||||
|
const logging = new LoggingService();
|
||||||
|
|
||||||
export const context = {
|
export const context = {
|
||||||
client: null as Client | null,
|
client: null as Client | null,
|
||||||
commandHandler: null as CommandHandler | null,
|
commandHandler: null as CommandHandler | null,
|
||||||
automod: new AutoModService(true, true),
|
logging,
|
||||||
logging: new LoggingService(),
|
automod: new AutoModService(logging, true, true),
|
||||||
music: new MusicService(),
|
music: new MusicService(),
|
||||||
tickets: new TicketService(),
|
tickets: new TicketService(),
|
||||||
leveling: new LevelService(),
|
leveling: new LevelService(),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { context } from '../config/context';
|
|||||||
const event: EventHandler = {
|
const event: EventHandler = {
|
||||||
name: 'guildBanAdd',
|
name: 'guildBanAdd',
|
||||||
execute(ban: GuildBan) {
|
execute(ban: GuildBan) {
|
||||||
context.logging.logAction(ban.user, 'Ban');
|
context.logging.logAction(ban.user, 'Ban', undefined, ban.guild);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const event: EventHandler = {
|
|||||||
async execute(message: Message) {
|
async execute(message: Message) {
|
||||||
const cfg = message.guildId ? settingsStore.get(message.guildId) : undefined;
|
const cfg = message.guildId ? settingsStore.get(message.guildId) : undefined;
|
||||||
if (message.guildId) context.admin.trackEvent('message', message.guildId);
|
if (message.guildId) context.admin.trackEvent('message', message.guildId);
|
||||||
if (cfg?.automodEnabled === true) context.automod.checkMessage(message, cfg.automodConfig);
|
if (cfg?.automodEnabled === true) context.automod.checkMessage(message, cfg);
|
||||||
if (cfg?.levelingEnabled === true) context.leveling.handleMessage(message);
|
if (cfg?.levelingEnabled === true) context.leveling.handleMessage(message);
|
||||||
// Ticket SLA + KB
|
// Ticket SLA + KB
|
||||||
await context.tickets.trackFirstResponse(message);
|
await context.tickets.trackFirstResponse(message);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Collection, Message, PermissionFlagsBits } from 'discord.js';
|
import { Collection, Message, PermissionFlagsBits } from 'discord.js';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
import { GuildSettings } from '../config/state';
|
||||||
|
import { LoggingService } from './loggingService';
|
||||||
|
|
||||||
export interface AutomodConfig {
|
export interface AutomodConfig {
|
||||||
spamThreshold?: number;
|
spamThreshold?: number;
|
||||||
@@ -37,11 +39,13 @@ export class AutoModService {
|
|||||||
};
|
};
|
||||||
private defaultBadwords = ['badword', 'spamword'];
|
private defaultBadwords = ['badword', 'spamword'];
|
||||||
|
|
||||||
constructor(private linkFilterEnabled = true, private antiSpamEnabled = true) {}
|
constructor(private logging?: LoggingService, private linkFilterEnabled = true, private antiSpamEnabled = true) {}
|
||||||
|
|
||||||
public async checkMessage(message: Message, cfg?: AutomodConfig) {
|
public async checkMessage(message: Message, cfg?: AutomodConfig | GuildSettings) {
|
||||||
if (message.author.bot) return;
|
if (message.author.bot || message.webhookId) return;
|
||||||
const config = { ...this.defaults, ...(cfg ?? {}) };
|
if (!message.inGuild()) return;
|
||||||
|
const guildConfig = (cfg as GuildSettings)?.automodConfig ? (cfg as GuildSettings).automodConfig : cfg;
|
||||||
|
const config = { ...this.defaults, ...(guildConfig ?? {}) };
|
||||||
const member = message.member;
|
const member = message.member;
|
||||||
|
|
||||||
if (member?.roles.cache.size && Array.isArray(config.whitelistRoles) && config.whitelistRoles.length) {
|
if (member?.roles.cache.size && Array.isArray(config.whitelistRoles) && config.whitelistRoles.length) {
|
||||||
@@ -51,22 +55,17 @@ export class AutoModService {
|
|||||||
|
|
||||||
if (this.linkFilterEnabled && config.deleteLinks !== false && this.containsLink(message.content, config.linkWhitelist)) {
|
if (this.linkFilterEnabled && config.deleteLinks !== false && this.containsLink(message.content, config.linkWhitelist)) {
|
||||||
if (message.member?.permissions.has(PermissionFlagsBits.ManageGuild)) return false;
|
if (message.member?.permissions.has(PermissionFlagsBits.ManageGuild)) return false;
|
||||||
message.delete().catch(() => undefined);
|
await this.deleteMessageWithReason(message, `${message.author}, Links sind hier nicht erlaubt.`);
|
||||||
message.channel
|
const reason = `Link gefunden (nicht freigegeben)${config.linkWhitelist?.length ? ` | Whitelist: ${config.linkWhitelist.join(', ')}` : ''}`;
|
||||||
.send({ content: `${message.author}, Links sind hier nicht erlaubt.` })
|
|
||||||
.then((m) => setTimeout(() => m.delete().catch(() => undefined), 5000));
|
|
||||||
logger.info(`Deleted link from ${message.author.tag}`);
|
logger.info(`Deleted link from ${message.author.tag}`);
|
||||||
await this.logAutomodAction(message, config, 'link_filter');
|
await this.logAutomodAction(message, config, 'link_filter', reason);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.badWordFilter !== false && this.containsBadword(message.content, config.customBadwords)) {
|
if (config.badWordFilter !== false && this.containsBadword(message.content, config.customBadwords)) {
|
||||||
if (message.member?.permissions.has(PermissionFlagsBits.ManageGuild)) return false;
|
if (message.member?.permissions.has(PermissionFlagsBits.ManageGuild)) return false;
|
||||||
message.delete().catch(() => undefined);
|
await this.deleteMessageWithReason(message, `${message.author}, bitte auf deine Wortwahl achten.`);
|
||||||
message.channel
|
await this.logAutomodAction(message, config, 'badword', 'Badword erkannt', message.content);
|
||||||
.send({ content: `${message.author}, bitte auf deine Wortwahl achten.` })
|
|
||||||
.then((m) => setTimeout(() => m.delete().catch(() => undefined), 5000));
|
|
||||||
await this.logAutomodAction(message, config, 'badword', message.content);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +73,9 @@ export class AutoModService {
|
|||||||
const letters = message.content.replace(/[^a-zA-Z]/g, '');
|
const letters = message.content.replace(/[^a-zA-Z]/g, '');
|
||||||
const upper = letters.replace(/[^A-Z]/g, '');
|
const upper = letters.replace(/[^A-Z]/g, '');
|
||||||
if (letters.length >= 10 && upper.length / letters.length > 0.7) {
|
if (letters.length >= 10 && upper.length / letters.length > 0.7) {
|
||||||
message.delete().catch(() => undefined);
|
await this.deleteMessageWithReason(message, `${message.author}, bitte weniger Capslock nutzen.`);
|
||||||
message.channel
|
const ratio = Math.round((upper.length / letters.length) * 100);
|
||||||
.send({ content: `${message.author}, bitte weniger Capslock nutzen.` })
|
await this.logAutomodAction(message, config, 'capslock', `Caps Anteil ${ratio}%`, message.content);
|
||||||
.then((m) => setTimeout(() => m.delete().catch(() => undefined), 5000));
|
|
||||||
await this.logAutomodAction(message, config, 'capslock', message.content);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,12 +95,11 @@ export class AutoModService {
|
|||||||
if (tracker.count >= threshold) {
|
if (tracker.count >= threshold) {
|
||||||
const timeoutMs = (config.spamTimeoutMinutes ?? this.defaults.spamTimeoutMinutes!) * 60 * 1000;
|
const timeoutMs = (config.spamTimeoutMinutes ?? this.defaults.spamTimeoutMinutes!) * 60 * 1000;
|
||||||
message.member?.timeout(timeoutMs, 'Automod: Spam').catch(() => undefined);
|
message.member?.timeout(timeoutMs, 'Automod: Spam').catch(() => undefined);
|
||||||
message.channel
|
await this.deleteMessageWithReason(message, `${message.author}, bitte langsamer schreiben (Spam-Schutz).`);
|
||||||
.send({ content: `${message.author}, bitte langsamer schreiben (Spam-Schutz).` })
|
|
||||||
.then((m) => setTimeout(() => m.delete().catch(() => undefined), 5000));
|
|
||||||
logger.warn(`Timed out ${message.author.tag} for spam`);
|
logger.warn(`Timed out ${message.author.tag} for spam`);
|
||||||
this.spamTracker.delete(message.author.id);
|
this.spamTracker.delete(message.author.id);
|
||||||
await this.logAutomodAction(message, config, 'spam', `Count ${tracker.count}`);
|
const reason = `Spam erkannt (${tracker.count}/${threshold} Nachrichten innerhalb ${config.windowMs ?? this.windowMs}ms)`;
|
||||||
|
await this.logAutomodAction(message, config, 'spam', reason);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,10 +121,30 @@ export class AutoModService {
|
|||||||
return !normalized.some((w) => url.includes(w));
|
return !normalized.some((w) => url.includes(w));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async logAutomodAction(message: Message, config: AutomodConfig, action: string, details?: string) {
|
private async deleteMessageWithReason(message: Message, response: string) {
|
||||||
|
await message.delete().catch(() => undefined);
|
||||||
|
await message.channel
|
||||||
|
.send({ content: response })
|
||||||
|
.then((m) => setTimeout(() => m.delete().catch(() => undefined), 5000))
|
||||||
|
.catch(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async logAutomodAction(message: Message, config: AutomodConfig, action: string, reason: string, content?: string) {
|
||||||
try {
|
try {
|
||||||
const guild = message.guild;
|
const guild = message.guild;
|
||||||
if (!guild) return;
|
if (!guild) return;
|
||||||
|
if (this.logging) {
|
||||||
|
this.logging.logAutomodAction(guild, {
|
||||||
|
userTag: message.author.tag,
|
||||||
|
userId: message.author.id,
|
||||||
|
action,
|
||||||
|
reason,
|
||||||
|
content,
|
||||||
|
channel: guild.channels.cache.get(message.channelId) ?? null,
|
||||||
|
messageUrl: message.url
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const loggingCfg = config.loggingConfig || {};
|
const loggingCfg = config.loggingConfig || {};
|
||||||
const flags = loggingCfg.categories || {};
|
const flags = loggingCfg.categories || {};
|
||||||
if (flags.automodActions === false) return;
|
if (flags.automodActions === false) return;
|
||||||
@@ -136,8 +152,8 @@ export class AutoModService {
|
|||||||
if (!channelId) return;
|
if (!channelId) return;
|
||||||
const channel = await guild.channels.fetch(channelId).catch(() => null);
|
const channel = await guild.channels.fetch(channelId).catch(() => null);
|
||||||
if (!channel || !channel.isTextBased()) return;
|
if (!channel || !channel.isTextBased()) return;
|
||||||
const content = `[Automod] ${action} by ${message.author.tag}${details ? ` | ${details}` : ''}`;
|
const body = `[Automod] ${action} by ${message.author.tag} | ${reason}${content ? ` | ${content.slice(0, 1800)}` : ''}`;
|
||||||
await channel.send({ content });
|
await channel.send({ content: body });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Automod log failed', err);
|
logger.error('Automod log failed', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class LoggingService {
|
|||||||
private resolve(guild: Guild) {
|
private resolve(guild: Guild) {
|
||||||
const cfg = settingsStore.get(guild.id);
|
const cfg = settingsStore.get(guild.id);
|
||||||
const loggingCfg = cfg?.loggingConfig || cfg?.automodConfig?.loggingConfig || {};
|
const loggingCfg = cfg?.loggingConfig || cfg?.automodConfig?.loggingConfig || {};
|
||||||
const logChannelId = loggingCfg.logChannelId || cfg?.logChannelId || this.fallbackLogChannelId;
|
const logChannelId = loggingCfg.logChannelId || cfg?.automodConfig?.logChannelId || cfg?.logChannelId || this.fallbackLogChannelId;
|
||||||
const flags = loggingCfg.categories || {};
|
const flags = loggingCfg.categories || {};
|
||||||
const channel = logChannelId ? guild.channels.cache.get(logChannelId) : null;
|
const channel = logChannelId ? guild.channels.cache.get(logChannelId) : null;
|
||||||
return { channel: channel && channel.type === 0 ? (channel as TextChannel) : null, flags };
|
return { channel: channel && channel.type === 0 ? (channel as TextChannel) : null, flags };
|
||||||
@@ -128,11 +128,11 @@ export class LoggingService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logAction(user: User, action: string, reason?: string) {
|
logAction(user: User | GuildMember, action: string, reason?: string, guild?: Guild) {
|
||||||
const guild = user instanceof GuildMember ? user.guild : null;
|
const resolvedGuild = guild ?? (user instanceof GuildMember ? user.guild : null);
|
||||||
if (!guild) return;
|
if (!resolvedGuild) return;
|
||||||
if (!this.shouldLog(guild, 'automodActions')) return;
|
if (!this.shouldLog(resolvedGuild, 'automodActions')) return;
|
||||||
const { channel } = this.resolve(guild);
|
const { channel } = this.resolve(resolvedGuild);
|
||||||
if (!channel) return;
|
if (!channel) return;
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Moderation')
|
.setTitle('Moderation')
|
||||||
@@ -141,7 +141,7 @@ export class LoggingService {
|
|||||||
.setColor(0x7289da)
|
.setColor(0x7289da)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
channel.send({ embeds: [embed] }).catch((err) => logger.error('Failed to log action', err));
|
channel.send({ embeds: [embed] }).catch((err) => logger.error('Failed to log action', err));
|
||||||
const guildId = (user as GuildMember)?.guild?.id;
|
const guildId = resolvedGuild.id;
|
||||||
if (guildId) {
|
if (guildId) {
|
||||||
adminSink?.pushGuildLog({
|
adminSink?.pushGuildLog({
|
||||||
guildId,
|
guildId,
|
||||||
@@ -154,6 +154,36 @@ export class LoggingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logAutomodAction(guild: Guild, options: { userTag: string; userId: string; action: string; reason: string; content?: string; channel?: GuildChannel | null; messageUrl?: string }) {
|
||||||
|
if (!this.shouldLog(guild, 'automodActions')) return;
|
||||||
|
const { channel } = this.resolve(guild);
|
||||||
|
if (!channel) return;
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('Automod')
|
||||||
|
.setDescription(`${options.userTag} (${options.userId}) -> ${options.action}`)
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Grund', value: this.safeField(options.reason) },
|
||||||
|
{ name: 'Kanal', value: options.channel ? `<#${options.channel.id}>` : 'Unbekannt' }
|
||||||
|
)
|
||||||
|
.setColor(0xff006e)
|
||||||
|
.setTimestamp();
|
||||||
|
if (options.content) {
|
||||||
|
embed.addFields({ name: 'Nachricht', value: this.safeField(options.content) });
|
||||||
|
}
|
||||||
|
if (options.messageUrl) {
|
||||||
|
embed.addFields({ name: 'Link', value: options.messageUrl });
|
||||||
|
}
|
||||||
|
channel.send({ embeds: [embed] }).catch((err) => logger.error('Failed to log automod action', err));
|
||||||
|
adminSink?.pushGuildLog({
|
||||||
|
guildId: guild.id,
|
||||||
|
level: 'INFO',
|
||||||
|
message: `Automod: ${options.action} (${options.userTag})`,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
category: 'automodActions'
|
||||||
|
});
|
||||||
|
adminSink?.trackGuildEvent(guild.id, 'automod');
|
||||||
|
}
|
||||||
|
|
||||||
logRoleUpdate(member: GuildMember, added: string[], removed: string[]) {
|
logRoleUpdate(member: GuildMember, added: string[], removed: string[]) {
|
||||||
const guildId = member.guild.id;
|
const guildId = member.guild.id;
|
||||||
adminSink?.pushGuildLog({
|
adminSink?.pushGuildLog({
|
||||||
|
|||||||
Reference in New Issue
Block a user