mirror of
https://github.com/arthur-pbty/xiao.git
synced 2026-06-03 15:07:42 +02:00
Add Docker support to simplify deployment
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
||||
node_modules
|
||||
tmp
|
||||
logs
|
||||
|
||||
.env
|
||||
.env.keys
|
||||
|
||||
command-leaderboard.json
|
||||
command-last-run.json
|
||||
blacklist.json
|
||||
jeopardy.json
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
+29
-23
@@ -1,45 +1,51 @@
|
||||
# Discord-related info
|
||||
XIAO_TOKEN=
|
||||
XIAO_TOKEN=your_discord_bot_token
|
||||
# Separate OWNERS with a ,
|
||||
OWNERS=
|
||||
OWNERS=123456789012345678
|
||||
LOVER_USER_ID=
|
||||
XIAO_PREFIX=
|
||||
INVITE=
|
||||
XIAO_PREFIX=!
|
||||
INVITE=https://discord.gg/your-server
|
||||
REPORT_CHANNEL_ID=
|
||||
JOIN_LEAVE_CHANNEL_ID=
|
||||
|
||||
# Redis info
|
||||
REDIS_HOST=
|
||||
REDIS_PASS=
|
||||
# For docker-compose, keep REDIS_HOST=redis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PASS=change_me_redis_password
|
||||
|
||||
# Runtime
|
||||
TZ=UTC
|
||||
# Folder used to persist bot state files in Docker
|
||||
XIAO_STATE_DIR=/data
|
||||
|
||||
# Emoji IDs
|
||||
SUCCESS_EMOJI_ID=
|
||||
FAILURE_EMOJI_ID=
|
||||
GOLD_FISH_EMOJI_ID=
|
||||
GOLD_FISH_EMOJI_NAME=
|
||||
GOLD_FISH_EMOJI_NAME=Gold Fish
|
||||
MOCKING_EMOJI_ID=
|
||||
MOCKING_EMOJI_NAME=
|
||||
SILVER_FISH_EMOJI_ID=
|
||||
SILVER_FISH_EMOJI_NAME=
|
||||
SILVER_FISH_EMOJI_NAME=Silver Fish
|
||||
PORTAL_EMOJI_ID=
|
||||
PORTAL_EMOJI_NAME=
|
||||
PORTAL_EMOJI_NAME=PORTAL
|
||||
LOADING_EMOJI_ID=
|
||||
MEGA_EVOLVE_EMOJI_ID=
|
||||
MEGA_EVOLVE_EMOJI_NAME=
|
||||
MEGA_EVOLVE_EMOJI_NAME=MEGA
|
||||
NAME_RATER_EMOJI_ID=
|
||||
NAME_RATER_EMOJI_NAME=
|
||||
|
||||
# API Keys, IDs, and Secrets
|
||||
ANILIST_USERNAME=
|
||||
BITLY_KEY=
|
||||
CLEVERBOT_KEY=
|
||||
GITHUB_ACCESS_TOKEN=
|
||||
GOV_KEY=
|
||||
IDIOT_URL=
|
||||
REMOVEBG_KEY=
|
||||
SAUCENAO_KEY=
|
||||
THECATAPI_KEY=
|
||||
THEDOGAPI_KEY=
|
||||
WEBSTER_KEY=
|
||||
XIAO_GITHUB_REPO_NAME=
|
||||
XIAO_GITHUB_REPO_USERNAME=
|
||||
ANILIST_USERNAME=AniList
|
||||
BITLY_KEY=your_bitly_key
|
||||
CLEVERBOT_KEY=your_cleverbot_key
|
||||
GITHUB_ACCESS_TOKEN=your_github_token
|
||||
GOV_KEY=your_nasa_api_key
|
||||
IDIOT_URL=https://en.wikipedia.org/wiki/Idiot
|
||||
REMOVEBG_KEY=your_removebg_key
|
||||
SAUCENAO_KEY=your_saucenao_key
|
||||
THECATAPI_KEY=your_thecatapi_key
|
||||
THEDOGAPI_KEY=your_thedogapi_key
|
||||
WEBSTER_KEY=your_webster_key
|
||||
XIAO_GITHUB_REPO_NAME=xiao
|
||||
XIAO_GITHUB_REPO_USERNAME=xiaobotdev
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
FROM node:24-bookworm
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# System dependencies used by native modules and image/audio commands.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
ffmpeg \
|
||||
git \
|
||||
graphicsmagick \
|
||||
imagemagick \
|
||||
libgif-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
liblqr-1-0 \
|
||||
libpango1.0-dev \
|
||||
librsvg2-dev \
|
||||
make \
|
||||
g++ \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install --omit=dev && npm cache clean --force
|
||||
|
||||
COPY . .
|
||||
|
||||
# Refresh parse-domain data when available.
|
||||
RUN npx --yes parse-domain-update || true
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD ["node", "Xiao.js"]
|
||||
@@ -0,0 +1,38 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: xiao-redis
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- redis-server
|
||||
- --appendonly
|
||||
- "yes"
|
||||
- --requirepass
|
||||
- ${REDIS_PASS:-change_me_redis_password}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
||||
xiao:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: xiao-bot
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- redis
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
REDIS_HOST: ${REDIS_HOST:-redis}
|
||||
REDIS_PASS: ${REDIS_PASS:-change_me_redis_password}
|
||||
TZ: ${TZ:-UTC}
|
||||
XIAO_STATE_DIR: ${XIAO_STATE_DIR:-/data}
|
||||
volumes:
|
||||
- xiao-tmp:/app/tmp
|
||||
- xiao-state:/data
|
||||
- ./.env:/app/.env:ro
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
xiao-tmp:
|
||||
xiao-state:
|
||||
+36
-6
@@ -7,6 +7,21 @@ const Registry = require('./Registry');
|
||||
const Dispatcher = require('./Dispatcher');
|
||||
require('./Extensions');
|
||||
|
||||
const STATE_DIR = process.env.XIAO_STATE_DIR
|
||||
? path.resolve(process.env.XIAO_STATE_DIR)
|
||||
: path.join(__dirname, '..');
|
||||
|
||||
function resolveStatePath(fileName) {
|
||||
return path.join(STATE_DIR, fileName);
|
||||
}
|
||||
|
||||
function writeJsonFile(filePath, data) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
const buf = Buffer.from(`${JSON.stringify(data, null, '\t')}\n`);
|
||||
fs.writeFileSync(filePath, buf, { encoding: 'utf8' });
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = class CommandClient extends Client {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
@@ -157,7 +172,11 @@ module.exports = class CommandClient extends Client {
|
||||
}
|
||||
|
||||
importBlacklist() {
|
||||
const read = fs.readFileSync(path.join(__dirname, '..', 'blacklist.json'), { encoding: 'utf8' });
|
||||
const filePath = resolveStatePath('blacklist.json');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return writeJsonFile(filePath, { guild: [], user: [] });
|
||||
}
|
||||
const read = fs.readFileSync(filePath, { encoding: 'utf8' });
|
||||
const file = JSON.parse(read);
|
||||
if (typeof file !== 'object' || Array.isArray(file)) return null;
|
||||
if (!file.guild || !file.user) return null;
|
||||
@@ -191,12 +210,17 @@ module.exports = class CommandClient extends Client {
|
||||
}
|
||||
text += '\n ]\n}\n';
|
||||
const buf = Buffer.from(text);
|
||||
fs.writeFileSync(path.join(__dirname, '..', 'blacklist.json'), buf, { encoding: 'utf8' });
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
fs.writeFileSync(resolveStatePath('blacklist.json'), buf, { encoding: 'utf8' });
|
||||
return buf;
|
||||
}
|
||||
|
||||
importCommandLeaderboard(add = false) {
|
||||
const read = fs.readFileSync(path.join(__dirname, '..', 'command-leaderboard.json'), {
|
||||
const filePath = resolveStatePath('command-leaderboard.json');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return writeJsonFile(filePath, {});
|
||||
}
|
||||
const read = fs.readFileSync(filePath, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
const file = JSON.parse(read);
|
||||
@@ -221,14 +245,19 @@ module.exports = class CommandClient extends Client {
|
||||
text = text.slice(0, -1);
|
||||
text += '\n}\n';
|
||||
const buf = Buffer.from(text);
|
||||
fs.writeFileSync(path.join(__dirname, '..', 'command-leaderboard.json'), buf, {
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
fs.writeFileSync(resolveStatePath('command-leaderboard.json'), buf, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
return buf;
|
||||
}
|
||||
|
||||
importLastRun() {
|
||||
const read = fs.readFileSync(path.join(__dirname, '..', 'command-last-run.json'), {
|
||||
const filePath = resolveStatePath('command-last-run.json');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return writeJsonFile(filePath, {});
|
||||
}
|
||||
const read = fs.readFileSync(filePath, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
const file = JSON.parse(read);
|
||||
@@ -254,7 +283,8 @@ module.exports = class CommandClient extends Client {
|
||||
text = text.slice(0, -1);
|
||||
text += '\n}\n';
|
||||
const buf = Buffer.from(text);
|
||||
fs.writeFileSync(path.join(__dirname, '..', 'command-last-run.json'), buf, {
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
fs.writeFileSync(resolveStatePath('command-last-run.json'), buf, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
return buf;
|
||||
|
||||
@@ -8,6 +8,14 @@ const path = require('path');
|
||||
const { checkFileExists } = require('../util/Util');
|
||||
const rounds = ['jeopardy_round', 'double_jeopardy_round', 'final_jeopardy_round'];
|
||||
|
||||
const STATE_DIR = process.env.XIAO_STATE_DIR
|
||||
? path.resolve(process.env.XIAO_STATE_DIR)
|
||||
: path.join(__dirname, '..');
|
||||
|
||||
function resolveStatePath(fileName) {
|
||||
return path.join(STATE_DIR, fileName);
|
||||
}
|
||||
|
||||
module.exports = class JeopardyScrape {
|
||||
constructor(client) {
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
@@ -70,17 +78,17 @@ module.exports = class JeopardyScrape {
|
||||
}
|
||||
|
||||
async importData() {
|
||||
const read = await fs.promises.readFile(path.join(__dirname, '..', 'jeopardy.json'), { encoding: 'utf8' });
|
||||
const read = await fs.promises.readFile(resolveStatePath('jeopardy.json'), { encoding: 'utf8' });
|
||||
const { seasons, gameIDs } = JSON.parse(read);
|
||||
this.gameIDs = gameIDs;
|
||||
this.seasons = seasons;
|
||||
this.gameIDs = Array.isArray(gameIDs) ? gameIDs : [];
|
||||
this.seasons = Array.isArray(seasons) ? seasons : [];
|
||||
this.clues = await this.importClues();
|
||||
this.imported = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
importClues() {
|
||||
const pipeline = fs.createReadStream(path.join(__dirname, '..', 'jeopardy.json'), { encoding: 'utf8' })
|
||||
const pipeline = fs.createReadStream(resolveStatePath('jeopardy.json'), { encoding: 'utf8' })
|
||||
.pipe(parser())
|
||||
.pipe(pick({ filter: 'clues' }))
|
||||
.pipe(streamArray());
|
||||
@@ -96,19 +104,35 @@ module.exports = class JeopardyScrape {
|
||||
gameIDs: this.gameIDs,
|
||||
seasons: this.seasons
|
||||
}));
|
||||
fs.writeFileSync(path.join(__dirname, '..', 'jeopardy.json'), buf, { encoding: 'utf8' });
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
fs.writeFileSync(resolveStatePath('jeopardy.json'), buf, { encoding: 'utf8' });
|
||||
return buf;
|
||||
}
|
||||
|
||||
async checkForUpdates() {
|
||||
if (!this.imported) {
|
||||
const fileExists = await checkFileExists(path.join(__dirname, '..', 'jeopardy.json'));
|
||||
const fileExists = await checkFileExists(resolveStatePath('jeopardy.json'));
|
||||
if (fileExists) {
|
||||
this.client.logger.info('[JEOPARDY] Importing from file...');
|
||||
await this.importData();
|
||||
this.client.logger.info('[JEOPARDY] Import complete!');
|
||||
} else {
|
||||
this.client.logger.warn('[JEOPARDY] No jeopardy.json found. Skipping update for now.');
|
||||
this.clues = [];
|
||||
this.gameIDs = [];
|
||||
this.seasons = [];
|
||||
this.imported = true;
|
||||
this.exportData();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(this.clues)) this.clues = [];
|
||||
if (!Array.isArray(this.gameIDs)) this.gameIDs = [];
|
||||
if (!Array.isArray(this.seasons)) this.seasons = [];
|
||||
if (!this.seasons.length) {
|
||||
this.client.logger.warn('[JEOPARDY] No seasons loaded. Skipping update.');
|
||||
return 0;
|
||||
}
|
||||
const cluesBefore = this.clues.length;
|
||||
const latestSeason = this.seasons[this.seasons.length - 1];
|
||||
const seasons = await this.fetchSeasons();
|
||||
|
||||
Reference in New Issue
Block a user