Compare commits
10 Commits
c677cb5d68
...
efb717f91f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efb717f91f | ||
|
|
12214df1e9 | ||
|
|
d419ca91e6 | ||
|
|
755a3876a9 | ||
|
|
b23c4c882e | ||
|
|
d7624cc25e | ||
|
|
67121a4349 | ||
|
|
69a74118ae | ||
|
|
7920681226 | ||
|
|
ffdce31ecb |
8
.env.example
Normal file
8
.env.example
Normal file
@@ -0,0 +1,8 @@
|
||||
# This file contains environment variables for the application.
|
||||
ENVIRONMENT=production # development, production, test
|
||||
|
||||
## Discord Bot Configuration
|
||||
CHANNEL=
|
||||
CLIENTID=
|
||||
CLIENTSECRET=
|
||||
TOKEN=
|
||||
39
Dockerfile
39
Dockerfile
@@ -1,38 +1,7 @@
|
||||
# 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
|
||||
FROM oven/bun
|
||||
WORKDIR /usr/firefly/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" ]
|
||||
RUN bun install
|
||||
CMD [ "bun", "run", "start" ]
|
||||
@@ -1,22 +0,0 @@
|
||||
### Building and running your application
|
||||
|
||||
When you're ready, start your application by running:
|
||||
`docker compose up --build`.
|
||||
|
||||
Your application will be available at http://localhost:8080.
|
||||
|
||||
### Deploying your application to the cloud
|
||||
|
||||
First, build your image, e.g.: `docker build -t myapp .`.
|
||||
If your cloud uses a different CPU architecture than your development
|
||||
machine (e.g., you are on a Mac M1 and your cloud provider is amd64),
|
||||
you'll want to build the image for that platform, e.g.:
|
||||
`docker build --platform=linux/amd64 -t myapp .`.
|
||||
|
||||
Then, push it to your registry, e.g. `docker push myregistry.com/myapp`.
|
||||
|
||||
Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/)
|
||||
docs for more detail on building and pushing.
|
||||
|
||||
### References
|
||||
* [Docker's Node.js guide](https://docs.docker.com/language/nodejs/)
|
||||
7
bun.lock
7
bun.lock
@@ -2,14 +2,17 @@
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "fireflydiscordbot",
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.19.0",
|
||||
"discord.js": "^14.23.2",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"typescript": "^5.9.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"dotenv": "^17.2.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -46,8 +49,12 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||
|
||||
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
|
||||
|
||||
57
compose.yaml
57
compose.yaml
@@ -1,50 +1,11 @@
|
||||
# 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
|
||||
version: "2.40"
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
app:
|
||||
build: .
|
||||
container_name: firefly-discord-bot
|
||||
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
|
||||
|
||||
ENVIRONMENT: ${ENVIRONMENT}
|
||||
TOKEN: ${TOKEN}
|
||||
CHANNEL: ${CHANNEL}
|
||||
CLIENTID: ${CLIENTID}
|
||||
CLIENTSECRET: ${CLIENTSECRET}
|
||||
|
||||
3
config.json
Normal file
3
config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"startup_messages": ["I'm online!", "Hello world!", "Hi there!"]
|
||||
}
|
||||
23
package.json
23
package.json
@@ -1,11 +1,32 @@
|
||||
{
|
||||
"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",
|
||||
"start": "bun src/app.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.19.0",
|
||||
"discord.js": "^14.23.2",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/pngjs": "^6.0.5"
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"dotenv": "^17.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
12
src/app.ts
12
src/app.ts
@@ -1,3 +1,11 @@
|
||||
import client from "./components/client"
|
||||
import "./events/preload"
|
||||
import dotenv from "dotenv"
|
||||
|
||||
client
|
||||
dotenv.config({ quiet: true })
|
||||
|
||||
import "./cmd/index"
|
||||
import path from "path"
|
||||
import { requireDirectory } from "./handler/requireDirectory"
|
||||
|
||||
// Require events directory
|
||||
requireDirectory(path.join(__dirname, "./events"))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { REST, Routes } from "discord.js"
|
||||
import { Cmd } from "../types/cmd"
|
||||
import { requireDirectory } from "../handler/requireDirectory"
|
||||
|
||||
const TOKEN = process.env.TOKEN
|
||||
const CLIENTID = process.env.CLIENTID
|
||||
|
||||
if (!TOKEN || !CLIENTID) {
|
||||
console.error("Missing TOKEN or CLIENTID in .env file")
|
||||
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()),
|
||||
})
|
||||
|
||||
10
src/cmd/ping.ts
Normal file
10
src/cmd/ping.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ChatInputCommandInteraction, EmbedBuilder, MessageFlags, SlashCommandBuilder } from "discord.js"
|
||||
|
||||
export const data = new SlashCommandBuilder().setName("ping").setDescription("Replies with Pong!")
|
||||
|
||||
export const exec = async (interaction: ChatInputCommandInteraction) => {
|
||||
await interaction.reply({
|
||||
content: "Pong 🏓!",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
})
|
||||
}
|
||||
@@ -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 in .env file")
|
||||
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
|
||||
|
||||
11
src/components/config.ts
Normal file
11
src/components/config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import JSONC from "jsonc-parser"
|
||||
import fs from "fs"
|
||||
|
||||
interface CONFIG {
|
||||
startup_messages: string[]
|
||||
}
|
||||
|
||||
const data = fs.readFileSync("config.json", "utf-8")
|
||||
const config: CONFIG = JSONC.parse(data)
|
||||
|
||||
export default config
|
||||
35
src/events/clientReady.ts
Normal file
35
src/events/clientReady.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ActivityType } from "discord.js"
|
||||
import { client } from "../components/client"
|
||||
import config from "../components/config"
|
||||
import { execCmds } from "../cmd"
|
||||
|
||||
client.on("clientReady", async client => {
|
||||
try {
|
||||
console.info(`Logged in as ${client.user?.tag}`)
|
||||
console.info(`${execCmds.size} commands loaded.`)
|
||||
|
||||
client.user.setActivity({
|
||||
name: "Docker",
|
||||
state: "Develop by Asaki Yuki",
|
||||
url: "https://asakiyuki.com/",
|
||||
type: ActivityType.Playing,
|
||||
})
|
||||
|
||||
try {
|
||||
const channelId = process.env.CLIENTID
|
||||
if (!channelId) throw new Error("Missing CLIENTID in .env file")
|
||||
|
||||
const channel = await client.channels.fetch(channelId)
|
||||
if (!channel) throw new Error("Channel not found")
|
||||
|
||||
const message = config.startup_messages?.[Math.floor(Math.random() * config.startup_messages.length)]
|
||||
if (!message) throw new Error("Missing startup message")
|
||||
|
||||
if (channel.isSendable()) await channel.send(message)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
6
src/events/interactionCreate.ts
Normal file
6
src/events/interactionCreate.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { client } from "../components/client"
|
||||
import { execCmd } from "../handler/executeCommand"
|
||||
|
||||
client.on("interactionCreate", async interaction => {
|
||||
if (interaction.isCommand() && interaction.isChatInputCommand()) execCmd(interaction)
|
||||
})
|
||||
12
src/events/preload.ts
Normal file
12
src/events/preload.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const time = Date.now()
|
||||
|
||||
const log = new Map<string, (...args: any[]) => void>()
|
||||
const keys = Object.keys(console).filter(key => !key.includes("time"))
|
||||
|
||||
keys.forEach(key => {
|
||||
const func: (...args: any[]) => void = (console as any)[key]
|
||||
log.set(key, func)
|
||||
;(console as any)[key] = (...args: any[]) => {
|
||||
log.get(key)?.(`[${Date.now() - time}ms] [${key.toUpperCase()}]`, ...args)
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
@@ -151,6 +151,11 @@ discord.js@^14.23.2:
|
||||
tslib "^2.6.3"
|
||||
undici "6.21.3"
|
||||
|
||||
dotenv@^17.2.3:
|
||||
version "17.2.3"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2"
|
||||
integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==
|
||||
|
||||
fast-deep-equal@3.1.3, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
|
||||
Reference in New Issue
Block a user