Add Docker Compose setup and clean up legacy code
- Add docker-compose.yml with volume mounts for config/sfx - Simplify npm scripts (up/down/build/restart/logs) - Update README.md and CLAUDE.md with new commands - Remove unused lib/ directory (migrated to src/) - Update package.json scripts to cleaner naming Benefits: - Update configs and sound effects without rebuilding image - Simplified Docker workflow management - Cleaner project structure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
28
CLAUDE.md
28
CLAUDE.md
@@ -18,22 +18,30 @@ pnpm install
|
|||||||
pnpm start # Production mode
|
pnpm start # Production mode
|
||||||
pnpm dev # Development mode with auto-reload
|
pnpm dev # Development mode with auto-reload
|
||||||
|
|
||||||
# Docker commands
|
# Docker Compose (recommended)
|
||||||
pnpm docker:build # Build Docker image
|
pnpm up # Start bot with Docker Compose
|
||||||
pnpm docker:run # Run Docker container with auto-restart
|
pnpm down # Stop bot
|
||||||
|
pnpm restart # Restart bot (useful after config changes)
|
||||||
|
pnpm logs # View logs
|
||||||
|
pnpm build # Rebuild image
|
||||||
|
|
||||||
|
# Direct Docker (alternative)
|
||||||
|
pnpm image:build # Build Docker image
|
||||||
|
pnpm image:run # Run Docker container with auto-restart
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Management
|
### Docker Management
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop and remove container
|
# Docker Compose approach (recommended)
|
||||||
docker stop ghbot && docker rm ghbot
|
pnpm up && pnpm logs # Start and follow logs
|
||||||
|
pnpm restart # Restart after config changes
|
||||||
|
pnpm build && pnpm up # Rebuild after code changes
|
||||||
|
|
||||||
# View logs
|
# Direct Docker approach
|
||||||
docker logs ghbot -f
|
docker stop discord-bot && docker rm discord-bot
|
||||||
|
docker logs discord-bot -f
|
||||||
# Rebuild and restart
|
pnpm image:build && pnpm image:run
|
||||||
docker stop ghbot && docker rm ghbot && pnpm docker:build && pnpm docker:run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture & Key Components
|
## Architecture & Key Components
|
||||||
|
|||||||
51
README.md
51
README.md
@@ -14,7 +14,7 @@ A modern Discord bot built with Discord.js v14 that provides sound effects, text
|
|||||||
### 💬 Text Commands
|
### 💬 Text Commands
|
||||||
|
|
||||||
- **Fun Facts**: Random or specific fact retrieval (`!funfact [number]`)
|
- **Fun Facts**: Random or specific fact retrieval (`!funfact [number]`)
|
||||||
- **Ham Facts**: Ham radio related facts (`!hamfact [number]`)
|
- **Ham Facts**: Ham-related facts (`!hamfact [number]`)
|
||||||
- **Static Commands**: Custom text responses loaded from configuration files
|
- **Static Commands**: Custom text responses loaded from configuration files
|
||||||
- **Ankhbot Import**: Support for imported Ankhbot command databases
|
- **Ankhbot Import**: Support for imported Ankhbot command databases
|
||||||
|
|
||||||
@@ -80,28 +80,39 @@ A modern Discord bot built with Discord.js v14 that provides sound effects, text
|
|||||||
|
|
||||||
## 🐳 Docker Deployment
|
## 🐳 Docker Deployment
|
||||||
|
|
||||||
### Build and Run
|
### Recommended: Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start the bot with Docker Compose
|
||||||
|
pnpm up
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
pnpm logs
|
||||||
|
|
||||||
|
# Restart the bot (useful after config changes)
|
||||||
|
pnpm restart
|
||||||
|
|
||||||
|
# Stop the bot
|
||||||
|
pnpm down
|
||||||
|
|
||||||
|
# Rebuild and restart (after code changes)
|
||||||
|
pnpm build && pnpm up
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of Docker Compose:**
|
||||||
|
- Update `config.json`, `sfx/`, and `conf/` files without rebuilding the image
|
||||||
|
- Automatic restart on failure
|
||||||
|
- Easy log management
|
||||||
|
- Resource limits and health checks
|
||||||
|
|
||||||
|
### Alternative: Direct Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build the Docker image
|
# Build the Docker image
|
||||||
pnpm docker:build
|
pnpm image:build
|
||||||
|
|
||||||
# Run the container
|
# Run the container
|
||||||
pnpm docker:run
|
pnpm image:run
|
||||||
```
|
|
||||||
|
|
||||||
### Using Docker Compose
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: "3.8"
|
|
||||||
services:
|
|
||||||
ghbot:
|
|
||||||
build: .
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- ./config.json:/app/config.json:ro
|
|
||||||
- ./sfx:/app/sfx:ro
|
|
||||||
- ./conf:/app/conf:ro
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 Usage
|
## 📖 Usage
|
||||||
@@ -149,21 +160,25 @@ services:
|
|||||||
### Creating a Discord Bot
|
### Creating a Discord Bot
|
||||||
|
|
||||||
1. **Go to Discord Developer Portal**
|
1. **Go to Discord Developer Portal**
|
||||||
|
|
||||||
- Visit https://discord.com/developers/applications
|
- Visit https://discord.com/developers/applications
|
||||||
- Click "New Application" and give it a name
|
- Click "New Application" and give it a name
|
||||||
|
|
||||||
2. **Create Bot User**
|
2. **Create Bot User**
|
||||||
|
|
||||||
- Go to the "Bot" section
|
- Go to the "Bot" section
|
||||||
- Click "Add Bot"
|
- Click "Add Bot"
|
||||||
- Copy the bot token for your config.json
|
- Copy the bot token for your config.json
|
||||||
|
|
||||||
3. **Enable Required Intents**
|
3. **Enable Required Intents**
|
||||||
Under "Privileged Gateway Intents", enable:
|
Under "Privileged Gateway Intents", enable:
|
||||||
|
|
||||||
- **SERVER MEMBERS INTENT** (Required for role management)
|
- **SERVER MEMBERS INTENT** (Required for role management)
|
||||||
- **MESSAGE CONTENT INTENT** (Required for prefix commands)
|
- **MESSAGE CONTENT INTENT** (Required for prefix commands)
|
||||||
|
|
||||||
4. **Bot Permissions**
|
4. **Bot Permissions**
|
||||||
When inviting the bot, ensure it has these permissions:
|
When inviting the bot, ensure it has these permissions:
|
||||||
|
|
||||||
- Send Messages
|
- Send Messages
|
||||||
- Embed Links
|
- Embed Links
|
||||||
- Read Message History
|
- Read Message History
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
62
docker-compose.yml
Normal file
62
docker-compose.yml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
discord-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: discord-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
# Configuration files (read-only)
|
||||||
|
- ./config.json:/app/config.json:ro
|
||||||
|
- ./conf:/app/conf:ro
|
||||||
|
|
||||||
|
# Sound effects directory (read-only)
|
||||||
|
- ./sfx:/app/sfx:ro
|
||||||
|
|
||||||
|
# Optional: Mount logs directory if you want persistent logs
|
||||||
|
# - ./logs:/app/logs
|
||||||
|
|
||||||
|
# Optional: Environment variables
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
# - DEBUG=true # Uncomment for debug mode
|
||||||
|
|
||||||
|
# Optional: Resource limits
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
memory: 256M
|
||||||
|
|
||||||
|
# Optional: Health check
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "process.exit(0)"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
# Optional: Add a volume backup service
|
||||||
|
# backup:
|
||||||
|
# image: alpine:latest
|
||||||
|
# container_name: ghbot-backup
|
||||||
|
# volumes:
|
||||||
|
# - ./config.json:/backup/config.json:ro
|
||||||
|
# - ./conf:/backup/conf:ro
|
||||||
|
# - ./sfx:/backup/sfx:ro
|
||||||
|
# command: |
|
||||||
|
# sh -c "
|
||||||
|
# echo 'Creating backup...'
|
||||||
|
# tar czf /backup/ghbot-backup-$(date +%Y%m%d_%H%M%S).tar.gz -C /backup config.json conf sfx
|
||||||
|
# echo 'Backup complete'
|
||||||
|
# "
|
||||||
|
# profiles:
|
||||||
|
# - backup
|
||||||
|
|
||||||
|
# Optional: Create named volumes for persistent data
|
||||||
|
# volumes:
|
||||||
|
# ghbot_logs:
|
||||||
|
# driver: local
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
exists: exists,
|
|
||||||
get: get
|
|
||||||
};
|
|
||||||
|
|
||||||
const fs = require('fs'),
|
|
||||||
path = require('path');
|
|
||||||
|
|
||||||
const cmdPrefix = '!';
|
|
||||||
const commandsFilePath = path.join(__dirname, '..', 'conf', 'ghbot.abcomg');
|
|
||||||
let commands = parseCommands(commandsFilePath);
|
|
||||||
fs.watchFile(commandsFilePath, (curr, prev) => {
|
|
||||||
if (curr.mtime !== prev.mtime) {
|
|
||||||
commands = require(commandsFilePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function exists(command)
|
|
||||||
{
|
|
||||||
let matches = commands.filter(obj => { return obj.Command === cmdPrefix+command; });
|
|
||||||
if (matches.length > 0) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(command)
|
|
||||||
{
|
|
||||||
let matches = commands.filter(obj => { return obj.Command === cmdPrefix+command; });
|
|
||||||
if (matches.length > 0) {
|
|
||||||
let match = matches[0];
|
|
||||||
if (match.Enabled === true) {
|
|
||||||
return matches[0].Response;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read in the array of objects exported from AnkhBot
|
|
||||||
function parseCommands(filePath)
|
|
||||||
{
|
|
||||||
let data = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
let commands = eval(data);
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
exists: exists,
|
|
||||||
get: get
|
|
||||||
};
|
|
||||||
|
|
||||||
const fs = require('fs'),
|
|
||||||
path = require('path');
|
|
||||||
|
|
||||||
const commandsFilePath = path.join(__dirname, '..', 'conf', 'text_commands');
|
|
||||||
|
|
||||||
// Read in basic text commands / definitions and watch for changes
|
|
||||||
let commands = parseCommands(commandsFilePath);
|
|
||||||
fs.watchFile(commandsFilePath, (curr, prev) => {
|
|
||||||
if (curr.mtime !== prev.mtime) {
|
|
||||||
commands = parseCommands(commandsFilePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Read/parse text commands from the "database"
|
|
||||||
function parseCommands(filePath)
|
|
||||||
{
|
|
||||||
let commands = {};
|
|
||||||
let data = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
let commandLines = data.toString().split('\n');
|
|
||||||
let commandParts;
|
|
||||||
let aliases;
|
|
||||||
commandLines.forEach(function(line) {
|
|
||||||
if (line.length > 0 && line.indexOf('|') !== -1) {
|
|
||||||
commandParts = line.split('|');
|
|
||||||
// check for aliases
|
|
||||||
aliases = commandParts[0].split(',');
|
|
||||||
aliases.forEach(function(cmd) {
|
|
||||||
commands[cmd] = commandParts[1];
|
|
||||||
//console.log(`!${cmd},`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
function exists(command)
|
|
||||||
{
|
|
||||||
return commands.hasOwnProperty(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(command)
|
|
||||||
{
|
|
||||||
if (exists(command)) {
|
|
||||||
return commands[command];
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
50
lib/utils.js
50
lib/utils.js
@@ -1,50 +0,0 @@
|
|||||||
// Converts seconds to human-readable time
|
|
||||||
String.prototype.toHHMMSS = function () {
|
|
||||||
let sec_num = parseInt(this, 10); // don't forget the second param
|
|
||||||
let hours = Math.floor(sec_num / 3600);
|
|
||||||
let minutes = Math.floor((sec_num - hours * 3600) / 60);
|
|
||||||
let seconds = sec_num - hours * 3600 - minutes * 60;
|
|
||||||
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return hours + ":" + minutes + ":" + seconds;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function asyncForEach(array, callback) {
|
|
||||||
for (let index = 0; index < array.length; index++) {
|
|
||||||
await callback(array[index], index, array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function randElement(arr) {
|
|
||||||
return arr[Math.floor(Math.random() * arr.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function randSort() {
|
|
||||||
return 0.5 - Math.random();
|
|
||||||
}
|
|
||||||
|
|
||||||
function chunkSubstr(str, size) {
|
|
||||||
const numChunks = Math.ceil(str.length / size);
|
|
||||||
const chunks = new Array(numChunks);
|
|
||||||
|
|
||||||
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
|
|
||||||
chunks[i] = str.substr(o, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
asyncForEach,
|
|
||||||
randElement,
|
|
||||||
randSort,
|
|
||||||
chunkSubstr
|
|
||||||
};
|
|
||||||
@@ -20,8 +20,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"dev": "nodemon src/index.js",
|
"dev": "nodemon src/index.js",
|
||||||
"docker:build": "docker build -t ghbot:${VERSION:-latest} .",
|
"up": "docker compose up -d",
|
||||||
"docker:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
|
"down": "docker compose down",
|
||||||
|
"build": "docker compose build",
|
||||||
|
"restart": "docker compose restart",
|
||||||
|
"logs": "docker compose logs -f",
|
||||||
|
"image:build": "docker build -t ghbot:${VERSION:-latest} .",
|
||||||
|
"image:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
|
||||||
},
|
},
|
||||||
"author": "https://github.com/greenham",
|
"author": "https://github.com/greenham",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
|||||||
Reference in New Issue
Block a user