Skip to content
This repository has been archived by the owner on Oct 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #64 from takejohn/feature/62-features
Browse files Browse the repository at this point in the history
関連コマンドをまとめる (#62)
  • Loading branch information
ringo360 authored Mar 12, 2024
2 parents 31a314e + 8ff5125 commit 87d075d
Show file tree
Hide file tree
Showing 54 changed files with 647 additions and 232 deletions.
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ discord.js@v14を使用した、多機能botです。


## コマンドを登録する
`commands`ディレクトリにファイルを作成するだけで、起動時に自動で読み込まれます。

### 例:
### `misc` パッケージに追加する方法
`packages/misc/commands`ディレクトリにファイルを作成するだけで、起動時に自動で読み込まれます。

#### 例:
```js
const { SlashCommandBuilder } = require('discord.js');

Expand All @@ -47,3 +49,33 @@ module.exports = {
}
};
```

### パッケージを追加する方法
`packages` ディレクトリ以下に workspace を作成します。
このパッケージは自動的に読み込まれます。
```sh
npm init -w packages/example
```

エントリーポイントのファイル (`index.js` など) でコマンドを追加し、feature をエクスポートします。

#### 例:
```js
const { CommandManager } = require('../../internal/commands');
const upload = require('./upload');

class ExampleFeature {
onLoad() {
CommandManager.default.addCommands({
data: new SlashCommandBuilder()
.setName('hello')
.setDescription('Hello World!'),
execute: async function (interaction) {
await interaction.reply('Hello World!') //処理を記述
}
});
}
}

module.exports = { feature: new ExampleFeature() };
```
122 changes: 26 additions & 96 deletions discordbot.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const { Client, GatewayIntentBits, ActivityType } = require('discord.js');
const fs = require('fs');
const path = require('path');
const { token, syslogChannel } = require('./config.json');
const { enableTempLinks } = require('./internal/templinks');
const { Player } = require('discord-player');
process.env['FFMPEG_PATH'] = path.join(__dirname, 'ffmpeg');

//!Load Internal dir code
Expand All @@ -16,21 +14,13 @@ const mongodb = require('./internal/mongodb');

mongodb.connectMongoose();

const {
getDuration,
saveQueue,
deleteSavedQueues,
restoreQueues,
} = require('./util/players');
const { LANG, strFormat } = require('./util/languages');
const { ClientMessageHandler } = require('./internal/messages');
const { CommandManager } = require('./internal/commands');

const creset = '\x1b[0m';
const cgreen = '\x1b[32m';

/** @type {import("./util/types").Command[]} */
let commands = [];

//!LOGGER
const oWrite = process.stdout.write;
process.stdout.write = function () {
Expand All @@ -47,16 +37,6 @@ process.stderr.write = function () {
//!RUN=======================

console.log(LANG.discordbot.main.botStarting);
let cmdscount = 0;
fs.readdirSync(path.join(__dirname, 'commands'), {
withFileTypes: true,
}).forEach((file) => {
if (!file.isFile() || path.extname(file.name) != '.js') return;
const cmds = require(path.join(__dirname, 'commands', file.name));
cmdscount++;
if (Array.isArray(cmds)) commands = [...commands, ...cmds];
else commands.push(cmds);
});

const options = {
intents: [
Expand All @@ -70,19 +50,37 @@ const options = {
};

console.log(
cgreen + strFormat(LANG.discordbot.main.commandsLoaded, [cmdscount]) + creset,
cgreen +
strFormat(LANG.discordbot.main.commandsLoaded, [
CommandManager.default.size,
]) +
creset,
);
const client = new Client(options);
console.log(LANG.discordbot.main.playerLoading);
const player = new Player(client);
player.extractors.loadDefault();
console.log(LANG.discordbot.main.setupActivityCalling);
activity.setupActivity(client);
/** @type {ClientMessageHandler | undefined} */
let messageHandler;

const features = fs
.readdirSync(path.join(__dirname, 'packages'))
.map((file) => {
console.log(`loading ${file} feature`);
const feature = require(file).feature;
if (feature == null) {
throw new TypeError(`${file} feature is undefined`);
}
return feature;
});
const featuresLoadPromise = Promise.all(
features.map((feature) => feature.onLoad?.(client)),
);

client.on('ready', async (readyClient) => {
enableTempLinks();
await featuresLoadPromise;
await Promise.all(
features.map((feature) => feature.onClientReady?.(readyClient)),
);
console.log(
strFormat(LANG.discordbot.ready.loggedIn, {
cgreen,
Expand All @@ -101,23 +99,17 @@ client.on('ready', async (readyClient) => {
status: 'dnd',
});
console.log(LANG.discordbot.ready.commandsRegistering);
await client.application.commands.set(commands.map((x) => x.data.toJSON()));
await CommandManager.default.setClient(client);
console.log(cgreen + LANG.discordbot.ready.commandsReady + creset);
const SyslogChannel = client.channels.cache.get(syslogChannel);
SyslogChannel.send(LANG.discordbot.ready.sysLog);
restoreQueues(player);
messageHandler = new ClientMessageHandler(readyClient);
});

onShutdown(async () => {
const SyslogChannel = client.channels.cache.get(syslogChannel);
await SyslogChannel.send(LANG.discordbot.shutdown.sysLog);
console.log('Saving queues');
for (const [guildId, queue] of player.nodes.cache) {
console.log(guildId);
await saveQueue(queue);
}
await player.destroy();
await Promise.all(features.map((feature) => feature.onUnload?.()));
await Promise.all([
client
.destroy()
Expand All @@ -128,73 +120,11 @@ onShutdown(async () => {
]);
});

client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return;

const command = commands.find((x) => x.data.name == interaction.commandName);
if (!command) {
console.error(
strFormat(LANG.discordbot.interactionCreate.unsupportedCommandError, [
interaction.commandName,
]),
);
return;
}
try {
await command.execute(interaction, client);
} catch (error) {
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: LANG.discordbot.interactionCreate.commandError,
ephemeral: true,
});
} else {
await interaction.reply({
content: LANG.discordbot.interactionCreate.commandError,
ephemeral: true,
});
}
throw error;
}
});

client.login(token);

client.on('messageCreate', (message) => messageHandler?.handleMessage(message));

//!EVENTS
player.events.on('playerStart', (queue, track) => {
// we will later define queue.metadata object while creating the queue
// queue.metadata.channel.send(`**${track.title}**を再生中`);
queue.metadata.channel.send({
embeds: [
{
title: strFormat(LANG.discordbot.playerStart.playingTrack, [
'**' +
strFormat(LANG.common.message.playerTrack, {
title: track.title,
duration: getDuration(track),
}) +
'**',
]),
thumbnail: {
url: track.thumbnail,
},
footer: {
text: strFormat(LANG.discordbot.playerStart.requestedBy, [
queue.currentTrack.requestedBy.tag,
]),
},
color: 0x5865f2,
},
],
});
});

player.events.on('playerFinish', (queue) => deleteSavedQueues(queue.guild.id));
player.events.on('queueDelete', (queue) => deleteSavedQueues(queue.guild.id));

player.on('error', () => console.log(LANG.discordbot.playerError.message));

process.on('uncaughtException', function (err) {
console.error(err);
Expand Down
100 changes: 100 additions & 0 deletions internal/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// @ts-check

const { strFormat, LANG } = require('../util/languages');

/**
* @template {boolean} [Ready = boolean]
* @typedef {import('discord.js').Client<Ready>} Client
*/

/**
* @typedef {import('../util/types').Command} Command
*/

class CommandManager {
/**
* @readonly
*/
static default = new CommandManager();

/** @type {import('discord.js').Client<true> | null} */
#client = null;

/** @type {Map<string, Command>} */
#commands = new Map();

/**
* クライアントにコマンドを登録する。
* @param {Client<true>} client ログイン済みのクライアント
*/
async setClient(client) {
this.#client = client;
const commands = [];
for (const command of this.#commands.values()) {
commands.push(command.data.toJSON());
}
await client.application.commands.set(commands);
client.on('interactionCreate', (interaction) => {
if (interaction.isChatInputCommand()) {
this.#handleInteraction(interaction, client);
}
});
}

/**
* コマンドを追加する。
* @param {Command | Command[]} commands 追加するコマンド
*/
addCommands(commands) {
if (Array.isArray(commands)) {
for (const command of commands) {
this.#commands.set(command.data.name, command);
}
} else {
this.#commands.set(commands.data.name, commands);
}
}

get client() {
return this.client;
}

get size() {
return this.#commands.size;
}

/**
* コマンドの処理を行う。
* @param {import('discord.js').ChatInputCommandInteraction} interaction
* @param {Client<true>} client
*/
async #handleInteraction(interaction, client) {
const command = this.#commands.get(interaction.commandName);
if (!command) {
console.error(
strFormat(LANG.discordbot.interactionCreate.unsupportedCommandError, [
interaction.commandName,
]),
);
return;
}
try {
await command.execute(interaction, client);
} catch (error) {
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: LANG.discordbot.interactionCreate.commandError,
ephemeral: true,
});
} else {
await interaction.reply({
content: LANG.discordbot.interactionCreate.commandError,
ephemeral: true,
});
}
throw error;
}
}
}

module.exports = { CommandManager };
Loading

0 comments on commit 87d075d

Please sign in to comment.