Carl-Bot-Paket: Moderation, Auto-/Sticky-Roles, Level-System, Tags, geplante Posts
- Moderation: /warn (DM + Mod-Log), /warns (Historie + löschen), /timeout (max 28d), /purge (bulk, 14-Tage-Limit sauber gemeldet); Mod-Log erweitert um Join/Leave und Nick-/Rollen-Änderungen - Auto-Rolle bei Join + Sticky-Roles (Rollen bei Leave gesichert, bei Rejoin wiederhergestellt; managed/@everyone ausgenommen) — Rollen-Tab - Level-System (opt-in): 15-25 XP/Nachricht mit 60s-Cooldown, MEE6-Formel, Level-Up-Announce, Rollen-Belohnungen (Level=RolleID), /rank mit Fortschritt, öffentliche Bestenliste /level; Community-Tab - Tags: /tag mit Autocomplete, Verwaltung im Composer-Tab (/api/tags) - Geplante Posts: einmalig/täglich/wöchentlich, Minuten-Scheduler, DST-sicher über Neuberechnung aus Zeitfeldern; Composer-Tab (/api/scheduled) - Autocomplete-Dispatch im InteractionCreate; alles smoke-getestet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+79
-3
@@ -1,7 +1,10 @@
|
||||
// Moderation & Kontakt: Modmail (DM ↔ Staff-Thread), Willkommens-Embed, Mod-Log
|
||||
import { ChannelType, EmbedBuilder, Events } from 'discord.js';
|
||||
import { saveModmail, modmailByUser, modmailByThread } from '../db.js';
|
||||
import { modmailChannelId, welcomeChannelId, modlogChannelId, publicUrl } from '../runtime-settings.js';
|
||||
import { saveModmail, modmailByUser, modmailByThread, saveStickyRoles, stickyRolesOf } from '../db.js';
|
||||
import {
|
||||
modmailChannelId, welcomeChannelId, modlogChannelId, publicUrl,
|
||||
autoroleId, stickyRolesEnabled,
|
||||
} from '../runtime-settings.js';
|
||||
|
||||
const BRAND_YELLOW = 0xf5c518;
|
||||
const BRAND_ORANGE = 0xff4d00;
|
||||
@@ -101,6 +104,26 @@ async function handleMemberAdd(member) {
|
||||
await channel.send({ content: `<@${member.id}>`, embeds: [embed] });
|
||||
}
|
||||
|
||||
/* ── Auto- & Sticky-Roles ──────────────────────────── */
|
||||
|
||||
/** Beim Join: gesicherte Rollen wiederherstellen + Auto-Rolle vergeben */
|
||||
async function restoreRoles(member) {
|
||||
const roleIds = new Set();
|
||||
if (stickyRolesEnabled()) {
|
||||
for (const id of stickyRolesOf(member.id)) roleIds.add(id);
|
||||
}
|
||||
const auto = autoroleId();
|
||||
if (auto) roleIds.add(auto);
|
||||
|
||||
for (const id of roleIds) {
|
||||
const role = member.guild.roles.cache.get(id);
|
||||
if (!role || role.managed || role.id === member.guild.id) continue;
|
||||
await member.roles.add(id).catch((e) =>
|
||||
console.error(`[roles] Rolle ${id} bei Join fehlgeschlagen: ${e.message}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mod-Log ───────────────────────────────────────── */
|
||||
|
||||
async function logToModlog(client, embed) {
|
||||
@@ -136,15 +159,68 @@ export function registerModTools(client) {
|
||||
}
|
||||
});
|
||||
|
||||
// Willkommens-Embed
|
||||
// Willkommens-Embed + Auto-/Sticky-Roles + Join-Log
|
||||
client.on(Events.GuildMemberAdd, async (member) => {
|
||||
try {
|
||||
await handleMemberAdd(member);
|
||||
await restoreRoles(member);
|
||||
await logToModlog(client, new EmbedBuilder()
|
||||
.setColor(0x00ff88)
|
||||
.setTitle('📥 Member beigetreten')
|
||||
.setDescription(`**${member.user.username}** (<@${member.id}>) — Mitglied #${member.guild.memberCount}`)
|
||||
.setTimestamp());
|
||||
} catch (error) {
|
||||
console.error('[welcome] Fehler:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Leave: Rollen für Sticky-Restore sichern + Log
|
||||
client.on(Events.GuildMemberRemove, async (member) => {
|
||||
try {
|
||||
const roleIds = [...(member.roles?.cache?.values() ?? [])]
|
||||
.filter((r) => r.id !== member.guild.id && !r.managed)
|
||||
.map((r) => r.id);
|
||||
if (roleIds.length > 0) saveStickyRoles(member.id, roleIds);
|
||||
|
||||
await logToModlog(client, new EmbedBuilder()
|
||||
.setColor(BRAND_ORANGE)
|
||||
.setTitle('📤 Member gegangen')
|
||||
.setDescription(`**${member.user?.username ?? member.id}**${roleIds.length ? ` — Rollen gesichert (${roleIds.length})` : ''}`)
|
||||
.setTimestamp());
|
||||
} catch (error) {
|
||||
console.error('[modlog] Leave-Handling fehlgeschlagen:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Nick-/Rollen-Änderungen loggen
|
||||
client.on(Events.GuildMemberUpdate, async (oldMember, newMember) => {
|
||||
try {
|
||||
const changes = [];
|
||||
if (oldMember.nickname !== newMember.nickname) {
|
||||
changes.push({
|
||||
name: 'Nickname',
|
||||
value: `\`${oldMember.nickname ?? oldMember.user?.username ?? '—'}\` → \`${newMember.nickname ?? newMember.user?.username ?? '—'}\``,
|
||||
});
|
||||
}
|
||||
const oldIds = new Set(oldMember.roles?.cache?.keys?.() ?? []);
|
||||
const newIds = new Set(newMember.roles?.cache?.keys?.() ?? []);
|
||||
const added = [...newIds].filter((id) => !oldIds.has(id) && id !== newMember.guild.id);
|
||||
const removed = [...oldIds].filter((id) => !newIds.has(id) && id !== newMember.guild.id);
|
||||
if (added.length) changes.push({ name: 'Rollen +', value: added.map((id) => `<@&${id}>`).join(' ') });
|
||||
if (removed.length) changes.push({ name: 'Rollen −', value: removed.map((id) => `<@&${id}>`).join(' ') });
|
||||
if (changes.length === 0) return;
|
||||
|
||||
await logToModlog(client, new EmbedBuilder()
|
||||
.setColor(BRAND_YELLOW)
|
||||
.setTitle('👤 Member geändert')
|
||||
.setDescription(`**${newMember.user?.username ?? newMember.id}**`)
|
||||
.addFields(changes)
|
||||
.setTimestamp());
|
||||
} catch (error) {
|
||||
console.error('[modlog] Member-Update fehlgeschlagen:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Mod-Log: gelöschte + bearbeitete Nachrichten (User-Nachrichten in Guild-Kanälen)
|
||||
client.on(Events.MessageDelete, async (message) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user