Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sapphire Version 1 Completed #2

Open
wants to merge 68 commits into
base: production
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
e5abfe7
added initial pass on punishments table
demitchell14 Jul 24, 2021
c2a08c3
allowed endsAt to be null and userId to be a string
demitchell14 Jul 24, 2021
0fe64fd
added PunishmentController to handle sending discord messages and upd…
demitchell14 Jul 24, 2021
e62c0e3
added lenientRoles
demitchell14 Jul 24, 2021
a68c54e
added active flag, getAllActive() function, getAllLatest() function
demitchell14 Jul 24, 2021
c623719
initial synchronization pass
demitchell14 Jul 24, 2021
e99d3c1
added guild ID to config
demitchell14 Jul 24, 2021
44da7c3
punishments now take place immediately instead of relying solely on s…
demitchell14 Jul 24, 2021
400bacf
switched from Commando to Sapphire framework
demitchell14 Aug 26, 2021
b5e94e8
implemented new logger
demitchell14 Aug 26, 2021
dc07b7b
updated logger
demitchell14 Aug 26, 2021
dc382b7
Merge branch 'punshments' into sapphire
demitchell14 Aug 26, 2021
fe07321
bugfix
demitchell14 Aug 26, 2021
8c8716f
lintfixes
demitchell14 Aug 26, 2021
37ec200
upgraded to `@sapphire/framework@next` to utilize Discord 13
demitchell14 Sep 3, 2021
c5350fa
first pass on changing how punishments work.
demitchell14 Sep 9, 2021
e81d3c2
added dryrun and muteRole configuration
demitchell14 Sep 9, 2021
0e9736a
added a clean script and attached it to the build process
demitchell14 Sep 9, 2021
e28638b
removed standard punishments as they are redundant since we will be u…
demitchell14 Sep 9, 2021
a04679d
renamed punishments to punishment history
demitchell14 Sep 10, 2021
df2d5bd
added start of punishments table which will contain all types of puni…
demitchell14 Sep 10, 2021
6ab0f46
updated how errors are implemented
demitchell14 Sep 10, 2021
825b0f8
updated how errors are implemented
demitchell14 Sep 10, 2021
40fdb0b
refactored types and added a second unique index
demitchell14 Sep 10, 2021
7f1b005
Start of command passed punishment management
demitchell14 Sep 10, 2021
2e13518
removed the `block` config
demitchell14 Sep 10, 2021
749795d
changed db
demitchell14 Sep 10, 2021
a45a04e
made minutes optional
demitchell14 Sep 10, 2021
7bce85f
changed init and changed message
demitchell14 Sep 10, 2021
bd7e953
now fetches punishments from database and added isMonitoredUser funct…
demitchell14 Sep 10, 2021
2c6340f
renamed BlockedUsers to MonitoredUsers
demitchell14 Sep 10, 2021
c6b86f7
removed unnecessary log
demitchell14 Sep 10, 2021
68f5872
isMonitoredUser is now a promise, as it fetches the user to try to ge…
demitchell14 Sep 10, 2021
ccc27df
punishment commands now required administrator
demitchell14 Sep 10, 2021
562b202
Messages sent in both excludedChannels and notifyChannels, and comman…
demitchell14 Sep 10, 2021
0463ae7
added synchronization after creating punishments
demitchell14 Sep 10, 2021
2da4477
fetch not required
demitchell14 Sep 11, 2021
7ef76fa
added moderatorRoles to config
demitchell14 Sep 11, 2021
334d266
added sentByAuthorizedUser function to compare roles against moderati…
demitchell14 Sep 11, 2021
a97ea05
will not execute for authorized users (moderators)
demitchell14 Sep 11, 2021
b7e7799
refactored isMonitoredUser(message) to isMonitoredMember(author) for …
demitchell14 Sep 11, 2021
8bd813d
punishment commands can only be executed by authorize users (moderators)
demitchell14 Sep 11, 2021
a8e1c64
removed unused listener
demitchell14 Sep 11, 2021
fa585c0
added parseUserOrRole function to parse a user or role into a discord…
demitchell14 Sep 11, 2021
0b28fbe
removed null type from results
demitchell14 Sep 11, 2021
56ecc41
added index to parse
demitchell14 Sep 11, 2021
0853be8
removed types and added getPunishmment function
demitchell14 Sep 11, 2021
b336f03
import update
demitchell14 Sep 11, 2021
83ab9c3
added help, for, and list commands
demitchell14 Sep 11, 2021
b80b44d
removed intent
demitchell14 Sep 11, 2021
7c72506
added ability to remove a punishment
demitchell14 Sep 11, 2021
90d3ff8
refactored getRoleOrUser out
demitchell14 Sep 11, 2021
cb0aa3e
added remove command
demitchell14 Sep 11, 2021
18d9a30
punishments only sync when changed
demitchell14 Sep 11, 2021
6ba6d66
added order by
demitchell14 Sep 11, 2021
c7e2060
bugfix
demitchell14 Sep 11, 2021
3a309e4
prettier
demitchell14 Sep 11, 2021
1b32d5b
eslint fixes
demitchell14 Sep 11, 2021
79fe0a1
added kick functionality
demitchell14 Sep 11, 2021
3f064c2
added documentation
demitchell14 Oct 1, 2021
3471c59
refactored to MessageCreate as Message is deprecated
demitchell14 Oct 1, 2021
515b584
prettier lint fix
demitchell14 Oct 1, 2021
1106d06
reimplemented notification removal
demitchell14 Oct 1, 2021
7f4470e
updated docs
demitchell14 Oct 1, 2021
74fb47c
added esling workflow
demitchell14 Oct 1, 2021
ae7d222
removed dummy ping command
demitchell14 Oct 1, 2021
2a3019c
Update commands.md
demitchell14 Oct 1, 2021
3235714
lintfix
demitchell14 Oct 1, 2021
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
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"no-plusplus": "off",
"no-shadow": "off",
"no-return-assign": "off",
"no-await-in-loop": "off",
"class-methods-use-this": "off",
"no-continue": "off",
"no-restricted-syntax": "off",
Expand Down
21 changes: 21 additions & 0 deletions .github/workflow/eslint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: ESLint

on:
push:
branches: [ production ]
pull_request:
branches: [ production ]

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install modules
run: yarn
- name: Run ESLint
run: yarn lint
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Discord Ping Blocker
![ESLint](https://github.com/NewCircuit/ping-pewds/actions/workflows/eslint.yml/badge.svg?style=flat-square)

This is a Discord bot written in Typescript, using Discord Commando.
# Discord Ping Blocker (ping-pewds)

### This is a Discord bot written in Typescript, using the [Sapphire Framework](https://www.sapphirejs.dev/)

#### [View Available Commands](docs/commands.md)

## Requirements
* [NodeJS](https://nodejs.org/)
Expand Down Expand Up @@ -39,15 +43,20 @@ npm run pm2
### Bot specific configuration
|Key|Default|Description|
|---|---|---|
|dryrun|`false`|Prevents actually performing the discord punishment, however will insert a record into the database|
|token|None|The oauth bot token provided by Discord|
|prefix|`!`|The string the bot looks for to indicate a command is fired|
|guild|None|The Guild ID to monitor (currently only watches one guild)|
|owners|`[]`|An array of owner snowflakes|
|blockTimeout|`10`|The delay in minutes to stop blocking pings if a `block`ed user has spoken|
|block|`[]`|The user IDs that the bot will listen for and block mentions of|
|notifyTimeout|`10`|The delay in minutes until `notifyRoles` are notified that a `block`ed user is no longer active|
|notifyRoles|`[]`|The role IDs that will get notified when a `block`ed user becomes active|
|notifyChannels|`[]`|The channel IDs that a message will be sent to notify `notifyRoles`|
|excludedChannels|`[]`|The channel IDs that the bot will completely ignore|
|moderatorRoles|`[]`|The role IDs that the bot should consider moderators|
|lenientRoles|`[]`|The role IDs that should be considered lenient when punishing|
|muteRole|`''`|The role ID that will be assigned to *mute* them.|

### Database specific configuration
|Key|Default|Description|
Expand Down
6 changes: 5 additions & 1 deletion config.default.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
bot:
dryrun: false # this skips the actual punishment if true, but displays and updates database as usual
token: 'BotToken'
prefix: '!'
guild: '' # the guild that the bot will be attached to
owners: ['id1', 'id2']
blockTimeout: 10 # delay in minutes to stop blocking pings if a specific user has spoken
block: [] # User IDs that will the bot will listen for mentions of
notifyTimeout: 10 # delay in minutes until notified roles will be alerted that the blocked users are no longer active
notifyRoles: [] # Role IDs that will get notified when a blocked user is active
notifyChannels: [] # Channel IDs that will get notified when a blocked user is active
excludedChannels: [] # Channel IDs that should be ignored by the bot
moderatorRoles: [] # Role IDs that are considered moderators or administrators
lenientRoles: [] # Role IDs that when punished, will have lighter punishments
muteRole: '' # the Role ID that will be assigned to muted users
database:
host: 'localhost'
port: 5432
Expand Down
107 changes: 107 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Commands provided by this bot

This guide assumes that the command prefix is `!`. Wherever you see the `!` symbol, you should assume
that this is whatever the prefix is set to by the administrators.

## Monitored User Commands
##### These are commands that are only available to users who should have ping protection.
____

### Extend Ping Window
```
!extend <TIME>
```
* `<TIME>` - The number of minutes that you will be allowed to be pinged

This is primarily meant to act as an AFK button. If you plan to leave for longer than
the standard ping window and you want to still be pinged, you can *extend* the time you'll be
allowed to be pinged. **If you speak after running this command in a monitored channel**,
your ping window will be reset back to the standard ping window.

<div style="margin-bottom:50px"></div>

### Clear Ping Window
```
!clear
```
Immediately stops you from being pinged. Anyone who pings you after you enter this command
will be punished.


____

## Moderation Commands
##### These commands are used to maintain punishment options and should only be available to those with a moderator role (that is contained in `moderatorRoles` in configuration)
___

### Punishments Help
```
!punishments [help]
```
* `help` - is optional

Will give you an inline guide on what each sub command should do.

<div style="margin-bottom:50px"></div>

### Punishments List
```
!punishments list
```

Returns several embeds depending on how many users and roles are monitored.
Below is an example output

![Demo Punishments list](images/punishments_list.png)

<div style="margin-bottom:50px"></div>

### Punishment Create
```
!punishments create <INDEX> <TYPE> <TARGET> <KEY> <LIENIENT> <LENGTH>
```
* `<INDEX>` - The priority in which the punishment is given out. Lower priority is given first
* `<TYPE>` - `ban`, `mute`, or `kick`
* `<TARGET>` - `user` or `role`
* `<KEY>` - Either a user ID or role ID depending on what `<TARGET>` you provide
* `<LENIENT>` - yes/no/true/false (booleanish)
* `<LENGTH>` - The amount of time in minutes the punishment should be

This function is used to create punishment options for roles and users.
You have the ability to define multiple punishments for virtually any person or role
within the guild.

<div style="margin-bottom:50px"></div>

### Punishment Remove
```
!punishments remove <INDEX> <TARGET> <KEY> <LENIENT>
```
* `<INDEX>` - The priority in which the punishment is given out. Lower priority is given first
* `<TARGET>` - `user` or `role`
* `<KEY>` - Either a user ID or role ID depending on what `<TARGET>` you provide
* `<LENIENT>` - yes/no/true/false (booleanish)

This function is used to remove a punishment option for roles or roles. You will
need to know all the information above, **but** you can get this information
by running `!punishments list`.

![Punishment Legend](images/punishment_legend.png)

* **Green** - this is your index
* **Pink** - this is your target
* **orange** - this is your key
* **Blue** - this is lenient or not

<div style="margin-bottom:50px"></div>

### Punishments For User
```
!punishments for <USER>
```
* `<USER>` - the user to look up punishments

This returns an embed containing the punishments that
the user has received in the past.

![Punishment History](images/punishment_history.png)
Binary file added docs/images/punishment_history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/punishment_legend.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/punishments_list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 15 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ping-pewds",
"description": "A discord bot that blocks pings of specified users, unless they are actively talking",
"version": "1.0.0",
"version": "1.1.0",
"main": "lib/index.js",
"license": "GPL-3.0",
"author": {
Expand All @@ -14,20 +14,26 @@
},
"scripts": {
"start": "node ./lib/index.js",
"dev": "nodemon --watch src/**/* -e ts,json --exec node --inspect=9229 -r ts-node/register src",
"debug": "node --inspect=9229 ./lib/index.js",
"dev": "yarn build && yarn debug",
"build": "yarn clean && tsc",
"test-database": "mocha -r ts-node/register \"src/database/tests/**/runner.test.ts\" --watch --watch-files **/*.ts",
"build": "tsc",
"pm2": "pm2 start ecosystem.config.js",
"prettier": "prettier --write \"./src/**/*.{ts,tsx}\"",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --ext .ts src/ --fix"
"lint:fix": "eslint --ext .ts src/ --fix",
"prettier": "prettier --write \"./src/**/*.{ts,tsx}\"",
"pm2": "pm2 start ecosystem.config.js",
"clean": "rimraf ./lib"
},
"dependencies": {
"@sapphire/framework": "next",
"@sapphire/plugin-logger": "next",
"@sapphire/decorators": "next",
"@sapphire/plugin-subcommands": "next",
"async-mutex": "^0.3.1",
"discord.js": "^12.5.3",
"discord.js-commando": "^0.12.3",
"discord.js": "13.1.0",
"js-yaml": "^4.1.0",
"pg": "^8.6.0",
"pg-protocol": "^1.5.0",
"winston": "^3.3.3"
},
"devDependencies": {
Expand All @@ -47,6 +53,7 @@
"mocha": "^9.0.2",
"nodemon": "^2.0.7",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"ts-node": "^10.0.0",
"typescript": "^4.3.4"
}
Expand Down
99 changes: 35 additions & 64 deletions src/Bot.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import { CommandoClient, CommandoMessage } from 'discord.js-commando';
import winston from 'winston';
import { SapphireClient } from '@sapphire/framework';
import { CONFIG } from './globals';
import getLogger from './utils/logger';
import IssueHandler from './IssueHandler';
import EventHandler from './EventHandler';
import { BotLogger } from './utils/logger';
import { PingableUserController } from './controllers/PingableUserController';
import DatabaseManager from './database/database';
import { PingController } from './controllers/PingController';
import ExtendTimeout from './commands/pingable/extendtimeout';
import ClearTimeout from './commands/pingable/cleartimeout';

class Bot extends CommandoClient {
private readonly events: EventHandler;
import PunishmentController from './controllers/PunishmentController';

class Bot extends SapphireClient {
private readonly pingableUserController: PingableUserController;

private readonly pingController: PingController;

private readonly punishmentController: PunishmentController;

private readonly database: DatabaseManager;

private readonly synchronizeTimeout: number;

private synchronizeLoop: NodeJS.Timeout | undefined;

constructor() {
super({
commandPrefix: CONFIG.bot.prefix,
owner: CONFIG.bot.owners,
intents: ['GUILDS', 'GUILD_MESSAGE_REACTIONS', 'GUILD_MESSAGES'],
defaultPrefix: CONFIG.bot.prefix,
id: CONFIG.bot.token,
caseInsensitiveCommands: true,
logger: {
instance: BotLogger.getInstance(),
},
});

this.synchronizeTimeout = 1000 * 60; // 1 minute

this.database = new DatabaseManager({
host: CONFIG.database.host,
port: CONFIG.database.port,
Expand All @@ -37,64 +44,24 @@ class Bot extends CommandoClient {

this.pingableUserController = new PingableUserController(this);
this.pingController = new PingController(this);

this.events = new EventHandler(this);
this.registerEvents();

this.dispatcher.addInhibitor(this.inhibitor.bind(this));

this.registry
.registerDefaults()
.registerDefaultGroups()
.registerDefaultCommands({
help: false,
unknownCommand: false,
commandState: false,
eval: false,
prefix: false,
ping: false,
})
.registerGroups([['pingable', 'Commands for Pingable users']]);
// .registerCommandsIn(path.join(__dirname, './commands'))

// TODO replace this with .registerCommandsIn()
// for some reason I can't get the commands to load from there
// so I am forced to register them manually
this.registry.registerCommands([ExtendTimeout, ClearTimeout]);
this.punishmentController = new PunishmentController(this);
}

/**
* start the bot by initializing the database and controllers, then logging into Discord.
*/
public async start(): Promise<void> {
await this.database.init();
await this.pingableUserController.init();
await this.login(CONFIG.bot.token);
}

/**
* Register message listeners and command handlers
* @private
*/
private registerEvents(): void {
const issues = new IssueHandler();
this.on('commandError', issues.onCommandError.bind(issues))
.on('commandRun', issues.onCommandRun.bind(issues))
.on('commandRegister', issues.onCommandRegister.bind(issues));

this.on('message', this.events.onMessage.bind(this.events));

this.once('ready', this.events.onReady.bind(this.events));
}

/**
* checks to see if the message starts with commandPrefix and message is not within a guild
* @param msg
* @private
*/
private inhibitor(msg: CommandoMessage): false | string {
const passes = msg.content.startsWith(this.commandPrefix) && msg.guild !== null;
return passes ? false : '';
await this.punishmentController.synchronize(true);
await this.pingableUserController.init();
this.synchronizeLoop = setInterval(
() =>
this.punishmentController
.synchronize()
.catch((err) => Bot.getLogger('synchronization').error(err)),
this.synchronizeTimeout
);
}

public getPingableUserController(): PingableUserController {
Expand All @@ -105,12 +72,16 @@ class Bot extends CommandoClient {
return this.pingController;
}

public getPunishmentController(): PunishmentController {
return this.punishmentController;
}

public getDatabase(): DatabaseManager {
return this.database;
}

public static getLogger(section: string): winston.Logger {
return getLogger(`Bot::${section}`);
public static getLogger(section: string): BotLogger {
return BotLogger.getInstance().getTitledInstance(section);
}
}

Expand Down
Loading