Skip to content

Commit f23d807

Browse files
Merge pull request #218 from Pdzly/feature/improve-moderation
2 parents 0feb2d9 + cc72ec6 commit f23d807

32 files changed

+4269
-11
lines changed

src/Config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ const devConfig: Config = {
5656
yesEmojiId: "👍",
5757
noEmojiId: "👎",
5858
},
59+
threatDetection: {
60+
enabled: true,
61+
alertChannel: "1432483525155623063",
62+
scamLinks: {
63+
enabled: true,
64+
blockShorteners: true,
65+
useExternalApi: true,
66+
},
67+
},
5968
modmail: {
6069
pingRole: "1412470653050818724",
6170
archiveChannel: "1412470199495561338",

src/config.type.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,58 @@ import type { Snowflake } from "discord.js";
22
import type { InformationMessage } from "./modules/information/information.js";
33
import type { BrandingConfig } from "./util/branding.js";
44

5+
export interface ThreatDetectionConfig {
6+
enabled: boolean;
7+
alertChannel?: Snowflake;
8+
exemptRoles?: Snowflake[];
9+
scamLinks?: {
10+
enabled: boolean;
11+
useExternalApi?: boolean;
12+
blockShorteners?: boolean;
13+
safeDomains?: string[];
14+
};
15+
spam?: {
16+
enabled: boolean;
17+
maxMessagesPerWindow: number;
18+
windowSeconds: number;
19+
duplicateThreshold: number;
20+
action: "delete" | "mute";
21+
muteDuration?: number;
22+
};
23+
raid?: {
24+
enabled: boolean;
25+
maxJoinsPerWindow: number;
26+
windowSeconds: number;
27+
action: "alert" | "lockdown" | "kick_new";
28+
newAccountThreshold: number;
29+
};
30+
mentionSpam?: {
31+
enabled: boolean;
32+
maxMentionsPerMessage: number;
33+
maxMentionsPerWindow: number;
34+
windowSeconds: number;
35+
action: "delete" | "mute";
36+
};
37+
toxicContent?: {
38+
enabled: boolean;
39+
detectBypasses: boolean;
40+
action: "flag" | "delete";
41+
};
42+
suspiciousAccounts?: {
43+
enabled: boolean;
44+
minAgeDays: number;
45+
flagDefaultAvatar: boolean;
46+
flagSuspiciousNames: boolean;
47+
suspiciousNamePatterns?: string[];
48+
action: "flag" | "kick";
49+
};
50+
escalation?: {
51+
warningsBeforeMute: number;
52+
mutesBeforeKick: number;
53+
scoreDecayRate: number;
54+
};
55+
}
56+
557
export interface Config {
658
guildId: string;
759
clientId: string;
@@ -60,4 +112,21 @@ export interface Config {
60112
};
61113
branding: BrandingConfig;
62114
informationMessage?: InformationMessage;
115+
threatDetection?: ThreatDetectionConfig;
116+
reputation?: {
117+
enabled: boolean;
118+
warningThresholds: {
119+
muteAt: number;
120+
muteDuration: string;
121+
banAt: number;
122+
};
123+
warningExpiration: {
124+
minor: string;
125+
moderate: string;
126+
severe: string;
127+
};
128+
scoreVisibility: "public" | "mods-only" | "self-only";
129+
allowAppeals: boolean;
130+
appealCooldown: string;
131+
};
63132
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { RolesModule } from "./modules/roles/roles.module.js";
2525
import { ShowcaseModule } from "./modules/showcase.module.js";
2626
import { StarboardModule } from "./modules/starboard/starboard.module.js";
2727
import SuggestModule from "./modules/suggest/suggest.module.js";
28+
import { ThreatDetectionModule } from "./modules/threatDetection/threatDetection.module.js";
2829
import { TokenScannerModule } from "./modules/tokenScanner.module.js";
2930
import { UserModule } from "./modules/user/user.module.js";
3031
import { XpModule } from "./modules/xp/xp.module.js";
@@ -69,6 +70,7 @@ export const moduleManager = new ModuleManager(
6970
ModmailModule,
7071
LeaderboardModule,
7172
UserModule,
73+
ThreatDetectionModule,
7274
],
7375
);
7476

src/modules/moderation/logs.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ export type ModerationLog =
3030
| TempBanExpiredLog
3131
| SoftBanLog
3232
| KickLog
33-
| InviteDeletedLog;
33+
| InviteDeletedLog
34+
| WarningLog
35+
| WarningPardonedLog
36+
| ReputationGrantedLog;
3437

3538
interface BanLog {
3639
kind: "Ban";
@@ -83,6 +86,35 @@ interface InviteDeletedLog {
8386
matches: string[];
8487
}
8588

89+
interface WarningLog {
90+
kind: "Warning";
91+
moderator: User;
92+
target: UserResolvable;
93+
reason: string;
94+
severity: number;
95+
warningId: number;
96+
warningCount: number;
97+
expiresAt: Date | null;
98+
}
99+
100+
interface WarningPardonedLog {
101+
kind: "WarningPardoned";
102+
moderator: User;
103+
target: UserResolvable;
104+
warningId: number;
105+
reason: string;
106+
}
107+
108+
interface ReputationGrantedLog {
109+
kind: "ReputationGranted";
110+
moderator: User;
111+
target: UserResolvable;
112+
eventType: string;
113+
scoreChange: number;
114+
newScore: number;
115+
reason: string;
116+
}
117+
86118
type ModerationKindMapping<T> = {
87119
[f in ModerationLog["kind"]]: T;
88120
};
@@ -95,6 +127,9 @@ const embedTitles: ModerationKindMapping<string> = {
95127
TempBan: "Member Tempbanned",
96128
Kick: "Member Kicked",
97129
TempBanEnded: "Tempban Expired",
130+
Warning: "Member Warned",
131+
WarningPardoned: "Warning Pardoned",
132+
ReputationGranted: "Reputation Granted",
98133
};
99134

100135
const embedColors: ModerationKindMapping<keyof typeof Colors> = {
@@ -105,6 +140,15 @@ const embedColors: ModerationKindMapping<keyof typeof Colors> = {
105140
Unban: "Green",
106141
TempBanEnded: "DarkGreen",
107142
InviteDeleted: "Blurple",
143+
Warning: "Gold",
144+
WarningPardoned: "Aqua",
145+
ReputationGranted: "Green",
146+
};
147+
148+
const SEVERITY_LABELS: Record<number, string> = {
149+
1: "Minor",
150+
2: "Moderate",
151+
3: "Severe",
108152
};
109153

110154
const embedReasons: {
@@ -120,6 +164,23 @@ const embedReasons: {
120164

121165
TempBan: (tempBan) =>
122166
`**Ban duration**: \`${prettyPrintDuration(tempBan.banDuration)}\``,
167+
168+
Warning: (warning) =>
169+
`**Severity:** ${SEVERITY_LABELS[warning.severity] || "Unknown"}\n` +
170+
`**Warning ID:** #${warning.warningId}\n` +
171+
`**Total Active Warnings:** ${warning.warningCount}\n` +
172+
(warning.expiresAt
173+
? `**Expires:** <t:${Math.floor(warning.expiresAt.getTime() / 1000)}:R>`
174+
: "**Expires:** Never"),
175+
176+
WarningPardoned: (pardon) =>
177+
`**Warning ID:** #${pardon.warningId}\n` +
178+
`**Pardon Reason:** ${pardon.reason}`,
179+
180+
ReputationGranted: (rep) =>
181+
`**Type:** ${rep.eventType}\n` +
182+
`**Score Change:** +${rep.scoreChange}\n` +
183+
`**New Score:** ${rep.newScore >= 0 ? "+" : ""}${rep.newScore}`,
123184
};
124185

125186
export async function logModerationAction(
@@ -142,7 +203,8 @@ export async function logModerationAction(
142203
embed.setColor(embedColors[action.kind]);
143204

144205
const targetUser = await client.users.fetch(action.target).catch(() => null);
145-
let description = `**Offender**: ${targetUser && fakeMention(targetUser)} ${actualMention(action.target)}\n`;
206+
const targetLabel = action.kind === "ReputationGranted" ? "Recipient" : "Offender";
207+
let description = `**${targetLabel}**: ${targetUser && fakeMention(targetUser)} ${actualMention(action.target)}\n`;
146208
if ("reason" in action && action.reason) {
147209
description += `**Reason**: ${action.reason}\n`;
148210
}

src/modules/moderation/moderation.module.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import { BanCommand } from "./ban.command.js";
33
import { DeletedMessagesListener } from "./deletedMessages.listener.js";
44
import { InviteListeners } from "./discordInvitesMonitor.listener.js";
55
import { KickCommand } from "./kick.command.js";
6+
import { PardonCommand } from "./pardon.command.js";
7+
import { ReputationCommand } from "./reputation.command.js";
68
import { SoftBanCommand } from "./softBan.command.js";
79
import { TempBanCommand } from "./tempBan.command.js";
810
import { TempBanListener } from "./tempBan.listener.js";
911
import { UnbanCommand } from "./unban.command.js";
12+
import { WarnCommand } from "./warn.command.js";
13+
import { WarningSchedulerListener } from "./warningScheduler.listener.js";
14+
import { WarningsCommand } from "./warnings.command.js";
15+
import { WordlistCommand } from "./wordlist.command.js";
1016
import { ZookeepCommand } from "./zookeep.command.js";
1117

1218
export const ModerationModule: Module = {
@@ -18,6 +24,11 @@ export const ModerationModule: Module = {
1824
TempBanCommand,
1925
KickCommand,
2026
ZookeepCommand,
27+
WarnCommand,
28+
WarningsCommand,
29+
PardonCommand,
30+
WordlistCommand,
31+
ReputationCommand,
2132
],
22-
listeners: [...InviteListeners, TempBanListener, DeletedMessagesListener],
33+
listeners: [...InviteListeners, TempBanListener, WarningSchedulerListener, DeletedMessagesListener],
2334
};

0 commit comments

Comments
 (0)