From 1078cfa8f5bafc1e430549f614502316f626aa20 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Thu, 3 Feb 2022 15:18:49 -0500 Subject: [PATCH 1/9] Added spam and troll detection --- config/production/vars.json | 4 +++- config/staging/vars.json | 4 +++- src/components/messageListener.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/config/production/vars.json b/config/production/vars.json index d7390bdf..0c45d048 100644 --- a/config/production/vars.json +++ b/config/production/vars.json @@ -2,5 +2,7 @@ "TARGET_GUILD_ID": "667823274201448469", "COFFEE_ROLE_ID": "936899047816589354", "NOTIF_CHANNEL_ID": "872305316522508329", - "MOD_CHANNEL_ID": "841526693135122452" + "MOD_CHANNEL_ID": "841526693135122452", + "HONEYPOT_CHANNEL_ID": "938889977121607750", + "PC_CATEGORY_ID": "813606301108142090" } diff --git a/config/staging/vars.json b/config/staging/vars.json index fb55f708..49ccdcd4 100644 --- a/config/staging/vars.json +++ b/config/staging/vars.json @@ -2,5 +2,7 @@ "TARGET_GUILD_ID": "701335585272627200", "COFFEE_ROLE_ID": "861744590775779358", "NOTIF_CHANNEL_ID": "844995465002483752", - "MOD_CHANNEL_ID": "844995465002483752" + "MOD_CHANNEL_ID": "844995465002483752", + "HONEYPOT_CHANNEL_ID": "938846001916166215", + "PC_CATEGORY_ID": "938643353543790624" } diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index 5f959a0b..1560ae31 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -2,8 +2,36 @@ import { Message } from 'discord.js'; import { applyBonusByUserId } from './coin'; import { client } from '../bot'; import { TextChannel } from 'discord.js'; +import { readFileSync } from 'fs'; +import { logError } from './logger'; + +const ENV: string = process.env.NODE_ENV || 'dev'; +const vars = JSON.parse(readFileSync(`./config/${ENV}/vars.json`, 'utf-8')); +const PC_CATEGORY_ID: string = vars.PC_CATEGORY_ID; +const HONEYPOT_CHANNEL_ID: string = vars.HONEYPOT_CHANNEL_ID; export const messageListener = async (message: Message): Promise => { + // Detects spammers/trolls/people who got hacked + const pingWords = ['@everyone', '@here']; + const punishableWords = ['http', 'nitro']; + if ( + (pingWords.some((word) => message.content.includes(word)) && + punishableWords.some((word) => message.content.toLowerCase().includes(word)) && + message.channel instanceof TextChannel && + message.channel.parentID !== PC_CATEGORY_ID) || + message.channel.id === HONEYPOT_CHANNEL_ID + ) { + // Soft bans member and deletes messages from past 1 day + try { + if (await message.member?.ban({ days: 1, reason: 'Spammer/troll/got hacked' })) { + await message.guild?.members.unban(message.author.id); + } + } catch (err) { + logError(err as Error); + } + return; + } + if (!client.user) { return; } From a46342ef4ccfbcf1690a2e2c7cdac13699e2df9b Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Thu, 3 Feb 2022 22:18:37 -0500 Subject: [PATCH 2/9] Code review changes and moved database initialization helper functions to db.ts to remove circular dependencies --- src/bot.ts | 6 +- src/components/coffeechat.ts | 18 +----- src/components/coin.ts | 44 ------------- src/components/cron.ts | 4 +- src/components/db.ts | 102 +++++++++++++++++++++++++++--- src/components/interview.ts | 9 --- src/components/messageListener.ts | 43 +++++++++---- src/components/suggestions.ts | 16 ----- src/constants.ts | 4 ++ 9 files changed, 132 insertions(+), 114 deletions(-) create mode 100644 src/constants.ts diff --git a/src/bot.ts b/src/bot.ts index 5ad47a56..b8b03477 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -3,7 +3,6 @@ dotenv.config(); import Discord from 'discord.js'; import yaml from 'js-yaml'; -import fs from 'fs'; import Commando from 'discord.js-commando'; import path from 'path'; @@ -13,15 +12,14 @@ import { messageListener } from './components/messageListener'; import { initEmojis } from './components/emojis'; import { createSuggestionCron, createBonusInterviewerListCron } from './components/cron'; import { readFileSync } from 'fs'; +import { vars } from './constants'; -const ENV: string = process.env.NODE_ENV || '.'; -const vars = JSON.parse(readFileSync(`./config/${ENV}/vars.json`, 'utf-8')); const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID; const BOT_TOKEN: string = process.env.BOT_TOKEN || '.'; const BOT_PREFIX = '.'; // initialize Commando client -const botOwners = yaml.load(fs.readFileSync('config/owners.yml', 'utf8')) as string[]; +const botOwners = yaml.load(readFileSync('config/owners.yml', 'utf8')) as string[]; export const client = new Commando.Client({ owner: botOwners, commandPrefix: BOT_PREFIX, diff --git a/src/components/coffeechat.ts b/src/components/coffeechat.ts index c8bbfce7..4e10047f 100644 --- a/src/components/coffeechat.ts +++ b/src/components/coffeechat.ts @@ -1,12 +1,9 @@ import { Client } from 'discord.js-commando'; -import { Database } from 'sqlite'; import { openDB } from './db'; import _ from 'lodash'; import { Person, stableMarriage } from 'stable-marriage'; -import { readFileSync } from 'fs'; +import { vars } from '../constants'; -const ENV: string = process.env.NODE_ENV || '.'; -const vars = JSON.parse(readFileSync(`./config/${ENV}/vars.json`, 'utf-8')); const COFFEE_ROLE_ID: string = vars.COFFEE_ROLE_ID; const TARGET_GUILD_ID: string = vars.TARGET_GUILD_ID; //since we might fully hit hundreds of people if we release this into the wider server, set iterations at around 100-200 to keep time at a reasonable number @@ -19,19 +16,6 @@ interface historic_match { match_date: string; } -export const initCoffeeChatTables = async (db: Database): Promise => { - //Database to store past matches, with TIMESTAMP being the time matches were written into DB - await db.run( - ` - CREATE TABLE IF NOT EXISTS coffee_historic_matches ( - first_user_id TEXT NOT NULL, - second_user_id TEXT NOT NULL, - match_date TIMESTAMP NOT NULL - ) - ` - ); -}; - /* * Generates a single match round based on historical match records * Does NOT save this match to history; must call writeHistoricMatches to "confirm" this matching happened diff --git a/src/components/coin.ts b/src/components/coin.ts index 9924164f..eb1eaf84 100644 --- a/src/components/coin.ts +++ b/src/components/coin.ts @@ -1,6 +1,4 @@ -import { Database } from 'sqlite'; import _ from 'lodash'; - import { openDB } from './db'; export enum BonusType { @@ -70,48 +68,6 @@ export interface UserCoinBonus { last_granted: Date; } -export const initUserCoinTable = async (db: Database): Promise => { - await db.run( - ` - CREATE TABLE IF NOT EXISTS user_coin ( - user_id VARCHAR(255) PRIMARY KEY NOT NULL, - balance INTEGER NOT NULL CHECK(balance>=0) - ); - ` - ); -}; - -export const initUserCoinBonusTable = async (db: Database): Promise => { - await db.run( - ` - CREATE TABLE IF NOT EXISTS user_coin_bonus ( - user_id VARCHAR(255) NOT NULL, - bonus_type INTEGER NOT NULL, - last_granted TIMESTAMP NOT NULL, - PRIMARY KEY (user_id, bonus_type) - ); - ` - ); -}; - -export const initUserCoinLedgerTable = async (db: Database): Promise => { - await db.run( - ` - CREATE TABLE IF NOT EXISTS user_coin_ledger ( - id INTEGER PRIMARY KEY NOT NULL, - user_id VARCHAR(255) NOT NULL, - amount INTEGER NOT NULL, - new_balance INTEGER NOT NULL, - event INTEGER NOT NULL, - reason VARCHAR(255), - admin_id VARCHAR(255), - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - ` - ); - await db.run('CREATE INDEX IF NOT EXISTS ix_user_coin_ledger_user_id ON user_coin_ledger (user_id)'); -}; - export const getCoinBalanceByUserId = async (userId: string): Promise => { const db = await openDB(); // Query user coin balance from DB. diff --git a/src/components/cron.ts b/src/components/cron.ts index 4a63cd12..bfc6e0b6 100644 --- a/src/components/cron.ts +++ b/src/components/cron.ts @@ -6,10 +6,8 @@ import { EMBED_COLOUR } from '../utils/embeds'; import { getInterviewers } from './interview'; import { coinBonusMap, BonusType, adjustCoinBalanceByUserId } from './coin'; import _ from 'lodash'; -import { readFileSync } from 'fs'; +import { vars } from '../constants'; -const ENV: string = process.env.NODE_ENV || '.'; -const vars = JSON.parse(readFileSync(`./config/${ENV}/vars.json`, 'utf-8')); const MOD_CHANNEL_ID: string = vars.MOD_CHANNEL_ID; // Checks for new suggestions every min diff --git a/src/components/db.ts b/src/components/db.ts index 60d7db70..3eac3e75 100644 --- a/src/components/db.ts +++ b/src/components/db.ts @@ -1,10 +1,5 @@ import sqlite3 from 'sqlite3'; import { open, Database } from 'sqlite'; - -import { initSuggestionsTable } from './suggestions'; -import { initInterviewTables } from './interview'; -import { initCoffeeChatTables } from './coffeechat'; -import { initUserCoinBonusTable, initUserCoinTable, initUserCoinLedgerTable } from './coin'; import logger from './logger'; let db: Database | null = null; @@ -12,14 +7,105 @@ let db: Database | null = null; export const openCommandoDB = async (): Promise => await open({ filename: 'db/commando.db', driver: sqlite3.Database }); +const initCoffeeChatTables = async (db: Database): Promise => { + //Database to store past matches, with TIMESTAMP being the time matches were written into DB + await db.run( + ` + CREATE TABLE IF NOT EXISTS coffee_historic_matches ( + first_user_id TEXT NOT NULL, + second_user_id TEXT NOT NULL, + match_date TIMESTAMP NOT NULL + ) + ` + ); +}; + +const initInterviewTables = async (db: Database): Promise => { + await db.run( + ` + CREATE TABLE IF NOT EXISTS interviewers ( + user_id TEXT PRIMARY KEY, + link TEXT NOT NULL, + status INTEGER NOT NULL DEFAULT 0 + ) + ` + ); + await db.run( + ` + CREATE TABLE IF NOT EXISTS domains ( + user_id TEXT NOT NULL, + domain TEXT NOT NULL + ) + ` + ); + await db.run('CREATE INDEX IF NOT EXISTS ix_domains_domain ON domains (domain)'); +}; + +const initSuggestionsTable = async (db: Database): Promise => { + await db.run( + ` + CREATE TABLE IF NOT EXISTS suggestions ( + id INTEGER PRIMARY KEY NOT NULL, + author_id VARCHAR(255) NOT NULL, + author_username TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + suggestion TEXT NOT NULL, + state VARCHAR(255) NOT NULL + ) + ` + ); +}; + +const initUserCoinBonusTable = async (db: Database): Promise => { + await db.run( + ` + CREATE TABLE IF NOT EXISTS user_coin_bonus ( + user_id VARCHAR(255) NOT NULL, + bonus_type INTEGER NOT NULL, + last_granted TIMESTAMP NOT NULL, + PRIMARY KEY (user_id, bonus_type) + ) + ` + ); +}; + +const initUserCoinLedgerTable = async (db: Database): Promise => { + await db.run( + ` + CREATE TABLE IF NOT EXISTS user_coin_ledger ( + id INTEGER PRIMARY KEY NOT NULL, + user_id VARCHAR(255) NOT NULL, + amount INTEGER NOT NULL, + new_balance INTEGER NOT NULL, + event INTEGER NOT NULL, + reason VARCHAR(255), + admin_id VARCHAR(255), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + ` + ); + await db.run('CREATE INDEX IF NOT EXISTS ix_user_coin_ledger_user_id ON user_coin_ledger (user_id)'); +}; + +const initUserCoinTable = async (db: Database): Promise => { + await db.run( + ` + CREATE TABLE IF NOT EXISTS user_coin ( + user_id VARCHAR(255) PRIMARY KEY NOT NULL, + balance INTEGER NOT NULL CHECK(balance>=0) + ) + ` + ); +}; + const initTables = async (db: Database): Promise => { //initialize all relevant tables - await initSuggestionsTable(db); - await initInterviewTables(db); await initCoffeeChatTables(db); - await initUserCoinTable(db); + await initInterviewTables(db); + await initSuggestionsTable(db); await initUserCoinBonusTable(db); await initUserCoinLedgerTable(db); + await initUserCoinTable(db); }; export const openDB = async (): Promise => { diff --git a/src/components/interview.ts b/src/components/interview.ts index eb963b5c..7fbd0b9d 100644 --- a/src/components/interview.ts +++ b/src/components/interview.ts @@ -1,6 +1,4 @@ import _ from 'lodash'; -import { Database } from 'sqlite'; - import { openDB } from './db'; //maps from key to readable string @@ -28,13 +26,6 @@ export enum Status { Paused } -export const initInterviewTables = async (db: Database): Promise => { - await db.run(`CREATE TABLE IF NOT EXISTS interviewers - (user_id TEXT PRIMARY KEY, link TEXT NOT NULL, status INTEGER NOT NULL DEFAULT 0)`); - await db.run('CREATE TABLE IF NOT EXISTS domains (user_id TEXT NOT NULL, domain TEXT NOT NULL)'); - await db.run('CREATE INDEX IF NOT EXISTS ix_domains_domain ON domains (domain)'); -}; - export const getInterviewer = async (id: string): Promise => { const db = await openDB(); return await db.get('SELECT * FROM interviewers WHERE user_id = ?', id); diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index 1560ae31..00df1e0e 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -2,26 +2,36 @@ import { Message } from 'discord.js'; import { applyBonusByUserId } from './coin'; import { client } from '../bot'; import { TextChannel } from 'discord.js'; -import { readFileSync } from 'fs'; import { logError } from './logger'; +import { vars } from '../constants'; -const ENV: string = process.env.NODE_ENV || 'dev'; -const vars = JSON.parse(readFileSync(`./config/${ENV}/vars.json`, 'utf-8')); const PC_CATEGORY_ID: string = vars.PC_CATEGORY_ID; const HONEYPOT_CHANNEL_ID: string = vars.HONEYPOT_CHANNEL_ID; -export const messageListener = async (message: Message): Promise => { - // Detects spammers/trolls/people who got hacked +/* + * Detect spammers/trolls/people who got hacked, not by the honeypot method, by + * detecting that the message contains a ping and punishable word, and is sent + * in the Discord server, not in the PC category + */ +const detectSpammersAndTrollsNotByHoneypot = (message: Message): boolean => { + // Pings that would mention many people in the Discord server const pingWords = ['@everyone', '@here']; + // Keywords that point towards the user being a spammer/troll/someone who got hacked const punishableWords = ['http', 'nitro']; - if ( - (pingWords.some((word) => message.content.includes(word)) && - punishableWords.some((word) => message.content.toLowerCase().includes(word)) && - message.channel instanceof TextChannel && - message.channel.parentID !== PC_CATEGORY_ID) || - message.channel.id === HONEYPOT_CHANNEL_ID - ) { - // Soft bans member and deletes messages from past 1 day + return ( + pingWords.some((word) => message.content.includes(word)) && + punishableWords.some((word) => message.content.toLowerCase().includes(word)) && + message.channel instanceof TextChannel && + message.channel.parentID !== PC_CATEGORY_ID + ); +}; + +/* + * Punish spammers/trolls/people who got hacked + */ +const punishSpammersAndTrolls = async (message: Message): Promise => { + if (detectSpammersAndTrollsNotByHoneypot(message) || message.channel.id === HONEYPOT_CHANNEL_ID) { + // Soft ban member and delete messages from past 1 day try { if (await message.member?.ban({ days: 1, reason: 'Spammer/troll/got hacked' })) { await message.guild?.members.unban(message.author.id); @@ -29,6 +39,13 @@ export const messageListener = async (message: Message): Promise => { } catch (err) { logError(err as Error); } + return true; + } + return false; +}; + +export const messageListener = async (message: Message): Promise => { + if (await punishSpammersAndTrolls(message)) { return; } diff --git a/src/components/suggestions.ts b/src/components/suggestions.ts index 2a0dd0c0..03456006 100644 --- a/src/components/suggestions.ts +++ b/src/components/suggestions.ts @@ -1,5 +1,4 @@ import _ from 'lodash'; -import { Database } from 'sqlite'; import { openDB } from './db'; // maps from key to readable string @@ -32,21 +31,6 @@ export interface Suggestion { state: string; } -export const initSuggestionsTable = async (db: Database): Promise => { - await db.run( - ` - CREATE TABLE IF NOT EXISTS suggestions ( - id INTEGER PRIMARY KEY NOT NULL, - author_id VARCHAR(255) NOT NULL, - author_username TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - suggestion TEXT NOT NULL, - state VARCHAR(255) NOT NULL - ); - ` - ); -}; - // Get id and suggestion from a list of Suggestions export const getSuggestionPrintout = async (suggestions: Suggestion[]): Promise => { return suggestions.map((suggestion) => '**' + suggestion['id'] + '** | ' + suggestion['suggestion']).join('\n'); diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..0efa601e --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,4 @@ +import { readFileSync } from 'fs'; + +const ENV: string = process.env.NODE_ENV || 'dev'; +export const vars: Record = JSON.parse(readFileSync(`./config/${ENV}/vars.json`, 'utf-8')); From 955360e1a9e8d935418654fc55c9d240d66ec67a Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Thu, 3 Feb 2022 23:15:40 -0500 Subject: [PATCH 3/9] Changed constants to config --- src/constants.ts => config/config.ts | 0 src/bot.ts | 2 +- src/components/coffeechat.ts | 2 +- src/components/cron.ts | 2 +- src/components/messageListener.ts | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/constants.ts => config/config.ts (100%) diff --git a/src/constants.ts b/config/config.ts similarity index 100% rename from src/constants.ts rename to config/config.ts diff --git a/src/bot.ts b/src/bot.ts index b8b03477..fe9c0d3a 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -12,7 +12,7 @@ import { messageListener } from './components/messageListener'; import { initEmojis } from './components/emojis'; import { createSuggestionCron, createBonusInterviewerListCron } from './components/cron'; import { readFileSync } from 'fs'; -import { vars } from './constants'; +import { vars } from '../config/config'; const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID; const BOT_TOKEN: string = process.env.BOT_TOKEN || '.'; diff --git a/src/components/coffeechat.ts b/src/components/coffeechat.ts index 4e10047f..72c7ffab 100644 --- a/src/components/coffeechat.ts +++ b/src/components/coffeechat.ts @@ -2,7 +2,7 @@ import { Client } from 'discord.js-commando'; import { openDB } from './db'; import _ from 'lodash'; import { Person, stableMarriage } from 'stable-marriage'; -import { vars } from '../constants'; +import { vars } from '../../config/config'; const COFFEE_ROLE_ID: string = vars.COFFEE_ROLE_ID; const TARGET_GUILD_ID: string = vars.TARGET_GUILD_ID; diff --git a/src/components/cron.ts b/src/components/cron.ts index bfc6e0b6..8954e583 100644 --- a/src/components/cron.ts +++ b/src/components/cron.ts @@ -6,7 +6,7 @@ import { EMBED_COLOUR } from '../utils/embeds'; import { getInterviewers } from './interview'; import { coinBonusMap, BonusType, adjustCoinBalanceByUserId } from './coin'; import _ from 'lodash'; -import { vars } from '../constants'; +import { vars } from '../../config/config'; const MOD_CHANNEL_ID: string = vars.MOD_CHANNEL_ID; diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index 00df1e0e..a11f3700 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -3,7 +3,7 @@ import { applyBonusByUserId } from './coin'; import { client } from '../bot'; import { TextChannel } from 'discord.js'; import { logError } from './logger'; -import { vars } from '../constants'; +import { vars } from '../../config/config'; const PC_CATEGORY_ID: string = vars.PC_CATEGORY_ID; const HONEYPOT_CHANNEL_ID: string = vars.HONEYPOT_CHANNEL_ID; From 23930f219400cc3b16ecfe1414afd1a61c44cdf9 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Thu, 3 Feb 2022 23:53:28 -0500 Subject: [PATCH 4/9] Changed soft ban to temp ban --- src/components/messageListener.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index a11f3700..84d7dbea 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -31,10 +31,10 @@ const detectSpammersAndTrollsNotByHoneypot = (message: Message): boolean => { */ const punishSpammersAndTrolls = async (message: Message): Promise => { if (detectSpammersAndTrollsNotByHoneypot(message) || message.channel.id === HONEYPOT_CHANNEL_ID) { - // Soft ban member and delete messages from past 1 day + // Temp ban member for 1 day and 1 hour, and delete messages from past 1 day try { if (await message.member?.ban({ days: 1, reason: 'Spammer/troll/got hacked' })) { - await message.guild?.members.unban(message.author.id); + setTimeout(async () => await message.guild?.members.unban(message.author.id), 25 * 60 * 60 * 1000); // 1 day and 1 hour in milliseconds } } catch (err) { logError(err as Error); From d59098d1a79b9553f40597aa31a60c7513cfd131 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Fri, 4 Feb 2022 17:49:00 -0500 Subject: [PATCH 5/9] Moved config file and added more to comment --- src/bot.ts | 2 +- src/components/coffeechat.ts | 2 +- src/components/cron.ts | 2 +- src/components/messageListener.ts | 3 ++- {config => src}/config.ts | 0 5 files changed, 5 insertions(+), 4 deletions(-) rename {config => src}/config.ts (100%) diff --git a/src/bot.ts b/src/bot.ts index fe9c0d3a..911d226e 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -12,7 +12,7 @@ import { messageListener } from './components/messageListener'; import { initEmojis } from './components/emojis'; import { createSuggestionCron, createBonusInterviewerListCron } from './components/cron'; import { readFileSync } from 'fs'; -import { vars } from '../config/config'; +import { vars } from './config'; const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID; const BOT_TOKEN: string = process.env.BOT_TOKEN || '.'; diff --git a/src/components/coffeechat.ts b/src/components/coffeechat.ts index 72c7ffab..d8d09b83 100644 --- a/src/components/coffeechat.ts +++ b/src/components/coffeechat.ts @@ -2,7 +2,7 @@ import { Client } from 'discord.js-commando'; import { openDB } from './db'; import _ from 'lodash'; import { Person, stableMarriage } from 'stable-marriage'; -import { vars } from '../../config/config'; +import { vars } from '../config'; const COFFEE_ROLE_ID: string = vars.COFFEE_ROLE_ID; const TARGET_GUILD_ID: string = vars.TARGET_GUILD_ID; diff --git a/src/components/cron.ts b/src/components/cron.ts index 8954e583..c011be9e 100644 --- a/src/components/cron.ts +++ b/src/components/cron.ts @@ -6,7 +6,7 @@ import { EMBED_COLOUR } from '../utils/embeds'; import { getInterviewers } from './interview'; import { coinBonusMap, BonusType, adjustCoinBalanceByUserId } from './coin'; import _ from 'lodash'; -import { vars } from '../../config/config'; +import { vars } from '../config'; const MOD_CHANNEL_ID: string = vars.MOD_CHANNEL_ID; diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index 84d7dbea..5e949ed6 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -3,7 +3,7 @@ import { applyBonusByUserId } from './coin'; import { client } from '../bot'; import { TextChannel } from 'discord.js'; import { logError } from './logger'; -import { vars } from '../../config/config'; +import { vars } from '../config'; const PC_CATEGORY_ID: string = vars.PC_CATEGORY_ID; const HONEYPOT_CHANNEL_ID: string = vars.HONEYPOT_CHANNEL_ID; @@ -28,6 +28,7 @@ const detectSpammersAndTrollsNotByHoneypot = (message: Message): boolean => { /* * Punish spammers/trolls/people who got hacked + * Return true if someone of this kind is detected, false otherwise */ const punishSpammersAndTrolls = async (message: Message): Promise => { if (detectSpammersAndTrollsNotByHoneypot(message) || message.channel.id === HONEYPOT_CHANNEL_ID) { diff --git a/config/config.ts b/src/config.ts similarity index 100% rename from config/config.ts rename to src/config.ts From 9003395ef17f33a134a5d5946e799dfabe7cbbc3 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Fri, 4 Feb 2022 19:10:43 -0500 Subject: [PATCH 6/9] Changed temp ban to kick and removal of relevant messages --- src/components/messageListener.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index 5e949ed6..a70cdf67 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -32,11 +32,10 @@ const detectSpammersAndTrollsNotByHoneypot = (message: Message): boolean => { */ const punishSpammersAndTrolls = async (message: Message): Promise => { if (detectSpammersAndTrollsNotByHoneypot(message) || message.channel.id === HONEYPOT_CHANNEL_ID) { - // Temp ban member for 1 day and 1 hour, and delete messages from past 1 day + // Delete the message, and kick the member if they are still in the server try { - if (await message.member?.ban({ days: 1, reason: 'Spammer/troll/got hacked' })) { - setTimeout(async () => await message.guild?.members.unban(message.author.id), 25 * 60 * 60 * 1000); // 1 day and 1 hour in milliseconds - } + await message.delete(); + await message.member?.kick('Spammer/troll/got hacked'); } catch (err) { logError(err as Error); } From fbbcf4ed4e97f4d16f19b2cfbcfdb8b5e9ad7204 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Sat, 5 Feb 2022 17:42:41 -0500 Subject: [PATCH 7/9] Log the kick --- README.md | 1 - config/production/vars.json | 1 - config/staging/vars.json | 1 - src/commands/coffeechats/coffeeMatch.ts | 3 +-- src/components/cron.ts | 4 ++-- src/components/messageListener.ts | 13 +++++++++---- src/utils/embeds.ts | 26 +++++++++++++++++++++++++ 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4cfb5160..01ea7546 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Set these accordingly within the correct config folder. If you are testing local - `TARGET_GUILD_ID`: the guild (server) in which coffee chats are being held. - `COFFEE_ROLE_ID`: the ID of the role the bot will use to decide who is enrolled into coffeechats. - `NOTIF_CHANNEL_ID`: the ID of the channel the bot will send system notifications to. -- `MOD_CHANNEL_ID` : the ID of the discord mod channel. ## Required environment variables diff --git a/config/production/vars.json b/config/production/vars.json index 0c45d048..82950f2e 100644 --- a/config/production/vars.json +++ b/config/production/vars.json @@ -2,7 +2,6 @@ "TARGET_GUILD_ID": "667823274201448469", "COFFEE_ROLE_ID": "936899047816589354", "NOTIF_CHANNEL_ID": "872305316522508329", - "MOD_CHANNEL_ID": "841526693135122452", "HONEYPOT_CHANNEL_ID": "938889977121607750", "PC_CATEGORY_ID": "813606301108142090" } diff --git a/config/staging/vars.json b/config/staging/vars.json index 49ccdcd4..95aaedba 100644 --- a/config/staging/vars.json +++ b/config/staging/vars.json @@ -2,7 +2,6 @@ "TARGET_GUILD_ID": "701335585272627200", "COFFEE_ROLE_ID": "861744590775779358", "NOTIF_CHANNEL_ID": "844995465002483752", - "MOD_CHANNEL_ID": "844995465002483752", "HONEYPOT_CHANNEL_ID": "938846001916166215", "PC_CATEGORY_ID": "938643353543790624" } diff --git a/src/commands/coffeechats/coffeeMatch.ts b/src/commands/coffeechats/coffeeMatch.ts index 9871bee1..15711a2f 100644 --- a/src/commands/coffeechats/coffeeMatch.ts +++ b/src/commands/coffeechats/coffeeMatch.ts @@ -1,8 +1,7 @@ -import { Message } from 'discord.js'; +import { Message, User } from 'discord.js'; import { CommandoClient, CommandoMessage } from 'discord.js-commando'; import { writeHistoricMatches, getMatch } from '../../components/coffeechat'; import { AdminCommand } from '../../utils/commands'; -import { User } from 'discord.js'; import _ from 'lodash'; import { logError } from '../../components/logger'; diff --git a/src/components/cron.ts b/src/components/cron.ts index c011be9e..193ea33a 100644 --- a/src/components/cron.ts +++ b/src/components/cron.ts @@ -8,7 +8,7 @@ import { coinBonusMap, BonusType, adjustCoinBalanceByUserId } from './coin'; import _ from 'lodash'; import { vars } from '../config'; -const MOD_CHANNEL_ID: string = vars.MOD_CHANNEL_ID; +const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID; // Checks for new suggestions every min export const createSuggestionCron = (client: CommandoClient): CronJob => @@ -16,7 +16,7 @@ export const createSuggestionCron = (client: CommandoClient): CronJob => const createdSuggestions = await getSuggestions(SuggestionState.Created); const createdSuggestionIds = createdSuggestions.map((a) => Number(a.id)); if (!_.isEmpty(createdSuggestionIds)) { - const messageChannel = client.channels.cache.get(MOD_CHANNEL_ID); + const messageChannel = client.channels.cache.get(NOTIF_CHANNEL_ID); if (!messageChannel) { throw 'Bad channel ID'; diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index a70cdf67..7255ce19 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -1,8 +1,8 @@ -import { Message } from 'discord.js'; +import { Message, TextChannel } from 'discord.js'; import { applyBonusByUserId } from './coin'; import { client } from '../bot'; -import { TextChannel } from 'discord.js'; import { logError } from './logger'; +import { sendKickEmbed } from '../utils/embeds'; import { vars } from '../config'; const PC_CATEGORY_ID: string = vars.PC_CATEGORY_ID; @@ -32,10 +32,15 @@ const detectSpammersAndTrollsNotByHoneypot = (message: Message): boolean => { */ const punishSpammersAndTrolls = async (message: Message): Promise => { if (detectSpammersAndTrollsNotByHoneypot(message) || message.channel.id === HONEYPOT_CHANNEL_ID) { - // Delete the message, and kick the member if they are still in the server + // Delete the message, and if the user is still in the server, then kick them and log it try { await message.delete(); - await message.member?.kick('Spammer/troll/got hacked'); + if (message.member) { + const user = message.member.user; + const reason = 'Spammer/troll/got hacked'; + await message.member.kick(reason); + await sendKickEmbed(message, user, reason); + } } catch (err) { logError(err as Error); } diff --git a/src/utils/embeds.ts b/src/utils/embeds.ts index 13ef608a..62647f95 100644 --- a/src/utils/embeds.ts +++ b/src/utils/embeds.ts @@ -1 +1,27 @@ +import { Message, MessageEmbed, User, TextChannel } from 'discord.js'; +import { client } from '../bot'; +import { vars } from '../config'; + +const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID; + export const EMBED_COLOUR = '#0099ff'; + +/* + * Send kick embed + */ +export const sendKickEmbed = async (message: Message, user: User, reason = ''): Promise => { + const kickEmbed = new MessageEmbed() + .setColor(EMBED_COLOUR) + .setTitle('Kick') + .addField('User', `${user.tag} (${user.id})`) + .setFooter(`Message ID: ${message.id} • ${new Date().toUTCString()}`); + if (reason) kickEmbed.addField('Reason', `${reason}`); + kickEmbed.addField('Channel', `<#${message.channel.id}> (${(message.channel as TextChannel).name})`); + if (message.content) kickEmbed.addField('Content', `${message.content}`); + if (message.attachments) { + message.attachments.forEach((attachment) => { + kickEmbed.addField('Attachment', `${attachment.url}`); + }); + } + await (client.channels.cache.get(NOTIF_CHANNEL_ID) as TextChannel).send(kickEmbed); +}; From 465144122f97aad7e46b1bd74cbb7b689b4f62b2 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Sat, 5 Feb 2022 21:29:04 -0500 Subject: [PATCH 8/9] Modified config variables in README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01ea7546..e0f5765c 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,11 @@ Set these accordingly within the correct config folder. If you are testing locally, use the `/config/dev` folder for local configurations. -- `TARGET_GUILD_ID`: the guild (server) in which coffee chats are being held. -- `COFFEE_ROLE_ID`: the ID of the role the bot will use to decide who is enrolled into coffeechats. +- `TARGET_GUILD_ID`: the ID of the guild (server) in which coffee chats are being held. +- `COFFEE_ROLE_ID`: the ID of the role the bot will use to decide who is enrolled into coffee chats. - `NOTIF_CHANNEL_ID`: the ID of the channel the bot will send system notifications to. +- `HONEYPOT_CHANNEL_ID`: the ID of the honeypot channel, used to help detect spammers and trolls. +- `PC_CATEGORY_ID`: the ID of the Program Committee category, containing the Program Committee channels. ## Required environment variables From e69656a8190798cfda844fd1ba98c578a3a28d52 Mon Sep 17 00:00:00 2001 From: Picowchew <58180935+Picowchew@users.noreply.github.com> Date: Sun, 6 Feb 2022 00:24:33 -0500 Subject: [PATCH 9/9] Also send embed when kick is unsuccessful --- src/components/messageListener.ts | 18 ++++++++++-------- src/utils/embeds.ts | 5 +++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/messageListener.ts b/src/components/messageListener.ts index 7255ce19..5fb7b16b 100644 --- a/src/components/messageListener.ts +++ b/src/components/messageListener.ts @@ -33,16 +33,18 @@ const detectSpammersAndTrollsNotByHoneypot = (message: Message): boolean => { const punishSpammersAndTrolls = async (message: Message): Promise => { if (detectSpammersAndTrollsNotByHoneypot(message) || message.channel.id === HONEYPOT_CHANNEL_ID) { // Delete the message, and if the user is still in the server, then kick them and log it - try { - await message.delete(); - if (message.member) { - const user = message.member.user; - const reason = 'Spammer/troll/got hacked'; + await message.delete(); + if (message.member) { + const user = message.member.user; + const reason = 'Spammer/troll/got hacked'; + let isSuccessful = true; + try { await message.member.kick(reason); - await sendKickEmbed(message, user, reason); + } catch (err) { + isSuccessful = false; + logError(err as Error); } - } catch (err) { - logError(err as Error); + await sendKickEmbed(message, user, reason, isSuccessful); } return true; } diff --git a/src/utils/embeds.ts b/src/utils/embeds.ts index 62647f95..8232b726 100644 --- a/src/utils/embeds.ts +++ b/src/utils/embeds.ts @@ -9,12 +9,13 @@ export const EMBED_COLOUR = '#0099ff'; /* * Send kick embed */ -export const sendKickEmbed = async (message: Message, user: User, reason = ''): Promise => { +export const sendKickEmbed = async (message: Message, user: User, reason = '', isSuccessful = true): Promise => { const kickEmbed = new MessageEmbed() .setColor(EMBED_COLOUR) - .setTitle('Kick') .addField('User', `${user.tag} (${user.id})`) .setFooter(`Message ID: ${message.id} • ${new Date().toUTCString()}`); + if (isSuccessful) kickEmbed.setTitle('Kick'); + else kickEmbed.setTitle('Kick Unsuccessful'); if (reason) kickEmbed.addField('Reason', `${reason}`); kickEmbed.addField('Channel', `<#${message.channel.id}> (${(message.channel as TextChannel).name})`); if (message.content) kickEmbed.addField('Content', `${message.content}`);