From ffdce31ecb818ac04a3595d748e3bf07acc77ae7 Mon Sep 17 00:00:00 2001 From: Asaki Yuki <108646953+AsakiYuki@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:41:27 +0700 Subject: [PATCH] add commands system --- Dockerfile | 38 ------------------------- bun.lock | 3 ++ compose.yaml | 50 --------------------------------- package.json | 36 ++++++++++++++++++------ src/app.ts | 5 ++-- src/cmd/index.ts | 40 ++++++++++++++++++++++++++ src/cmd/ping.ts | 7 +++++ src/components/client.ts | 8 ++---- src/events/index.ts | 2 ++ src/events/interactionCreate.ts | 9 ++++++ src/events/ready.ts | 5 ++++ src/handler/executeCommand.ts | 22 +++++++++++++++ src/handler/requireDirectory.ts | 20 +++++++++++++ src/types/cmd.ts | 8 ++++++ 14 files changed, 148 insertions(+), 105 deletions(-) delete mode 100644 compose.yaml create mode 100644 src/cmd/ping.ts create mode 100644 src/events/index.ts create mode 100644 src/events/interactionCreate.ts create mode 100644 src/events/ready.ts create mode 100644 src/handler/executeCommand.ts create mode 100644 src/handler/requireDirectory.ts create mode 100644 src/types/cmd.ts diff --git a/Dockerfile b/Dockerfile index 2e7791e..e69de29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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" ] diff --git a/bun.lock b/bun.lock index 0c757ee..be50182 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index fd6fd8c..0000000 --- a/compose.yaml +++ /dev/null @@ -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 - diff --git a/package.json b/package.json index 79b3b70..a2ce2d0 100644 --- a/package.json +++ b/package.json @@ -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" + } } diff --git a/src/app.ts b/src/app.ts index 42f732f..4a927f1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,3 +1,2 @@ -import client from "./components/client" - -client +import "./cmd/index" +import "./events/index" diff --git a/src/cmd/index.ts b/src/cmd/index.ts index e69de29..156b737 100644 --- a/src/cmd/index.ts +++ b/src/cmd/index.ts @@ -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() + +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()), +}) diff --git a/src/cmd/ping.ts b/src/cmd/ping.ts new file mode 100644 index 0000000..442d6fe --- /dev/null +++ b/src/cmd/ping.ts @@ -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!") +} diff --git a/src/components/client.ts b/src/components/client.ts index f9106fa..2f5930a 100644 --- a/src/components/client.ts +++ b/src/components/client.ts @@ -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 diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..6d0c680 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,2 @@ +import { requireDirectory } from "../handler/requireDirectory" +requireDirectory(__dirname, undefined, ["index"]) diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts new file mode 100644 index 0000000..97dee02 --- /dev/null +++ b/src/events/interactionCreate.ts @@ -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) + } +}) diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..e407c0f --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,5 @@ +import { client } from "../components/client" + +client.on("ready", client => { + console.log(`Logged in as ${client.user?.tag}`) +}) diff --git a/src/handler/executeCommand.ts b/src/handler/executeCommand.ts new file mode 100644 index 0000000..a447670 --- /dev/null +++ b/src/handler/executeCommand.ts @@ -0,0 +1,22 @@ +import { ChatInputCommandInteraction, CacheType } from "discord.js" +import { execCmds } from "../cmd" + +export async function execCmd(interaction: ChatInputCommandInteraction) { + 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) + } +} diff --git a/src/handler/requireDirectory.ts b/src/handler/requireDirectory.ts new file mode 100644 index 0000000..f42b45a --- /dev/null +++ b/src/handler/requireDirectory.ts @@ -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) + } + }) +} diff --git a/src/types/cmd.ts b/src/types/cmd.ts new file mode 100644 index 0000000..1cbefb2 --- /dev/null +++ b/src/types/cmd.ts @@ -0,0 +1,8 @@ +import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js" + +export type CommandFunction = (interaction: ChatInputCommandInteraction) => void + +export interface Cmd { + data: SlashCommandBuilder + exec: CommandFunction +}