add commands system
This commit is contained in:
38
Dockerfile
38
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" ]
|
|
||||||
|
|||||||
3
bun.lock
3
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.19.0",
|
"@discordjs/voice": "^0.19.0",
|
||||||
"discord.js": "^14.23.2",
|
"discord.js": "^14.23.2",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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=="],
|
"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=="],
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||||
|
|||||||
50
compose.yaml
50
compose.yaml
@@ -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
|
|
||||||
|
|
||||||
18
package.json
18
package.json
@@ -1,7 +1,25 @@
|
|||||||
{
|
{
|
||||||
|
"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": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.19.0",
|
"@discordjs/voice": "^0.19.0",
|
||||||
"discord.js": "^14.23.2",
|
"discord.js": "^14.23.2",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
import client from "./components/client"
|
import "./cmd/index"
|
||||||
|
import "./events/index"
|
||||||
client
|
|
||||||
|
|||||||
@@ -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
7
src/cmd/ping.ts
Normal 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!")
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Client, GatewayIntentBits } from "discord.js"
|
import { Client, GatewayIntentBits } from "discord.js"
|
||||||
|
|
||||||
const token: string | undefined = process.env.DISCORD_BOT_TOKEN
|
const token: string | undefined = process.env.TOKEN
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.error("Missing DISCORD_BOT_TOKEN environment variable")
|
console.error("Missing TOKEN environment variable")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new Client({
|
export const client = new Client({
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
|
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -15,5 +15,3 @@ client.login(token).catch(error => {
|
|||||||
console.error("Failed to login to Discord:", error)
|
console.error("Failed to login to Discord:", error)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default client
|
|
||||||
|
|||||||
2
src/events/index.ts
Normal file
2
src/events/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import { requireDirectory } from "../handler/requireDirectory"
|
||||||
|
requireDirectory(__dirname, undefined, ["index"])
|
||||||
9
src/events/interactionCreate.ts
Normal file
9
src/events/interactionCreate.ts
Normal 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
5
src/events/ready.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { client } from "../components/client"
|
||||||
|
|
||||||
|
client.on("ready", client => {
|
||||||
|
console.log(`Logged in as ${client.user?.tag}`)
|
||||||
|
})
|
||||||
22
src/handler/executeCommand.ts
Normal file
22
src/handler/executeCommand.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/handler/requireDirectory.ts
Normal file
20
src/handler/requireDirectory.ts
Normal 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
8
src/types/cmd.ts
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user