diff --git a/config.example.ts b/config.example.ts index 75ccdb3..a64e31f 100644 --- a/config.example.ts +++ b/config.example.ts @@ -9,6 +9,8 @@ export default { dbFile: '', autoBan: true, autoBanThreshold: 3, + antiRaidMembers: 3, + antiRaidRoles: 2, repTriggers: [], repEmote: '', activities: ['Serving NaN users!'], @@ -24,6 +26,9 @@ export default { }, roleIDs: { muted: '', - verified: '' + verified: '', + staff: '', + senior: '', + mvp: '' } } diff --git a/package.json b/package.json index b55502d..156fdc8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "license": "MIT", "private": false, "scripts": { + "start": "ts-node ./src/main.ts --strict", "dev": "nodemon --exec ts-node ./src/main.ts --strict", "lint": "prettier --check ./src", "lint:fix": "prettier --write ./src" diff --git a/src/Devmod.ts b/src/Devmod.ts index de5d9d9..6eb5be1 100644 --- a/src/Devmod.ts +++ b/src/Devmod.ts @@ -16,7 +16,7 @@ import { log } from './utils/log' import { Create } from './utils/submodules/Create' import { Moderation } from './utils/submodules/Moderation' import { Utils } from './utils/submodules/Utils' -import { commandListener } from './processes/commandListener' +import { antiraidListener } from './processes/antiraidListener' import { SubmodulesInterface } from './types/interfaces/SubmodulesInterface' import { DevmodError } from './types/errors/DevmodError' @@ -35,7 +35,7 @@ export class Devmod { // Create a list of processes to be run with the bot const processes: ProcessInterface[] = [ - commandListener + antiraidListener // other listeners to come ] diff --git a/src/processes/antiraidListener.ts b/src/processes/antiraidListener.ts new file mode 100644 index 0000000..aaa2940 --- /dev/null +++ b/src/processes/antiraidListener.ts @@ -0,0 +1,86 @@ +/* + * André Weller 2020 + * Process file for the antiraid listener + */ + +import { Client, Message } from 'discord.js' +import { ConfigInterface } from '../types/interfaces/ConfigInterface' +import { ProcessInterface } from '../types/interfaces/ProcessInterface' +import { SubmodulesInterface } from '../types/interfaces/SubmodulesInterface' + +import * as moment from 'moment' + +export const antiraidListener: ProcessInterface = { + name: 'AntiraidListener', + init(client: Client, config: ConfigInterface, sub: SubmodulesInterface) { + const mentions = new Map() + const mutedRole = config.roleIDs.muted + + // NOTE: Pull from config, same role is achieved + const staffRoleId = config.roleIDs.staff + + // NOTE: Get role from config + // Deprecated + // const seniorRoleId = config.roleIDs.senior + + const maxMentionedRoles = config.antiRaidRoles + const maxMentionedMemebers = config.antiRaidMembers + const maxMentionedEverything = maxMentionedRoles + maxMentionedMemebers + + // DEBUG: Debug console log for role comparison + // console.log(staffRole, seniorRole) + + client.on('message', async (message: Message) => { + if (message.author.bot || message.channel.type === 'dm') return + + const lastKnownTimestamp = mentions.get(message.author.id) ?? null + + const userRoleIds = message.member.roles.cache.map(r => r.id) + + const isStaff = userRoleIds.find(r => r === staffRoleId) + + if (isStaff) { + return + } else { + const { + users: { size: mentionedUsers }, + roles: { size: mentionedRoles } + } = message.mentions + + if ( + mentionedRoles > maxMentionedRoles || + mentionedUsers > maxMentionedMemebers || + mentionedUsers + mentionedRoles > maxMentionedEverything + ) { + lastKnownTimestamp + ? checkOffender(message, lastKnownTimestamp) + : addOffenderToList(message) + } + } + }) + + const addOffenderToList = message => { + mentions.set(message.author.id, [message.createdTimestamp]) + } + + const checkOffender = (message, lastKnownTimestamp) => { + const startDate = moment(message.createdTimestamp * 1000) + const endDate = moment(lastKnownTimestamp * 1000) + + const diff = startDate.diff(endDate) + + diff > 10 ? triggerAntiraid(message) : addOffenderToList(message) + } + + const triggerAntiraid = async message => { + await message.delete() + message.member.roles.add(mutedRole) + addOffenderToList(message) + + // TODO: Send message to #mod-log + message.channel.send( + `<@${message.author.id}> messed with the honk, so he got the bonk. (<@&${staffRoleId}>)` + ) + } + } +} diff --git a/src/types/interfaces/ConfigInterface.ts b/src/types/interfaces/ConfigInterface.ts index ae8312f..8eb10a5 100644 --- a/src/types/interfaces/ConfigInterface.ts +++ b/src/types/interfaces/ConfigInterface.ts @@ -27,6 +27,8 @@ export interface ConfigInterface { dbFile: string autoBan: boolean autoBanThreshold: number + antiRaidMembers: number + antiRaidRoles: number repTriggers: string[] repEmote: string activities: string[] diff --git a/src/types/interfaces/ConfigRolesInterface.ts b/src/types/interfaces/ConfigRolesInterface.ts index f54fce7..dfee6ce 100644 --- a/src/types/interfaces/ConfigRolesInterface.ts +++ b/src/types/interfaces/ConfigRolesInterface.ts @@ -8,9 +8,11 @@ import { Role, RoleResolvable } from 'discord.js' export interface ConfigRoleInterface { muted: RoleResolvable verified: RoleResolvable + staff: RoleResolvable } export interface LiveConfigRoleInterface { muted?: Role verified?: Role + staff?: Role } diff --git a/src/types/interfaces/UserConfigInterface.ts b/src/types/interfaces/UserConfigInterface.ts index a2b768f..4cdd369 100644 --- a/src/types/interfaces/UserConfigInterface.ts +++ b/src/types/interfaces/UserConfigInterface.ts @@ -18,6 +18,8 @@ export interface UserConfigInterface { dbFile?: string autoBan?: boolean autoBanThreshold?: number + antiRaidMembers?: number + antiRaidRoles?: number repTriggers?: string[] repEmote?: string activities?: string[] diff --git a/src/utils/config/mergeConfig.ts b/src/utils/config/mergeConfig.ts index 3d2f4d4..d0dd82a 100644 --- a/src/utils/config/mergeConfig.ts +++ b/src/utils/config/mergeConfig.ts @@ -20,6 +20,8 @@ export const mergeConfigs = (config: UserConfigInterface): ConfigInterface => { dbFile: config.dbFile || join(__dirname, '..', '..', '..', 'devmod.db'), // Absolute path for the database file. autoBan: config.autoBan || true, // Whether or not to enforce auto-banning after a specified number of warnings. autoBanThreshold: config.autoBanThreshold || 3, // Amount of warnings to warrant an auto-ban if enabled. + antiRaidMembers: config.antiRaidMembers || 1, // Amount of members that are allowed to be pinged in a message + antiRaidRoles: config.antiRaidRoles || 2, // Amount of roles that are allowed to be pinged in a message repTriggers: config.repTriggers || ['thanks', 'kudos'], // List of triggers for thanking users. repEmote: config.repEmote || '👍', // The emoji to prefix the thanks received message with. activities: config.activities || ['Serving NaN users!'], // List of activities for the bot to show as a status. @@ -35,7 +37,8 @@ export const mergeConfigs = (config: UserConfigInterface): ConfigInterface => { }, roleIDs: { muted: config.roleIDs.muted, // ID of the role to apply to muted users. - verified: config.roleIDs.verified // ID of the role to apply to verified users. + verified: config.roleIDs.verified, // ID of the role to apply to verified users. + staff: config.roleIDs.staff // ID of the role for staff on the server. } } }