AutoMod sichtbar machen: Treffer ins Protokoll, Regeln ins Panel
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Discords eigener Filter arbeitet serverseitig und ohne Latenz — daran ist
nichts zu verbessern. Was fehlte, war die Sicht darauf: Treffer landeten nur
in Discords eigenem Kanal, und die Regeln pflegte man in einer ganz anderen
Oberflaeche als alles andere.

Jetzt spiegelt der Bot jeden Treffer als Embed ins Mod-Log — mit Nutzer,
Regel, Ausloeser, Massnahme, getroffenem Wort und gekuerztem Wortlaut, ohne
Erwaehnungen, die den Kanal anpingen. Im Panel steht unter Support jede Regel
mit Ausloeser, Massnahmen und der Zahl ausgenommener Rollen und Kanaele —
letzteres oft der Grund, warum eine Regel scheinbar nicht greift. Schalten
geht dort direkt.

Das Bearbeiten der Wortlisten bleibt bewusst in Discord; dort ist es gut
geloest, und ein zweiter Ort dafuer waere nur eine weitere Stelle, an der
etwas auseinanderlaeuft.

Dazu noetig: der Gateway-Intent AutoModerationExecution. Nicht privilegiert,
aber Discord schickt die Ereignisse nur an Bots mit "Server verwalten" —
fehlt das Recht, sagt das Panel es statt still leer zu bleiben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 06:53:33 +02:00
co-authored by Claude Opus 5
parent c8b1f604f0
commit 3080f52e71
8 changed files with 253 additions and 1 deletions
+2
View File
@@ -302,6 +302,8 @@ export default {
'feat.moderation.desc': 'Warnings, timeouts, cleanup — with history and a log.',
'feat.anti_raid': 'Raid protection',
'feat.anti_raid.desc': 'Spots waves of joins and locks the server down for a while.',
'feat.automod': 'AutoMod log',
'feat.automod.desc': "Hits from Discord's own filter land in the log; rules can be switched here.",
'feat.modlog': 'Audit log',
'feat.modlog.desc': 'Deleted and edited messages, joins, name and role changes.',
'feat.modmail': 'Modmail',
+3 -1
View File
@@ -10,6 +10,7 @@ import {
IconArchive, IconMail, IconSupport, IconMagic, IconTeam, IconRefresh,
IconWarning, IconServer, IconMonitor, IconVoice, IconZap, IconClock,
IconSend, IconTag, IconDatabase, IconPackage, IconLock, IconIdea, IconLink,
IconBot,
} from './icons.jsx';
export const MODULE_ICONS = {
@@ -19,7 +20,8 @@ export const MODULE_ICONS = {
voting: IconThumbsUp, giveaways: IconGiveaway, birthdays: IconBirthday,
events: IconEvents, playtester: IconGame, alpha_keys: IconKey,
role_menus: IconRoles, applications: IconApplications,
moderation: IconShield, anti_raid: IconLock, modlog: IconArchive, modmail: IconMail,
moderation: IconShield, anti_raid: IconLock, automod: IconBot,
modlog: IconArchive, modmail: IconMail,
linked_roles: IconLink, polls: IconThumbsUp,
tickets: IconSupport, welcome: IconMagic, autorole: IconTeam,
sticky_roles: IconRefresh, bug_reports: IconWarning,
+70
View File
@@ -343,6 +343,7 @@ export default function Settings({ me }) {
const [audit, setAudit] = useState([]);
const [serverStats, setServerStats] = useState(null);
const [watchdog, setWatchdog] = useState(null);
const [automod, setAutomod] = useState(null);
const [dienstDraft, setDienstDraft] = useState({ id: null, name: '', url: '', gruppe: 'Dienste', oeffentlich: false });
const [owner, setOwner] = useState(null);
const [auditAlle, setAuditAlle] = useState(false);
@@ -471,6 +472,7 @@ export default function Settings({ me }) {
// Fuers Dashboard: Mitgliederzahl und Nachrichten pro Tag
apiGet('/api/serverstats').then(setServerStats).catch(() => {});
apiGet('/api/watchdog').then(setWatchdog).catch(() => {});
apiGet('/api/automod').then(setAutomod).catch(() => {});
apiGet('/api/ssoapps').then((d) => setSsoApps(d.apps)).catch(() => {});
apiGet('/api/services').then((d) => setServices(d.services)).catch(() => {});
apiGet('/api/pages/all/list').then((d) => setPages(d.pages)).catch(() => {});
@@ -1818,8 +1820,76 @@ export default function Settings({ me }) {
</>
);
/** AutoMod-Regel an- oder abschalten */
async function automodSchalten(id, aktiv) {
try {
await apiPut(`/api/automod/${id}`, { aktiv });
const frisch = await apiGet('/api/automod');
setAutomod(frisch);
} catch (e) {
setFeedback(e.body?.error ?? 'Umschalten fehlgeschlagen.');
setTimeout(() => setFeedback(null), 3000);
}
}
const tabSupport = (
<>
{automod && (
<div className="settings-section">
<h2 className="settings-title">// AutoMod</h2>
<p className="section-intro">
Discords eigener Filter er läuft serverseitig und ohne Umweg über den Bot.
Hier siehst du, was eingerichtet ist, und kannst Regeln schalten.
Wortlisten bearbeitest du in Discord unter <strong>Servereinstellungen AutoMod</strong>.
{!automod.aktiv && ' Das Protokollieren der Treffer ist gerade ausgeschaltet.'}
</p>
{!automod.ok && (
<p className="notice">
Regeln nicht abrufbar: {automod.grund}
{' '} fehlt dem Bot das Recht Server verwalten"?
</p>
)}
{automod.ok && automod.regeln.length === 0 && (
<p className="notice">
Noch keine AutoMod-Regel eingerichtet. Anlegen geht in Discord unter
{' '}<strong>Servereinstellungen → AutoMod</strong>.
</p>
)}
{automod.regeln.map((r) => (
<div className="am-row card" key={r.id}>
<div className="am-head">
<span className={`wd-dot ${r.aktiv ? 'an' : 'wartet'}`} />
<span className="st-name">{r.name}</span>
<span className="tm-chip">{r.ausloeser}</span>
<label className="toggle am-toggle">
<input
type="checkbox"
checked={r.aktiv}
onChange={(e) => automodSchalten(r.id, e.target.checked)}
/>
<span>{r.aktiv ? 'aktiv' : 'aus'}</span>
</label>
</div>
<div className="am-meta">
{r.details.length > 0 && <span>{r.details.join(' · ')}</span>}
<span className="am-actions">
{r.aktionen.map((a) => <span className="tm-chip" key={a}>{a}</span>)}
</span>
{(r.ausnahmen.rollen > 0 || r.ausnahmen.kanaele > 0) && (
<span className="am-exempt">
ausgenommen: {[
r.ausnahmen.rollen > 0 && `${r.ausnahmen.rollen} ${r.ausnahmen.rollen === 1 ? 'Rolle' : 'Rollen'}`,
r.ausnahmen.kanaele > 0 && `${r.ausnahmen.kanaele} ${r.ausnahmen.kanaele === 1 ? 'Kanal' : 'Kanäle'}`,
].filter(Boolean).join(', ')}
</span>
)}
</div>
</div>
))}
</div>
)}
<div className="settings-section">
<h2 className="settings-title">// Moderation &amp; Kontakt</h2>
<div className="field" data-setting="modmail_channel_id">
+11
View File
@@ -1873,6 +1873,17 @@ label.toggle {
font-size: .62rem; letter-spacing: .06em; color: var(--muted2); white-space: nowrap;
}
/* ── AUTOMOD-REGELN ────────────────────────────────── */
.am-row { padding: .9rem 1.1rem; margin-bottom: .7rem; min-width: 0; }
.am-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.am-toggle { margin-left: auto; }
.am-meta {
display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
margin-top: .55rem; font-size: .78rem; color: var(--muted);
}
.am-actions { display: flex; gap: .35rem; flex-wrap: wrap; }
.am-exempt { font-family: var(--mono); font-size: .64rem; color: var(--muted2); }
/* ── ERREICHBARKEITS-BALKEN ────────────────────────── */
/* Ein Strich pro Stunde, 168 Stück. Auf schmalen Schirmen werden die Striche
dünner als ein Pixel — deshalb scrollt der Balken dort lieber, statt zu