Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@
"sessionToken": "",
"leaderBoardJsonUrl": "",
"userMap": {}
},
"autoban": {
"enabled": true,
"dryRun": true,
"deleteThreshold": 40,
"banThreshold": 60,
"banDurationHours": 24,
"timeWindowDuration": "PT5M"
}
},

Expand All @@ -90,7 +98,8 @@
"votesChannelId": "893179191556706384",
"botSpamChannelId": "893179191556706387",
"hauptwoisTextChannelId": "893179191825162371",
"roleAssignerChannelId": "893179190881452115"
"roleAssignerChannelId": "893179190881452115",
"spamLogChannelId": "1513948506266538275"
},

"voiceChannel": {
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ jobs:
DISCORD_CLIENT_ID: ${{ secrets.CI_CLIENT_ID }}
PR_ID: ${{ github.event.number }}
run: |
envsubst < .github/config.json > config.json
envsubst < .github/config.json > config-ephemeral.json

- name: Read config.json
id: config
uses: juliangruber/read-file-action@v1
with:
path: ./config.json
path: ./config-ephemeral.json

- name: Build args
id: args
Expand Down
18 changes: 17 additions & 1 deletion config.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@
"sessionToken": "",
"leaderBoardJsonUrl": "",
"userMap": {}
},
"autoban": {
// Set to true to enable automatic spam detection and banning
"enabled": false,
// Set to false to actually delete messages and ban users. While true, spam is only detected and logged.
"dryRun": true,
// Score at which a suspicious message is silently deleted (no ban)
"deleteThreshold": 40,
// Score at which the user is deleted + banned for banDurationHours
"banThreshold": 60,
// Duration of the automatic ban in hours
"banDurationHours": 24,
// ISO8601 duration used as the time window to detect the same message across multiple channels
"timeWindowDuration": "PT5M"
}
},

Expand All @@ -94,7 +108,9 @@
"votesChannelId": "893179191556706384",
"botSpamChannelId": "893179191556706387",
"hauptwoisTextChannelId": "893179191825162371",
"roleAssignerChannelId": "893179190881452115"
"roleAssignerChannelId": "893179190881452115",
// Channel ID of a mod-only channel for spam audit logs. Leave empty to disable.
"spamLogChannelId": ""
},

"voiceChannel": {
Expand Down
8 changes: 7 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "#/handler/commandHandler.ts";
import * as guildMemberHandler from "#/handler/guildMemberHandler.ts";
import deleteThreadMessagesHandler from "#/handler/messageCreate/deleteThreadMessagesHandler.ts";
import spamDetectionHandler from "#/handler/messageCreate/spamDetectionHandler.ts";
import { handlePresenceUpdate } from "#/handler/presenceHandler.ts";
import { createBotContext, type BotContext } from "#/context.ts";
import { ehreReactionHandler } from "#/commands/ehre.ts";
Expand Down Expand Up @@ -135,22 +136,27 @@ login().then(
client.user.setActivity(config.activity);

try {
log.debug("Creating bot context...");
botContext = await createBotContext(client);

log.debug("Bot context created, loading commands...");
await loadCommands(botContext);

log.debug("Commands loaded, scheduling cron jobs...");
await cronService.schedule(botContext);

// When the application is ready, slash commands should be registered
log.debug("Registering application commands as guild commands...");
await registerAllApplicationCommandsAsGuildCommands(botContext);
} catch (err) {
sentry.captureException(err);
log.error(err, "Error in `ready` handler");
sentry.captureException(err);
process.exit(1);
}

log.info("Registering main event handlers...");

client.on("messageCreate", m => spamDetectionHandler(m, botContext));
client.on("messageCreate", m => messageCommandHandler(m, botContext));
client.on("messageCreate", m => deleteThreadMessagesHandler(m, botContext));
client.on("guildMemberAdd", m => guildMemberHandler.added(botContext, m));
Expand Down
27 changes: 27 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SpotifyApi } from "@spotify/web-api-ts-sdk";

import type { UserMapEntry } from "#/commands/aoc.ts";
import { readConfig } from "#/service/config.ts";
import log from "#log";

/**
* Object that's passed to every executed command to make it easier to access common channels without repeatedly retrieving stuff via IDs.
Expand Down Expand Up @@ -84,6 +85,14 @@ export interface BotContext {
leaderBoardJsonUrl: string;
userMap: Record<Snowflake, UserMapEntry>;
};
autoban: {
enabled: boolean;
dryRun: boolean;
deleteThreshold: number;
banThreshold: number;
banDurationHours: number;
timeWindowDuration: Temporal.Duration;
};
};

roles: {
Expand Down Expand Up @@ -120,6 +129,7 @@ export interface BotContext {
botSpam: TextChannel;
hauptwoisText: TextChannel;
roleAssigner: TextChannel;
spamLog: TextChannel | null;
};

voiceChannels: {
Expand Down Expand Up @@ -228,8 +238,10 @@ function ensureEmoji(guild: Guild, emojiId: Snowflake, fallbackName: string): Gu
// #endregion

export async function createBotContext(client: Client<true>): Promise<BotContext> {
log.debug("createBotContext: reading config...");
const config = await readConfig();

log.debug("createBotContext: config read, resolving guild...");
const guild = client.guilds.cache.get(config.guildGuildId);
if (!guild) {
throw new Error(`Cannot find configured guild "${config.guildGuildId}"`);
Expand All @@ -240,7 +252,9 @@ export async function createBotContext(client: Client<true>): Promise<BotContext
const voiceChannel = config.voiceChannel;

const soundsPath = path.resolve("data/sounds");
log.debug({ soundsPath }, "createBotContext: creating sounds directory...");
await fs.mkdir(soundsPath, { recursive: true });
log.debug("createBotContext: sounds directory ready, building context object...");

return {
client,
Expand Down Expand Up @@ -319,6 +333,16 @@ export async function createBotContext(client: Client<true>): Promise<BotContext
leaderBoardJsonUrl: config.command.aoc.leaderBoardJsonUrl,
userMap: config.command.aoc.userMap,
},
autoban: {
enabled: config.command.autoban?.enabled ?? false,
dryRun: config.command.autoban?.dryRun ?? true,
deleteThreshold: config.command.autoban?.deleteThreshold ?? 40,
banThreshold: config.command.autoban?.banThreshold ?? 60,
banDurationHours: config.command.autoban?.banDurationHours ?? 24,
timeWindowDuration: Temporal.Duration.from(
config.command.autoban?.timeWindowDuration ?? "PT5M",
),
},
},

deleteThreadMessagesInChannelIds: new Set(config.deleteThreadMessagesInChannelIds),
Expand Down Expand Up @@ -351,6 +375,9 @@ export async function createBotContext(client: Client<true>): Promise<BotContext
botSpam: ensureTextChannel(guild, textChannel.botSpamChannelId),
hauptwoisText: ensureTextChannel(guild, textChannel.hauptwoisTextChannelId),
roleAssigner: ensureTextChannel(guild, textChannel.roleAssignerChannelId),
spamLog: textChannel.spamLogChannelId
? ensureTextChannel(guild, textChannel.spamLogChannelId)
: null,
},

voiceChannels: {
Expand Down
Loading
Loading