Skip to content

Commit ed599bd

Browse files
Merge branch 'logging' into 'master'
added logger and started logging commands and errors See merge request csc/discord-bot!2
2 parents 66c9560 + d29ae3a commit ed599bd

10 files changed

+741
-79
lines changed

.eslintrc

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"parser": "@typescript-eslint/parser", // Specifies the ESLint parser
3-
"env": {
4-
"ecmaVersion": 2020 // Allows for the parsing of modern ECMAScript features
5-
},
6-
"extends": [
7-
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
8-
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
9-
"plugin:prettier/recommended"
10-
]
2+
"parser": "@typescript-eslint/parser", // Specifies the ESLint parser
3+
"env": {
4+
"ecmaVersion": 2020 // Allows for the parsing of modern ECMAScript features
5+
},
6+
"extends": [
7+
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
8+
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
9+
"plugin:prettier/recommended"
10+
]
1111
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/node_modules
22
.env
3+
/logs
4+
/dist

.prettierrc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"semi": true,
3-
"trailingComma": "none",
4-
"singleQuote": true,
5-
"printWidth": 120
6-
}
2+
"semi": true,
3+
"trailingComma": "none",
4+
"singleQuote": true,
5+
"printWidth": 120
6+
}

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"editor.formatOnSave": true,
4+
"[javascript]": {
5+
"editor.defaultFormatter": "esbenp.prettier-vscode"
6+
}
7+
}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Codey Bot
22

33
## Required environment variables
4+
45
- `BOT_TOKEN`: the token found in the bot user account.
56
- `NOTIF_CHANNEL_ID`: the ID of the channel the bot will send system notifications to.
67

78
## Running the bot locally
9+
810
1. Run `yarn` to install dependencies.
911
1. Add the required environment variables in a `.env` file in the root directory.
1012
1. Run `yarn dev` to start the bot locally.

index.ts

Lines changed: 80 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,86 @@
1-
import dotenv = require('dotenv')
2-
dotenv.config()
3-
4-
import Discord = require('discord.js')
5-
import _ = require('lodash')
6-
7-
const NOTIF_CHANNEL_ID: string = process.env.NOTIF_CHANNEL_ID
8-
const BOT_TOKEN: string = process.env.BOT_TOKEN
9-
const BOT_PREFIX = "."
10-
11-
const client = new Discord.Client()
12-
13-
const parseCommand = message => {
14-
// extract arguments by splitting by spaces and grouping strings in quotes
15-
// e.g. .ping 1 "2 3" => ['ping', '1', '2 3']
16-
let args = message.content.slice(BOT_PREFIX.length).match(/[^\s"']+|"([^"]*)"|'([^']*)'/g)
17-
args = _.map(args, arg => {
18-
if (arg[0].match(/'|"/g) && arg[arg.length-1].match(/'|"/g)) {
19-
return arg.slice(1,arg.length-1)
20-
}
21-
return arg
22-
})
23-
const firstArg = args.shift()
24-
if (!firstArg) return
25-
const command = firstArg.toLowerCase()
26-
return { command, args }
27-
}
28-
29-
const handleCommand = async (message, command, args) => {
30-
switch(command) {
31-
case 'ping':
32-
await message.channel.send('pong')
33-
}
34-
}
35-
36-
const handleMessage = async message => {
37-
// ignore messages without bot prefix and messages from other bots
38-
if (!message.content.startsWith(BOT_PREFIX) || message.author.bot) return
39-
// obtain command and args from the command message
40-
const { command, args } = parseCommand(message)
41-
// TODO: log commands
42-
43-
try {
44-
await handleCommand(message, command, args)
45-
} catch(e) {
46-
// TODO: handle error
1+
import dotenv from 'dotenv';
2+
dotenv.config();
3+
4+
import Discord from 'discord.js';
5+
import _ from 'lodash';
6+
7+
import logger from './logger';
8+
9+
const NOTIF_CHANNEL_ID: string = process.env.NOTIF_CHANNEL_ID || '.';
10+
const BOT_TOKEN: string = process.env.BOT_TOKEN || '.';
11+
const BOT_PREFIX = '.';
12+
13+
const client = new Discord.Client();
14+
15+
const parseCommand = (message: Discord.Message): { command: string | null; args: string[] } => {
16+
// extract arguments by splitting by spaces and grouping strings in quotes
17+
// e.g. .ping 1 "2 3" => ['ping', '1', '2 3']
18+
let args = message.content.slice(BOT_PREFIX.length).match(/[^\s"']+|"([^"]*)"|'([^']*)'/g);
19+
args = _.map(args, (arg) => {
20+
if (arg[0].match(/'|"/g) && arg[arg.length - 1].match(/'|"/g)) {
21+
return arg.slice(1, arg.length - 1);
4722
}
48-
}
23+
return arg;
24+
});
25+
// obtain the first argument after the prefix
26+
const firstArg = args.shift();
27+
if (!firstArg) return { command: null, args: [] };
28+
const command = firstArg.toLowerCase();
29+
return { command, args };
30+
};
31+
32+
const handleCommand = async (message: Discord.Message, command: string, args: string[]) => {
33+
// log command and its author info
34+
logger.info({
35+
event: 'command',
36+
messageId: message.id,
37+
author: message.author.id,
38+
authorName: message.author.username,
39+
channel: message.channel.id,
40+
command,
41+
args
42+
});
43+
44+
switch (command) {
45+
case 'ping':
46+
await message.channel.send('pong');
47+
}
48+
};
49+
50+
const handleMessage = async (message: Discord.Message) => {
51+
// ignore messages without bot prefix and messages from other bots
52+
if (!message.content.startsWith(BOT_PREFIX) || message.author.bot) return;
53+
// obtain command and args from the command message
54+
const { command, args } = parseCommand(message);
55+
if (!command) return;
56+
57+
try {
58+
await handleCommand(message, command, args);
59+
} catch (e) {
60+
// log error
61+
logger.error({
62+
event: 'error',
63+
messageId: message.id,
64+
command: command,
65+
args: args,
66+
error: e
67+
});
68+
}
69+
};
4970

5071
const startBot = async () => {
51-
client.once('ready', async () => {
52-
const notif = await client.channels.fetch(NOTIF_CHANNEL_ID) as Discord.TextChannel
53-
notif.send('Codey is up!')
54-
})
72+
client.once('ready', async () => {
73+
// log bot init event and send system notification
74+
logger.info({
75+
event: 'init'
76+
});
77+
const notif = (await client.channels.fetch(NOTIF_CHANNEL_ID)) as Discord.TextChannel;
78+
notif.send('Codey is up!');
79+
});
5580

56-
client.on('message', handleMessage)
81+
client.on('message', handleMessage);
5782

58-
client.login(BOT_TOKEN)
59-
}
83+
client.login(BOT_TOKEN);
84+
};
6085

61-
startBot()
86+
startBot();

logger.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const winston = require('winston');
2+
require('winston-daily-rotate-file');
3+
4+
const dailyRotateTransport = new winston.transports.DailyRotateFile({
5+
filename: '%DATE%.log',
6+
dirname: 'logs',
7+
zippedArchive: true
8+
});
9+
10+
const dailyRotateErrorTransport = new winston.transports.DailyRotateFile({
11+
filename: 'error-%DATE%.log',
12+
dirname: 'logs',
13+
zippedArchive: true,
14+
level: 'error'
15+
});
16+
17+
const consoleTransport = new winston.transports.Console({
18+
format: winston.format.prettyPrint()
19+
});
20+
21+
const logger = winston.createLogger({
22+
format: winston.format.combine(
23+
winston.format.timestamp(),
24+
winston.format.printf(
25+
({ level, message, timestamp }: { level: string; message: string; timestamp: string }) =>
26+
`[${timestamp}] ${level}: ${JSON.stringify(message)}`
27+
)
28+
),
29+
transports: [dailyRotateTransport, dailyRotateErrorTransport]
30+
});
31+
32+
if (process.env.NODE_ENV === 'dev') {
33+
logger.add(
34+
new winston.transports.Console({
35+
format: winston.format.prettyPrint()
36+
})
37+
);
38+
}
39+
40+
export default logger;

package.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,29 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7-
"dev": "nodemon index.ts",
7+
"dev": "concurrently \"yarn run-dev\" \"yarn watch\"",
8+
"run-dev": "NODE_ENV=dev nodemon index.ts",
9+
"watch": "tsc --watch",
10+
"build": "tsc",
811
"test": "echo \"Error: no test specified\" && exit 1"
912
},
1013
"author": "",
1114
"license": "ISC",
1215
"dependencies": {
16+
"concurrently": "^6.1.0",
1317
"discord.js": "^12.5.3",
1418
"dotenv": "^8.2.0",
1519
"lodash": "^4.17.21",
16-
"typescript": "^4.2.4"
20+
"moment": "^2.29.1",
21+
"typescript": "^4.2.4",
22+
"winston": "^3.3.3",
23+
"winston-daily-rotate-file": "^4.5.5"
1724
},
1825
"devDependencies": {
26+
"@tsconfig/node14": "^1.0.0",
1927
"@types/lodash": "^4.14.168",
2028
"@types/node": "^15.0.1",
29+
"@types/winston": "^2.4.4",
2130
"@typescript-eslint/eslint-plugin": "^4.22.0",
2231
"@typescript-eslint/parser": "^4.22.0",
2332
"eslint": "^7.25.0",

tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "@tsconfig/node14/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"preserveConstEnums": true,
6+
"esModuleInterop": true
7+
},
8+
"include": ["**/*.ts"]
9+
}

0 commit comments

Comments
 (0)