dockerized local dev env
dockerize init using docker compose to manage container removed debug using npm instead of yarn when building image change container workdir persist logs and db using volumes
This commit is contained in:
parent
f50e1ba47a
commit
cf38d5b181
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -1,5 +1,5 @@
|
|||
/node_modules
|
||||
.env
|
||||
/logs
|
||||
/logs/*
|
||||
/dist
|
||||
/db
|
||||
/db/*
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
FROM node:16-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
RUN npm install
|
||||
|
||||
# Copy app files
|
||||
COPY . .
|
||||
|
||||
CMD [ "npm", "run", "local:run" ]
|
18
README.md
18
README.md
|
@ -5,8 +5,20 @@
|
|||
- `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.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Yarn](https://classic.yarnpkg.com/en/docs/install)
|
||||
- [Docker](https://docs.docker.com/get-docker/) (tested up to v20.10.6)
|
||||
|
||||
## 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. Build docker image: `yarn image:build`
|
||||
1. Start container in detached mode: `yarn start`
|
||||
1. View and follow console output: `yarn logs`
|
||||
|
||||
## Other usage
|
||||
|
||||
- Stop the container: `yarn stop`
|
||||
- Stop and remove the container: `yarn clean`
|
||||
- Restart the container: `yarn restart`
|
||||
- Fresh build and restart: `yarn image:build && yarn clean && yarn start`
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
codey-bot:
|
||||
image: codey:latest
|
||||
container_name: codey-bot
|
||||
environment:
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
volumes:
|
||||
- ./src:/usr/app/src
|
||||
- ./logs:/usr/app/logs
|
||||
- ./db:/usr/app/db
|
91
index.ts
91
index.ts
|
@ -1,92 +1,3 @@
|
|||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
import Discord from 'discord.js';
|
||||
import _ from 'lodash';
|
||||
import { openDB, testDb } 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 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 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');
|
||||
break;
|
||||
}
|
||||
|
||||
//dev testing
|
||||
if (process.env.NODE_ENV == 'dev') {
|
||||
testDb(message, command, args);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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 () => {
|
||||
// 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);
|
||||
|
||||
client.login(BOT_TOKEN);
|
||||
};
|
||||
import { startBot } from './src/bot';
|
||||
|
||||
startBot();
|
||||
|
|
14
package.json
14
package.json
|
@ -4,10 +4,16 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"yarn run-dev\" \"yarn watch\"",
|
||||
"run-dev": "cross-env NODE_ENV=dev nodemon index.ts",
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"local:run": "concurrently \"yarn node:watch\" \"yarn ts:watch\"",
|
||||
"node:watch": "NODE_ENV=dev nodemon index.ts",
|
||||
"ts:watch": "tsc --watch",
|
||||
"ts:build": "tsc",
|
||||
"image:build": "docker build . -t codey",
|
||||
"clean": "docker compose down",
|
||||
"stop": "docker compose stop",
|
||||
"start": "docker compose up -d",
|
||||
"restart": "docker compose restart",
|
||||
"logs": "docker logs -f codey-bot",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
import Discord from 'discord.js';
|
||||
import _ from 'lodash';
|
||||
import { openDB, testDb } 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 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 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');
|
||||
break;
|
||||
}
|
||||
|
||||
//dev testing
|
||||
if (process.env.NODE_ENV == 'dev') {
|
||||
testDb(message, command, args);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
try {
|
||||
await handleCommand(message, command, args);
|
||||
} catch (e) {
|
||||
// log error
|
||||
logger.error({
|
||||
event: 'error',
|
||||
messageId: message.id,
|
||||
command: command,
|
||||
args: args,
|
||||
error: e
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const startBot = async () => {
|
||||
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);
|
||||
|
||||
client.login(BOT_TOKEN);
|
||||
};
|
|
@ -1,22 +1,22 @@
|
|||
import sqlite3 = require('sqlite3')
|
||||
import { open, Database } from 'sqlite'
|
||||
import Discord from 'discord.js'
|
||||
import sqlite3 = require('sqlite3');
|
||||
import { open, Database } from 'sqlite';
|
||||
import Discord from 'discord.js';
|
||||
|
||||
let db : Database | null = null;
|
||||
let db: Database | null = null;
|
||||
|
||||
export async function openDB () {
|
||||
if(db == null){
|
||||
export async function openDB() {
|
||||
if (db == null) {
|
||||
db = await open({
|
||||
filename: './db/bot.db',
|
||||
filename: 'db/bot.db',
|
||||
driver: sqlite3.Database
|
||||
})
|
||||
await db.run('CREATE TABLE IF NOT EXISTS saved_data (msg_id INTEGER PRIMARY KEY,data TEXT NOT NULL);')
|
||||
});
|
||||
await db.run('CREATE TABLE IF NOT EXISTS saved_data (msg_id INTEGER PRIMARY KEY,data TEXT NOT NULL);');
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function testDb(message: Discord.Message, command: string, args: string[]){
|
||||
switch(command){
|
||||
export async function testDb(message: Discord.Message, command: string, args: string[]) {
|
||||
switch (command) {
|
||||
case 'save':
|
||||
if (args.length < 1) {
|
||||
await message.channel.send('no args');
|
||||
|
@ -35,7 +35,7 @@ export async function testDb(message: Discord.Message, command: string, args: st
|
|||
.setTitle('Database Dump')
|
||||
.setURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
||||
const res = await db.all('SELECT * FROM saved_data');
|
||||
for(const rows of res){
|
||||
for (const rows of res) {
|
||||
console.log(rows['msg_id'], rows['data']);
|
||||
outEmbed = outEmbed.addField(rows['msg_id'], rows['data'], true);
|
||||
console.log(outEmbed);
|
||||
|
@ -65,4 +65,4 @@ export async function testDb(message: Discord.Message, command: string, args: st
|
|||
}
|
||||
}
|
||||
|
||||
console.log('connected to db')
|
||||
console.log('connected to db');
|
Loading…
Reference in New Issue