add commands system

This commit is contained in:
Asaki Yuki
2025-10-18 17:41:27 +07:00
parent c677cb5d68
commit ffdce31ecb
14 changed files with 148 additions and 105 deletions

View File

@@ -1,38 +0,0 @@
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1 AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] tests & build
ENV NODE_ENV=production
RUN bun test
RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/index.ts .
COPY --from=prerelease /usr/src/app/package.json .
# run the app
USER bun
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "run", "index.ts" ]

View File

@@ -5,6 +5,7 @@
"dependencies": {
"@discordjs/voice": "^0.19.0",
"discord.js": "^14.23.2",
"dotenv": "^17.2.3",
"typescript": "^5.9.3",
},
"devDependencies": {
@@ -46,6 +47,8 @@
"discord.js": ["discord.js@14.23.2", "", { "dependencies": { "@discordjs/builders": "^1.12.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.29", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-tU2NFr823X3TXEc8KyR/4m296KLxPai4nirN3q9kHCpY4TKj96n9lHZnyLzRNMui8EbL07jg9hgH2PWWfKMGIg=="],
"dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],

View File

@@ -1,50 +0,0 @@
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker Compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/
# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
server:
build:
context: .
environment:
NODE_ENV: production
ports:
- 8080:8080
# The commented out section below is an example of how to define a PostgreSQL
# database that your application can use. `depends_on` tells Docker Compose to
# start the database before your application. The `db-data` volume persists the
# database data between container restarts. The `db-password` secret is used
# to set the database password. You must create `db/password.txt` and add
# a password of your choosing to it before running `docker-compose up`.
# depends_on:
# db:
# condition: service_healthy
# db:
# image: postgres
# restart: always
# user: postgres
# secrets:
# - db-password
# volumes:
# - db-data:/var/lib/postgresql/data
# environment:
# - POSTGRES_DB=example
# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
# expose:
# - 5432
# healthcheck:
# test: [ "CMD", "pg_isready" ]
# interval: 10s
# timeout: 5s
# retries: 5
# volumes:
# db-data:
# secrets:
# db-password:
# file: db/password.txt

View File

@@ -1,11 +1,29 @@
{
"dependencies": {
"@discordjs/voice": "^0.19.0",
"discord.js": "^14.23.2",
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/node": "^24.7.2",
"@types/pngjs": "^6.0.5"
}
"name": "fireflydiscordbot",
"version": "1.0.0",
"description": "con bot cua tao",
"keywords": [
"DiscordJS"
],
"repository": {
"type": "git",
"url": "git@100.124.183.21:/srv/git/discordbot.git"
},
"license": "ISC",
"author": "AsakiYuki",
"type": "commonjs",
"main": "src/app.ts",
"scripts": {
"dev": "bun --watch src/app.ts"
},
"dependencies": {
"@discordjs/voice": "^0.19.0",
"discord.js": "^14.23.2",
"dotenv": "^17.2.3",
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/node": "^24.7.2",
"@types/pngjs": "^6.0.5"
}
}

View File

@@ -1,3 +1,2 @@
import client from "./components/client"
client
import "./cmd/index"
import "./events/index"

View File

@@ -0,0 +1,40 @@
import { REST, Routes } from "discord.js"
import dotenv from "dotenv"
import path from "path"
import fs from "fs"
import { Cmd } from "../types/cmd"
import { requireDirectory } from "../handler/requireDirectory"
dotenv.config()
const TOKEN = process.env.TOKEN
const CLIENTID = process.env.CLIENTID
if (!TOKEN) {
console.error("Missing TOKEN environment variable")
process.exit(1)
}
if (!CLIENTID) {
console.error("Missing CLIENTID environment variable")
process.exit(1)
}
const excludeFiles = ["index"]
export const execCmds = new Map<string, Cmd>()
requireDirectory(
__dirname,
({ data, exec }: Cmd, filename) => {
if (!data || !exec) throw new Error(`Command ${data.name} is missing data or execute function.`)
if (!data.name) data.setName(filename)
execCmds.set(data.name, { data, exec })
},
excludeFiles
)
const rest = new REST({ version: "10" }).setToken(TOKEN)
rest.put(Routes.applicationCommands(CLIENTID), {
body: Array.from(execCmds.values()).map(({ data }) => data.toJSON()),
})

7
src/cmd/ping.ts Normal file
View File

@@ -0,0 +1,7 @@
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"
export const data = new SlashCommandBuilder().setName("ping").setDescription("Replies with Pong!")
export const exec = async (interaction: ChatInputCommandInteraction) => {
await interaction.reply("Pong!")
}

View File

@@ -1,13 +1,13 @@
import { Client, GatewayIntentBits } from "discord.js"
const token: string | undefined = process.env.DISCORD_BOT_TOKEN
const token: string | undefined = process.env.TOKEN
if (!token) {
console.error("Missing DISCORD_BOT_TOKEN environment variable")
console.error("Missing TOKEN environment variable")
process.exit(1)
}
const client = new Client({
export const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
})
@@ -15,5 +15,3 @@ client.login(token).catch(error => {
console.error("Failed to login to Discord:", error)
process.exit(1)
})
export default client

2
src/events/index.ts Normal file
View File

@@ -0,0 +1,2 @@
import { requireDirectory } from "../handler/requireDirectory"
requireDirectory(__dirname, undefined, ["index"])

View File

@@ -0,0 +1,9 @@
import { client } from "../components/client"
import { execCmd } from "../handler/executeCommand"
client.on("interactionCreate", async interaction => {
if (interaction.isCommand() && interaction.isChatInputCommand()) {
console.log(interaction)
execCmd(interaction)
}
})

5
src/events/ready.ts Normal file
View File

@@ -0,0 +1,5 @@
import { client } from "../components/client"
client.on("ready", client => {
console.log(`Logged in as ${client.user?.tag}`)
})

View File

@@ -0,0 +1,22 @@
import { ChatInputCommandInteraction, CacheType } from "discord.js"
import { execCmds } from "../cmd"
export async function execCmd(interaction: ChatInputCommandInteraction<CacheType>) {
try {
const commandName = interaction.commandName
const command = execCmds.get(commandName)
console.info(`Executing command ${commandName} by ${interaction.user.tag} (${interaction.user.id})`)
const options = interaction.options.data
if (options.length > 0) {
console.info(
options.map(option => `\x1b[34m${option.name}\x1b[0m: \x1b[90m${option.value}\x1b[0m`).join(", ")
)
}
if (command) command.exec(interaction)
else console.warn(`Command ${commandName} not found`)
} catch (error) {
console.error(error)
}
}

View File

@@ -0,0 +1,20 @@
import fs from "fs"
export function requireDirectory(
directoryPath: string,
callback?: (fileExport: any, fileName: string) => void,
excludeFileNames?: string[]
) {
const dir = fs.readdirSync(directoryPath).map(file => file.replace(/\.(ts|js)$/, ""))
const files = dir.filter(file => !excludeFileNames?.includes(file))
files.forEach(file => {
try {
if (!callback) return require(`${directoryPath}/${file}`)
const fileExport = require(`${directoryPath}/${file}`)
callback(fileExport, file)
} catch (error) {
console.error(error)
}
})
}

8
src/types/cmd.ts Normal file
View File

@@ -0,0 +1,8 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"
export type CommandFunction = (interaction: ChatInputCommandInteraction<CacheType>) => void
export interface Cmd {
data: SlashCommandBuilder
exec: CommandFunction
}