Files
Papo/src/events/guildMemberAdd.ts
Pepe44DEV 9943254aa4 add permission scanner, info panels, image QR scan, invite tracker, ticket templates
Five new features:
- /permissions scan: audits admin/ban roles, dangerous bot permissions,
  public channels, empty/duplicate/useless roles, too-many-admins warning.
- /panel create: fixed info panels (rules/support/bewerbung/partner/
  rollen/events/faq) with buttons wired into the existing ticket/
  register/partner flows, FAQ panels get a question dropdown.
- Image moderation: QR-code detection in message attachments via jimp+jsqr
  (pure JS, no native build deps) with a small scam-pattern check; opt-in
  toggle in dashboard settings since decoding costs per-message.
- Invite tracker extension: now resolves the inviter (not just the code),
  tracks suspicious joins per invite, and the dashboard growth page gets
  an invite breakdown + recent-joins table.
- Ticket categories: /ticketconfig lets each ticket topic get its own
  ping role and a modal question template shown before the channel is
  created; topics without config behave exactly as before.

Also fixes two rough edges found along the way: the welcome embed had
no way to attach an image despite the backend already supporting it
(added URL/file-upload fields + live preview), and Reaction Roles
required typing raw role IDs into a textarea (replaced with a proper
role picker). Incidentally repairs node_modules/.bin symlinks that were
committed as literal broken shell-script text instead of real symlinks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 00:46:23 +02:00

72 lines
3.3 KiB
TypeScript

import { GuildMember, EmbedBuilder } from 'discord.js';
import { EventHandler } from '../utils/types';
import { context } from '../config/context';
import { settingsStore } from '../config/state';
const event: EventHandler = {
name: 'guildMemberAdd',
execute(member: GuildMember) {
if (context.lockdown.isActive(member.guild.id)) {
member.kick('Lockdown aktiv - neue Mitglieder werden abgewiesen').catch(() => undefined);
context.logging.logMemberJoin(member);
return;
}
Promise.all([context.altAccounts.check(member), context.growth.resolveUsedInvite(member.guild)])
.then(([{ score, reasons }, invite]) => {
const suspicious = context.altAccounts.isSuspicious(score);
if (suspicious) context.logging.logSuspiciousJoin(member, reasons);
context.growth.recordJoin(member.guild.id, member.id, invite, suspicious);
})
.catch(() => undefined);
const guildConfig = settingsStore.get(member.guild.id);
const welcomeCfg = guildConfig?.welcomeConfig || guildConfig?.automodConfig?.welcomeConfig;
if (welcomeCfg?.enabled && welcomeCfg.channelId) {
const channel = member.guild.channels.cache.get(welcomeCfg.channelId);
if (channel && channel.isTextBased()) {
const files: any[] = [];
const colorVal = parseInt((welcomeCfg.embedColor || '00ff99').replace('#', ''), 16);
const embed = new EmbedBuilder()
.setTitle(welcomeCfg.embedTitle || 'Willkommen!')
.setDescription(welcomeCfg.embedDescription || `${member} ist beigetreten.`)
.setColor(isNaN(colorVal) ? 0x00ff99 : colorVal);
const footerText = (welcomeCfg.embedFooter || '').trim();
if (footerText) {
embed.setFooter({ text: footerText });
}
if (welcomeCfg.embedThumbnailData && welcomeCfg.embedThumbnailData.startsWith('data:')) {
const [meta, b64] = welcomeCfg.embedThumbnailData.split(',');
const ext = meta.includes('gif') ? 'gif' : 'png';
const buf = Buffer.from(b64, 'base64');
const name = `welcome-thumb.${ext}`;
files.push({ attachment: buf, name });
embed.setThumbnail(`attachment://${name}`);
} else if (welcomeCfg.embedThumbnail) {
embed.setThumbnail(welcomeCfg.embedThumbnail);
}
if (welcomeCfg.embedImageData && welcomeCfg.embedImageData.startsWith('data:')) {
const [meta, b64] = welcomeCfg.embedImageData.split(',');
const ext = meta.includes('gif') ? 'gif' : 'png';
const buf = Buffer.from(b64, 'base64');
const name = `welcome-image.${ext}`;
files.push({ attachment: buf, name });
embed.setImage(`attachment://${name}`);
} else if (welcomeCfg.embedImage) {
embed.setImage(welcomeCfg.embedImage);
}
channel.send({ embeds: [embed], files }).catch(() => undefined);
}
} else if (guildConfig?.welcomeChannelId) {
const channel = member.guild.channels.cache.get(guildConfig.welcomeChannelId);
if (channel && channel.isTextBased()) {
channel.send({ content: `Willkommen ${member}!` }).catch(() => undefined);
}
}
context.logging.logMemberJoin(member);
context.stats.refreshGuild(member.guild.id).catch(() => undefined);
}
};
export default event;