This commit is contained in:
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
24
.gitea/workflows/build.yml
Normal file
24
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Gitea Auto Deploy
|
||||||
|
run-name: ${{ gitea.actor }} pushed code 🚀
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Deploy-Container:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out latest code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Stop and remove old containers
|
||||||
|
run: |
|
||||||
|
docker compose down || true
|
||||||
|
|
||||||
|
- name: Remove unused Docker resources
|
||||||
|
run: |
|
||||||
|
docker system prune -a --volumes -f
|
||||||
|
|
||||||
|
- name: Build and restart containers
|
||||||
|
run: |
|
||||||
|
docker compose up -d
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
.history
|
||||||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM node:20-alpine AS base
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json package-lock*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=deps /app/package.json ./package.json
|
||||||
|
COPY package.json package-lock.json tsconfig.json .env ./
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
CMD ["npm", "run", "start"]
|
||||||
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
services:
|
||||||
|
firefly-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: firefly-bot
|
||||||
|
networks:
|
||||||
|
- bot-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bot-network:
|
||||||
1390
package-lock.json
generated
Normal file
1390
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "firefly-bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/index.ts",
|
||||||
|
"start": "tsx src/index.ts",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"_moduleAliases": {
|
||||||
|
"@": "src"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"discord.js": "^14.19.3",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"module-alias": "^2.2.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"tsx": "^4.19.4",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/commands/index.ts
Normal file
2
src/commands/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./prefix";
|
||||||
|
export * from "./slash";
|
||||||
10
src/commands/prefix/fetchApk/fetch.ts
Normal file
10
src/commands/prefix/fetchApk/fetch.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Client, Message } from "discord.js";
|
||||||
|
import { fetchApk } from "@/services/fetchApk";
|
||||||
|
|
||||||
|
export const name = "apk";
|
||||||
|
export const description = "Fetch apk";
|
||||||
|
|
||||||
|
export async function execute(client: Client, message: Message, args: string[]) {
|
||||||
|
const version = args[0];
|
||||||
|
await fetchApk(version, message);
|
||||||
|
}
|
||||||
24
src/commands/prefix/index.ts
Normal file
24
src/commands/prefix/index.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Client, Collection } from "discord.js";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import type { prefixCommand } from "@/types/discord.js";
|
||||||
|
|
||||||
|
export function ActiveAllPrefixCommands(client: Client) {
|
||||||
|
const commandFolder = path.join(__dirname);
|
||||||
|
client.prefix = new Collection<string, prefixCommand>();
|
||||||
|
for (const folder of fs.readdirSync(commandFolder)) {
|
||||||
|
const folderPath = path.join(commandFolder, folder);
|
||||||
|
if (!fs.statSync(folderPath).isDirectory()) continue;
|
||||||
|
|
||||||
|
const commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith(".ts"));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const command = require(path.join(folderPath, file));
|
||||||
|
if (!command.name || typeof command.execute !== "function") {
|
||||||
|
console.log(`Prefix command at ${path.join(folderPath, file)} is missing a name or execute function`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// console.log(`Prefix command ${command.name} loaded`);
|
||||||
|
client.prefix.set(command.name, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/commands/prefix/ping/ping.ts
Normal file
10
src/commands/prefix/ping/ping.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Message, Client } from "discord.js";
|
||||||
|
|
||||||
|
export const name = "ping";
|
||||||
|
export const description = "Kiểm tra thời gian phản hồi của bot";
|
||||||
|
|
||||||
|
export async function execute(client: Client, message: Message, args: string[]) {
|
||||||
|
const sent = await message.reply('Pong!');
|
||||||
|
const latency = sent.createdTimestamp - message.createdTimestamp;
|
||||||
|
await sent.edit(`Pong! \`${latency}ms\``);
|
||||||
|
}
|
||||||
13
src/commands/slash/fetchApk/fetch.js
Normal file
13
src/commands/slash/fetchApk/fetch.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Client, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { fetchApk } from "@/services/fetchApk";
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName("apk")
|
||||||
|
.setDescription("Fetch apk")
|
||||||
|
.addStringOption((option) => option
|
||||||
|
.setName("version")
|
||||||
|
.setDescription("Version of apk")
|
||||||
|
.setRequired(true));
|
||||||
|
export async function execute(client, interaction) {
|
||||||
|
const version = interaction.options.getString("version");
|
||||||
|
await fetchApk(version, interaction);
|
||||||
|
}
|
||||||
17
src/commands/slash/fetchApk/fetch.ts
Normal file
17
src/commands/slash/fetchApk/fetch.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Client, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { fetchApk } from "@/services/fetchApk";
|
||||||
|
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName("apk")
|
||||||
|
.setDescription("Fetch apk")
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName("version")
|
||||||
|
.setDescription("Version of apk")
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
export async function execute(client: Client, interaction: ChatInputCommandInteraction) {
|
||||||
|
const version = interaction.options.getString("version");
|
||||||
|
await fetchApk(version, interaction);
|
||||||
|
}
|
||||||
47
src/commands/slash/index.js
Normal file
47
src/commands/slash/index.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Client, Collection, REST, Routes } from "discord.js";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import { config } from "@/config";
|
||||||
|
export async function ActiveAllSlashCommands(client) {
|
||||||
|
client.slash = new Collection();
|
||||||
|
const commandFolder = path.join(__dirname);
|
||||||
|
for (const folder of fs.readdirSync(commandFolder)) {
|
||||||
|
const folderPath = path.join(commandFolder, folder);
|
||||||
|
if (!fs.statSync(folderPath).isDirectory())
|
||||||
|
continue;
|
||||||
|
const commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith(".ts"));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const command = require(path.join(folderPath, file));
|
||||||
|
if (!command.data?.name || typeof command.execute !== "function") {
|
||||||
|
console.log(`Slash command at ${path.join(folderPath, file)} is missing a name or execute function`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
client.slash.set(command.data.name, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rest = new REST({ version: '10' }).setToken(config.DISCORD_TOKEN);
|
||||||
|
export async function registerCommandsAll() {
|
||||||
|
const commands = [];
|
||||||
|
const commandFolder = path.join(__dirname);
|
||||||
|
for (const folder of fs.readdirSync(commandFolder)) {
|
||||||
|
const folderPath = path.join(commandFolder, folder);
|
||||||
|
if (!fs.statSync(folderPath).isDirectory())
|
||||||
|
continue;
|
||||||
|
const commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith(".ts"));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const command = require(path.join(folderPath, file));
|
||||||
|
if (!command.data?.name || typeof command.execute !== "function")
|
||||||
|
continue;
|
||||||
|
commands.push(command.data.toJSON());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log('Started refreshing application (/) commands.');
|
||||||
|
const data = await rest.put(Routes.applicationCommands(config.DISCORD_CLIENT_ID), { body: commands });
|
||||||
|
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/commands/slash/index.ts
Normal file
53
src/commands/slash/index.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { slashCommand } from "@/types/discord.js";
|
||||||
|
import { Client, Collection, REST, Routes, type RESTPostAPIChatInputApplicationCommandsJSONBody } from "discord.js";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import { config } from "@/config";
|
||||||
|
|
||||||
|
export async function ActiveAllSlashCommands(client: Client) {
|
||||||
|
client.slash = new Collection<string, slashCommand>();
|
||||||
|
|
||||||
|
const commandFolder = path.join(__dirname);
|
||||||
|
for (const folder of fs.readdirSync(commandFolder)) {
|
||||||
|
const folderPath = path.join(commandFolder, folder);
|
||||||
|
if (!fs.statSync(folderPath).isDirectory()) continue;
|
||||||
|
|
||||||
|
const commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith(".ts"));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const command = require(path.join(folderPath, file));
|
||||||
|
if (!command.data?.name || typeof command.execute !== "function") {
|
||||||
|
console.log(`Slash command at ${path.join(folderPath, file)} is missing a name or execute function`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// console.log(`Slash command ${command.data.name} loaded`);
|
||||||
|
client.slash.set(command.data.name, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rest = new REST({ version: '10' }).setToken(config.DISCORD_TOKEN);
|
||||||
|
|
||||||
|
export async function registerCommandsAll() {
|
||||||
|
const commands: RESTPostAPIChatInputApplicationCommandsJSONBody[] = [];
|
||||||
|
const commandFolder = path.join(__dirname);
|
||||||
|
|
||||||
|
for (const folder of fs.readdirSync(commandFolder)) {
|
||||||
|
const folderPath = path.join(commandFolder, folder);
|
||||||
|
if (!fs.statSync(folderPath).isDirectory()) continue;
|
||||||
|
|
||||||
|
const commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith(".ts"));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const command = require(path.join(folderPath, file));
|
||||||
|
if (!command.data?.name || typeof command.execute !== "function") continue;
|
||||||
|
commands.push(command.data.toJSON());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Started refreshing application (/) commands.');
|
||||||
|
const data = await rest.put(Routes.applicationCommands(config.DISCORD_CLIENT_ID),
|
||||||
|
{ body: commands }) as RESTPostAPIChatInputApplicationCommandsJSONBody[];
|
||||||
|
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/commands/slash/ping/ping.js
Normal file
7
src/commands/slash/ping/ping.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { ChatInputCommandInteraction, Client, SlashCommandBuilder } from "discord.js";
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName("ping")
|
||||||
|
.setDescription("Replies with pong!");
|
||||||
|
export async function execute(client, interaction) {
|
||||||
|
await interaction.reply("Pong!");
|
||||||
|
}
|
||||||
10
src/commands/slash/ping/ping.ts
Normal file
10
src/commands/slash/ping/ping.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ChatInputCommandInteraction, Client, SlashCommandBuilder } from "discord.js";
|
||||||
|
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName("ping")
|
||||||
|
.setDescription("Replies with pong!")
|
||||||
|
|
||||||
|
|
||||||
|
export async function execute(client: Client, interaction: ChatInputCommandInteraction) {
|
||||||
|
await interaction.reply("Pong!");
|
||||||
|
}
|
||||||
18
src/config.ts
Normal file
18
src/config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID, DISCORD_PREFIX } = process.env;
|
||||||
|
|
||||||
|
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID || !DISCORD_GUILD_ID || !DISCORD_PREFIX) {
|
||||||
|
throw new Error("Missing environment variables");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
DISCORD_TOKEN,
|
||||||
|
DISCORD_CLIENT_ID,
|
||||||
|
DISCORD_GUILD_ID,
|
||||||
|
DISCORD_PREFIX,
|
||||||
|
};
|
||||||
|
|
||||||
26
src/events/index.ts
Normal file
26
src/events/index.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Client } from "discord.js";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
export default function ActiveAllEvents(client: Client) {
|
||||||
|
const eventFolder = path.join(__dirname);
|
||||||
|
for (const folder of fs.readdirSync(eventFolder)) {
|
||||||
|
const folderPath = path.join(eventFolder, folder);
|
||||||
|
if (!fs.statSync(folderPath).isDirectory()) continue;
|
||||||
|
|
||||||
|
const eventFiles = fs.readdirSync(folderPath).filter(file => file.endsWith(".ts"));
|
||||||
|
for (const file of eventFiles) {
|
||||||
|
const event = require(path.join(folderPath, file));
|
||||||
|
if (!event.name || typeof event.execute !== "function") {
|
||||||
|
console.log(`Event at ${path.join(folderPath, file)} is missing a name or execute function`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// console.log(`Event ${event.name} loaded`);
|
||||||
|
if (event.once) {
|
||||||
|
client.once(event.name, (...args) => event.execute(client, ...args));
|
||||||
|
} else {
|
||||||
|
client.on(event.name, (...args) => event.execute(client, ...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/events/interaction/interaction.ts
Normal file
31
src/events/interaction/interaction.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { ChatInputCommandInteraction, Client, Events, MessageFlags } from "discord.js";
|
||||||
|
|
||||||
|
export const name = Events.InteractionCreate;
|
||||||
|
|
||||||
|
export const once = false;
|
||||||
|
|
||||||
|
export async function execute(client: Client, interaction: ChatInputCommandInteraction) {
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
const command = client.slash.get(interaction.commandName);
|
||||||
|
if (!command) {
|
||||||
|
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await command.execute(client, interaction);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (interaction.replied || interaction.deferred) {
|
||||||
|
await interaction.followUp({
|
||||||
|
content: "An error occurred while executing this command.",
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await interaction.reply({
|
||||||
|
content: "An error occurred while executing this command.",
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/events/messageCreate/message.ts
Normal file
30
src/events/messageCreate/message.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { config } from "@/config";
|
||||||
|
import { Client, Events, Message } from "discord.js";
|
||||||
|
|
||||||
|
export const name = Events.MessageCreate;
|
||||||
|
|
||||||
|
export const once = false;
|
||||||
|
|
||||||
|
export async function execute(client: Client, message: Message) {
|
||||||
|
if (message.author.bot) return;
|
||||||
|
|
||||||
|
const prefix = config.DISCORD_PREFIX;
|
||||||
|
if (!message.content.startsWith(prefix)) return;
|
||||||
|
|
||||||
|
const args = message.content.slice(prefix.length).trim().split(/ +/);
|
||||||
|
const commandName = args.shift()?.toLowerCase();
|
||||||
|
|
||||||
|
if (!commandName) return;
|
||||||
|
|
||||||
|
const command = client.prefix.get(commandName);
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
try {
|
||||||
|
await command.execute(client, message, args);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
await message.reply('Đã xảy ra lỗi khi thực hiện lệnh này!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
33
src/events/ready/ready.js
Normal file
33
src/events/ready/ready.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { ActivityType, Client, Events, PresenceUpdateStatus } from "discord.js";
|
||||||
|
export const name = Events.ClientReady;
|
||||||
|
export const once = true;
|
||||||
|
export async function execute(client) {
|
||||||
|
if (!client.user || !client.application)
|
||||||
|
return;
|
||||||
|
console.log(`🚩Logged in as ${client.user.tag}!`);
|
||||||
|
const activityTypeMap = {
|
||||||
|
'PLAYING': ActivityType.Playing,
|
||||||
|
'WATCHING': ActivityType.Watching,
|
||||||
|
'LISTENING': ActivityType.Listening,
|
||||||
|
'STREAMING': ActivityType.Streaming,
|
||||||
|
'COMPETING': ActivityType.Competing
|
||||||
|
};
|
||||||
|
const statusMap = {
|
||||||
|
'online': PresenceUpdateStatus.Online,
|
||||||
|
'idle': PresenceUpdateStatus.Idle,
|
||||||
|
'dnd': PresenceUpdateStatus.DoNotDisturb,
|
||||||
|
'invisible': PresenceUpdateStatus.Invisible
|
||||||
|
};
|
||||||
|
const statusType = process.env.BOT_STATUS || 'online';
|
||||||
|
const activityType = process.env.ACTIVITY_TYPE || 'PLAYING';
|
||||||
|
const activityName = process.env.ACTIVITY_NAME || 'Shoudo sakusen jikkou!';
|
||||||
|
client.user.setPresence({
|
||||||
|
status: statusMap[statusType],
|
||||||
|
activities: [{
|
||||||
|
name: activityName,
|
||||||
|
type: activityTypeMap[activityType]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
console.log(`🗿Bot status set to: ${statusType}`);
|
||||||
|
console.log(`👨🎤Activity set to: ${activityType} ${activityName}`);
|
||||||
|
}
|
||||||
41
src/events/ready/ready.ts
Normal file
41
src/events/ready/ready.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { ActivityType, Client, Events, PresenceUpdateStatus } from "discord.js";
|
||||||
|
// import { registerCommandsAll } from "@/commands";
|
||||||
|
|
||||||
|
export const name = Events.ClientReady;
|
||||||
|
|
||||||
|
export const once = true;
|
||||||
|
|
||||||
|
export async function execute(client: Client) {
|
||||||
|
if (!client.user || !client.application) return;
|
||||||
|
console.log(`🚩Logged in as ${client.user.tag}!`);
|
||||||
|
// await registerCommandsAll()
|
||||||
|
const activityTypeMap = {
|
||||||
|
'PLAYING': ActivityType.Playing,
|
||||||
|
'WATCHING': ActivityType.Watching,
|
||||||
|
'LISTENING': ActivityType.Listening,
|
||||||
|
'STREAMING': ActivityType.Streaming,
|
||||||
|
'COMPETING': ActivityType.Competing
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
'online': PresenceUpdateStatus.Online,
|
||||||
|
'idle': PresenceUpdateStatus.Idle,
|
||||||
|
'dnd': PresenceUpdateStatus.DoNotDisturb,
|
||||||
|
'invisible': PresenceUpdateStatus.Invisible
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const statusType = (process.env.BOT_STATUS as keyof typeof statusMap) || 'online';
|
||||||
|
const activityType = (process.env.ACTIVITY_TYPE as keyof typeof activityTypeMap) || 'PLAYING';
|
||||||
|
const activityName = process.env.ACTIVITY_NAME || 'Shoudo sakusen jikkou!';
|
||||||
|
|
||||||
|
client.user.setPresence({
|
||||||
|
status: statusMap[statusType],
|
||||||
|
activities: [{
|
||||||
|
name: activityName,
|
||||||
|
type: activityTypeMap[activityType]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🗿Bot status set to: ${statusType}`);
|
||||||
|
console.log(`👨🎤Activity set to: ${activityType} ${activityName}`)
|
||||||
|
}
|
||||||
31
src/index.ts
Normal file
31
src/index.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'module-alias/register'
|
||||||
|
import { Client, GatewayIntentBits, Partials } from "discord.js";
|
||||||
|
import { config } from "./config";
|
||||||
|
import ActiveAllEvents from "./events";
|
||||||
|
import { ActiveAllPrefixCommands, ActiveAllSlashCommands } from "./commands";
|
||||||
|
|
||||||
|
console.log("🔥 Starting bot...")
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: [
|
||||||
|
GatewayIntentBits.Guilds,
|
||||||
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.MessageContent,
|
||||||
|
GatewayIntentBits.GuildMembers,
|
||||||
|
GatewayIntentBits.GuildPresences,
|
||||||
|
],
|
||||||
|
partials: [
|
||||||
|
Partials.Channel,
|
||||||
|
Partials.Message,
|
||||||
|
Partials.User,
|
||||||
|
Partials.GuildMember
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ActiveAllPrefixCommands(client);
|
||||||
|
ActiveAllSlashCommands(client);
|
||||||
|
ActiveAllEvents(client);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await client.login(config.DISCORD_TOKEN);
|
||||||
|
})();
|
||||||
67
src/services/fetchApk.ts
Normal file
67
src/services/fetchApk.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { ChatInputCommandInteraction, EmbedBuilder, Message, MessageFlags } from "discord.js";
|
||||||
|
|
||||||
|
export async function fetchApk(version: string | undefined | null, req: ChatInputCommandInteraction | Message) {
|
||||||
|
if (!version) {
|
||||||
|
await req.reply({
|
||||||
|
content: '❌ Please provide a version.',
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const t = Math.floor(Date.now() / 1000);
|
||||||
|
const url = 'https://globaldp-beta-cn01.bhsr.com/query_dispatch';
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
version: `CNBETAAndroid${version.replace(/\s+/g, "")}`,
|
||||||
|
t: t.toString(),
|
||||||
|
language_type: '3',
|
||||||
|
platform_type: '3',
|
||||||
|
channel_id: '1',
|
||||||
|
sub_channel_id: '1',
|
||||||
|
is_new_format: '1',
|
||||||
|
};
|
||||||
|
let sent;
|
||||||
|
if (req instanceof Message) {
|
||||||
|
sent = await req.reply({
|
||||||
|
content: '🔄 Fetching APK...',
|
||||||
|
flags: MessageFlags.SuppressNotifications
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { params });
|
||||||
|
const decoded = Buffer.from(response.data, 'base64').toString('utf-8');
|
||||||
|
const match = decoded.match(/https?:\/\/[^\s\\"]+/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("📦 APK Download Link")
|
||||||
|
.setDescription(`[Click here to download](${match[0]})`)
|
||||||
|
.addFields({ name: "Version", value: version })
|
||||||
|
.setColor("Random")
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await req.reply({ embeds: [embed] });
|
||||||
|
} else {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("❌ No URL Found")
|
||||||
|
.setDescription("No valid APK URL was found in the response.")
|
||||||
|
.setColor("Red");
|
||||||
|
|
||||||
|
await req.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("❌ Error:", error.message);
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("🚨 Error")
|
||||||
|
.setDescription("An error occurred while fetching the APK.")
|
||||||
|
.setColor("Red");
|
||||||
|
|
||||||
|
await req.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sent) {
|
||||||
|
await sent.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/types/discord.js.d.ts
vendored
Normal file
19
src/types/discord.js.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Collection, Client as BaseClient, SlashCommandBuilder, CommandInteraction } from "discord.js";
|
||||||
|
|
||||||
|
export interface slashCommand {
|
||||||
|
data: SlashCommandBuilder;
|
||||||
|
execute: (client: BaseClient, interaction: CommandInteraction) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface prefixCommand {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
execute: (client: Client, message: Message, args: string[]) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "discord.js" {
|
||||||
|
interface Client {
|
||||||
|
slash: Collection<string, slashCommand>;
|
||||||
|
prefix: Collection<string, prefixCommand>;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
tsconfig.json
Normal file
35
tsconfig.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"sourceMap": false,
|
||||||
|
"removeComments": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"baseUrl": "src",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
// "noEmit": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"lib": ["es2022"],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user