work in progress
This commit is contained in:
commit
c3b9d2d2dd
18
.eslintrc
18
.eslintrc
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser", // Specifies the ESLint parser
|
||||
"env": {
|
||||
"ecmaVersion": 2020 // Allows for the parsing of modern ECMAScript features
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
"plugin:prettier/recommended"
|
||||
]
|
||||
"parser": "@typescript-eslint/parser", // Specifies the ESLint parser
|
||||
"env": {
|
||||
"ecmaVersion": 2020 // Allows for the parsing of modern ECMAScript features
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
"plugin:prettier/recommended"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/node_modules
|
||||
.env
|
||||
/logs
|
||||
/dist
|
||||
/db/*.db
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"semi": true,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
"semi": true,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
# Codey Bot
|
||||
|
||||
## Required environment variables
|
||||
|
||||
- `BOT_TOKEN`: the token found in the bot user account.
|
||||
- `NOTIF_CHANNEL_ID`: the ID of the channel the bot will send system notifications to.
|
||||
|
||||
## Running the bot locally
|
||||
|
||||
1. Run `yarn` to install dependencies.
|
||||
1. Add the required environment variables in a `.env` file in the root directory.
|
||||
1. Run `yarn dev` to start the bot locally.
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import sqlite3 = require('sqlite3')
|
||||
export const db = new sqlite3.Database('./db/bot.db', (err) =>{
|
||||
if(err){
|
||||
console.log(err.message)
|
||||
}
|
||||
});
|
||||
db.run('CREATE TABLE IF NOT EXISTS saved_data (msg_id INTEGER PRIMARY KEY,data TEXT NOT NULL);')
|
||||
import { open } from 'sqlite'
|
||||
|
||||
|
||||
export async function openDb () {
|
||||
return open({
|
||||
filename: './db/bot.db',
|
||||
driver: sqlite3.Database
|
||||
})
|
||||
}
|
||||
console.log('connected to db')
|
||||
|
|
224
index.ts
224
index.ts
|
@ -1,114 +1,136 @@
|
|||
import dotenv = require('dotenv')
|
||||
dotenv.config()
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import { Database } from 'sqlite'
|
||||
|
||||
import Discord = require('discord.js')
|
||||
import _ = require('lodash')
|
||||
import { db } from './components/db'
|
||||
import Discord from 'discord.js';
|
||||
import _ from 'lodash';
|
||||
import { openDb } from './components/db'
|
||||
import logger from './logger';
|
||||
|
||||
const NOTIF_CHANNEL_ID: string = process.env.NOTIF_CHANNEL_ID
|
||||
const BOT_TOKEN: string = process.env.BOT_TOKEN
|
||||
const BOT_PREFIX = "."
|
||||
const NOTIF_CHANNEL_ID: string = process.env.NOTIF_CHANNEL_ID || '.';
|
||||
const BOT_TOKEN: string = process.env.BOT_TOKEN || '.';
|
||||
let db : Database;
|
||||
const BOT_PREFIX = '.';
|
||||
const client = new Discord.Client();
|
||||
|
||||
const client = new Discord.Client()
|
||||
const parseCommand = (message: Discord.Message): { command: string | null; args: string[] } => {
|
||||
// extract arguments by splitting by spaces and grouping strings in quotes
|
||||
// e.g. .ping 1 "2 3" => ['ping', '1', '2 3']
|
||||
let args = message.content.slice(BOT_PREFIX.length).match(/[^\s"']+|"([^"]*)"|'([^']*)'/g);
|
||||
args = _.map(args, (arg) => {
|
||||
if (arg[0].match(/'|"/g) && arg[arg.length - 1].match(/'|"/g)) {
|
||||
return arg.slice(1, arg.length - 1);
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
// obtain the first argument after the prefix
|
||||
const firstArg = args.shift();
|
||||
if (!firstArg) return { command: null, args: [] };
|
||||
const command = firstArg.toLowerCase();
|
||||
return { command, args };
|
||||
};
|
||||
|
||||
const parseCommand = message => {
|
||||
// extract arguments by splitting by spaces and grouping strings in quotes
|
||||
// e.g. .ping 1 "2 3" => ['ping', '1', '2 3']
|
||||
let args = message.content.slice(BOT_PREFIX.length).match(/[^\s"']+|"([^"]*)"|'([^']*)'/g)
|
||||
args = _.map(args, arg => {
|
||||
if (arg[0].match(/'|"/g) && arg[arg.length-1].match(/'|"/g)) {
|
||||
return arg.slice(1,arg.length-1)
|
||||
const handleCommand = async (message: Discord.Message, command: string, args: string[]) => {
|
||||
// log command and its author info
|
||||
logger.info({
|
||||
event: 'command',
|
||||
messageId: message.id,
|
||||
author: message.author.id,
|
||||
authorName: message.author.username,
|
||||
channel: message.channel.id,
|
||||
command,
|
||||
args
|
||||
});
|
||||
|
||||
switch (command) {
|
||||
case 'ping':
|
||||
await message.channel.send('pong');
|
||||
|
||||
//dev testing commands
|
||||
case 'save':
|
||||
if (args.length < 1) {
|
||||
await message.channel.send('no args')
|
||||
return
|
||||
}
|
||||
return arg
|
||||
})
|
||||
const firstArg = args.shift()
|
||||
if (!firstArg) return
|
||||
const command = firstArg.toLowerCase()
|
||||
return { command, args }
|
||||
}
|
||||
await db.run('INSERT INTO saved_data (msg_id,data)' +
|
||||
'VALUES(?,?)', [message.id, args[0]]).then((stmt : any, lastID : any, changes : any) => {
|
||||
consoel
|
||||
})
|
||||
break
|
||||
case 'dump':
|
||||
//TODO: make messages be embeds to test
|
||||
let flag: boolean = true
|
||||
let outEmbed = new Discord.MessageEmbed().setColor('#0099ff').setTitle('Database Dump').setURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ').addField("test", "test", true)
|
||||
db.each('SELECT * FROM saved_data', (err, rows) => {
|
||||
if (err) {
|
||||
flag = false;
|
||||
return;
|
||||
}
|
||||
console.log(rows['msg_id'], rows['data'])
|
||||
outEmbed = outEmbed.addField(rows['msg_id'], rows['data'], true)
|
||||
console.log(outEmbed)
|
||||
})
|
||||
console.log(outEmbed)
|
||||
if (flag) {
|
||||
if (outEmbed.fields.values.length == 0) {
|
||||
await message.channel.send("empty")
|
||||
}
|
||||
else {
|
||||
await message.channel.send(outEmbed)
|
||||
}
|
||||
}
|
||||
else {
|
||||
await message.channel.send("error")
|
||||
}
|
||||
break;
|
||||
case 'clear':
|
||||
db.run('DELETE FROM saved_data', async (err : any) => {
|
||||
if (err) {
|
||||
await message.channel.send(err.message)
|
||||
return
|
||||
}
|
||||
await message.channel.send("Cleared.")
|
||||
|
||||
const handleCommand = async (message, command, args) => {
|
||||
switch(command) {
|
||||
case 'ping':
|
||||
await message.channel.send('pong')
|
||||
break
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
//dev testing commands
|
||||
case 'save':
|
||||
if(args.length<1){
|
||||
await message.channel.send('no args')
|
||||
return
|
||||
}
|
||||
db.run('INSERT INTO saved_data (msg_id,data)' +
|
||||
'VALUES(?,?)', [message.id, args[0]], async (err) =>{
|
||||
if(err){
|
||||
await message.channel.send(err)
|
||||
return
|
||||
}
|
||||
await message.channel.send('saved "'+args[0]+'" with id '+message.id+'.')
|
||||
})
|
||||
break
|
||||
case 'dump':
|
||||
//TODO: make messages be embeds to test
|
||||
let flag : boolean = true
|
||||
let outEmbed = new Discord.MessageEmbed().setColor('#0099ff').setTitle('Database Dump').setURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ').addField("test", "test", true)
|
||||
await db.each('SELECT * FROM saved_data', (err, rows) =>{
|
||||
if(err){
|
||||
flag = false;
|
||||
return;
|
||||
}
|
||||
console.log(rows['msg_id'], rows['data'])
|
||||
outEmbed = outEmbed.addField(rows['msg_id'], rows['data'], true)
|
||||
console.log(outEmbed)
|
||||
})
|
||||
console.log(outEmbed)
|
||||
if(flag){
|
||||
if(outEmbed.fields.values.length == 0){
|
||||
await message.channel.send("empty")
|
||||
}
|
||||
else{
|
||||
await message.channel.send(outEmbed)
|
||||
}
|
||||
}
|
||||
else{
|
||||
await message.channel.send("error")
|
||||
}
|
||||
break;
|
||||
case 'clear':
|
||||
db.run('DELETE FROM saved_data', async (err) =>{
|
||||
if(err){
|
||||
await message.channel.send(err)
|
||||
return
|
||||
}
|
||||
await message.channel.send("Cleared.")
|
||||
const handleMessage = async (message: Discord.Message) => {
|
||||
// ignore messages without bot prefix and messages from other bots
|
||||
if (!message.content.startsWith(BOT_PREFIX) || message.author.bot) return;
|
||||
// obtain command and args from the command message
|
||||
const { command, args } = parseCommand(message);
|
||||
if (!command) return;
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleMessage = async message => {
|
||||
// ignore messages without bot prefix and messages from other bots
|
||||
if (!message.content.startsWith(BOT_PREFIX) || message.author.bot) return
|
||||
// obtain command and args from the command message
|
||||
const { command, args } = parseCommand(message)
|
||||
// TODO: log commands
|
||||
|
||||
try {
|
||||
await handleCommand(message, command, args)
|
||||
} catch(e) {
|
||||
// TODO: handle error
|
||||
}
|
||||
}
|
||||
try {
|
||||
await handleCommand(message, command, args);
|
||||
} catch (e) {
|
||||
// log error
|
||||
logger.error({
|
||||
event: 'error',
|
||||
messageId: message.id,
|
||||
command: command,
|
||||
args: args,
|
||||
error: e
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const startBot = async () => {
|
||||
client.once('ready', async () => {
|
||||
const notif = await client.channels.fetch(NOTIF_CHANNEL_ID) as Discord.TextChannel
|
||||
notif.send('Codey is up!')
|
||||
})
|
||||
client.once('ready', async () => {
|
||||
// log bot init event and send system notification
|
||||
logger.info({
|
||||
event: 'init'
|
||||
});
|
||||
const notif = (await client.channels.fetch(NOTIF_CHANNEL_ID)) as Discord.TextChannel;
|
||||
notif.send('Codey is up!');
|
||||
});
|
||||
|
||||
client.on('message', handleMessage)
|
||||
db = await openDb();
|
||||
console.log(db)
|
||||
client.on('message', handleMessage);
|
||||
|
||||
client.login(BOT_TOKEN)
|
||||
}
|
||||
client.login(BOT_TOKEN);
|
||||
};
|
||||
|
||||
startBot()
|
||||
startBot();
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
const winston = require('winston');
|
||||
require('winston-daily-rotate-file');
|
||||
|
||||
const dailyRotateTransport = new winston.transports.DailyRotateFile({
|
||||
filename: '%DATE%.log',
|
||||
dirname: 'logs',
|
||||
zippedArchive: true
|
||||
});
|
||||
|
||||
const dailyRotateErrorTransport = new winston.transports.DailyRotateFile({
|
||||
filename: 'error-%DATE%.log',
|
||||
dirname: 'logs',
|
||||
zippedArchive: true,
|
||||
level: 'error'
|
||||
});
|
||||
|
||||
const consoleTransport = new winston.transports.Console({
|
||||
format: winston.format.prettyPrint()
|
||||
});
|
||||
|
||||
const logger = winston.createLogger({
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.printf(
|
||||
({ level, message, timestamp }: { level: string; message: string; timestamp: string }) =>
|
||||
`[${timestamp}] ${level}: ${JSON.stringify(message)}`
|
||||
)
|
||||
),
|
||||
transports: [dailyRotateTransport, dailyRotateErrorTransport]
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
logger.add(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.prettyPrint()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default logger;
|
15
package.json
15
package.json
|
@ -4,21 +4,32 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon index.ts",
|
||||
"dev": "concurrently \"yarn run-dev\" \"yarn watch\"",
|
||||
"run-dev": "NODE_ENV=dev nodemon index.ts",
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/sqlite3": "^3.1.7",
|
||||
"concurrently": "^6.1.0",
|
||||
"discord.js": "^12.5.3",
|
||||
"dotenv": "^8.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"sqlite": "^4.0.22",
|
||||
"sqlite3": "^5.0.2",
|
||||
"typescript": "^4.2.4"
|
||||
"typescript": "^4.2.4",
|
||||
"winston": "^3.3.3",
|
||||
"winston-daily-rotate-file": "^4.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^15.0.1",
|
||||
"@types/winston": "^2.4.4",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"eslint": "^7.25.0",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "@tsconfig/node14/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"preserveConstEnums": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
Loading…
Reference in New Issue