@femshima/djs-interaction v0.2.1
djs-interaction
discord.jsのコマンドやComponentの定義と Interactionのハンドラを近い位置に書けるようにするフレームワークです。
使い方
詳しい使い方はsample/を見てください。
| Interactionの種類 | 説明 |
|---|---|
Command | スラッシュコマンド |
SubCommandGroup | スラッシュコマンド関連、ApplicationCommandSubGroupDataに相当 |
SubCommand | スラッシュコマンド関連、ApplicationCommandSubCommandDataに相当 |
MessageContextMenu | メッセージを右クリックすると出てくるコンテキストメニュー |
UserContextMenu | ユーザーを右クリックすると出てくるコンテキストメニュー |
Button | ボタン |
SelectMenu | 選択ボックス |
Modal | モーダルウィンドウ |
Setup
djs-interactionのCommand、SubCommandGroup、SubCommand、MessageContextMenu、UserContextMenu、Button、SelectMenu、Modalを継承したクラスを一つでもインスタンス化する前にframe.setup()を呼び出す必要があります。
また、frame.setup()を複数回呼び出すとその都度コマンドの登録が行われるため、問題が発生する可能性があります。
import { frame } from 'djs-interaction';
//...
await frame.setup({
client,
commands: {
...Command,
...ContextMenu,
},
components: Component,
guilds: !env.production,
subscribeToEvent: true,
async fallback(interaction) {
if ('replied' in interaction && !interaction.replied) {
await interaction.reply('Unknown interaction.');
}
},
});frame.setup()の引数は一つで、次のようなオブジェクトです。
| キー | 説明 |
|---|---|
| client | Discord.jsのclientです。 |
| commands | 登録するコマンド(Command、MessageContextMenu、UserContextMenuを継承したもの)をすべてここに指定します。コマンドのクラス(インスタンス化してあっても、する前のものでも構いません)の配列または、keyを文字列、それらのクラスをvalueとするオブジェクトを渡してください。 |
| components | 使用時に都度インスタンス化して使うもの(Button、SelectMenu、Modalを継承したもの)をすべてここに指定します。コマンドのクラス(インスタンス化する前のものである必要があります)の配列または、keyを文字列、それらのクラスをvalueとするオブジェクトを渡してください。 |
| subscribeToEvent | setupメソッドの中で自動的にinteractionCreateイベントにハンドラを登録するかどうかbooleanで指定します。falseをセットした場合、別の場所でframe.interactionCreateをハンドラとして登録する必要があります。 |
| fallback | 受信したinteractionのハンドラが見つからなかった場合や、ハンドラが応答しなかった場合に呼び出される関数です。 |
| database | データベースとの連携を使用する場合、このオプションを使います。詳しくはデータベースと連携させるを参照してください。 |
| idGen | idを生成するクラスのインスタンスを指定します。ユニークなID(文字列)を生成して返すgenerateIDメソッドを実装している必要があります。 |
スラッシュコマンド(ChatInputApplicationCommand)
すべてのコマンド定義はCommandを継承している必要があります。
constructorでコマンド定義をsuperに渡して呼び出します。
handlerを実装していなくてもTypeScriptのエラーは出ませんが、interactionに応答しないとDiscord側でエラーメッセージが表示されるため、SubCommandを使用する時以外は実装することが推奨されます。
import { CommandInteraction } from 'discord.js';
import { Command } from 'djs-interaction';
export default class Ping extends Command {
constructor() {
super({
name: 'ping',
description: 'Ping!',
});
}
async handle(interaction: CommandInteraction<'cached'>) {
await interaction.reply({ content: 'Pong!' });
}
}サブコマンド
djs-interactionはサブコマンドにも対応しています。
サブコマンドではSubCommandを継承してください。
通常のコマンドと同じように、constructorでコマンド定義をsuperに渡して呼び出します。
ただし、通常のコマンドと異なり、handlerを定義しないとTypeScriptでエラーになります。
import { ChatInputCommandInteraction } from 'discord.js';
import { SubCommand } from 'djs-interaction';
export default class Locale extends SubCommand {
constructor() {
super({
name: 'locale',
description: 'shows supported locales',
});
}
async handle(interaction: ChatInputCommandInteraction<'cached'>) {
await interaction.reply('en,ja');
}
}サブコマンドを定義したら、それらをオプションとしてまとめたコマンドを定義します。
import { Command } from 'djs-interaction';
import Admin from './admin';
import Langs from './langs';
import Locale from './locales';
export default class Greet extends Command {
constructor() {
super({
name: 'greet',
description: 'Commands about greetings',
options: [new Langs(), new Locale(), new Admin()],
});
}
}なお、サブコマンドをもつコマンドでも、通常のコマンド同様handlerを定義することができます。handlerはCommand、(存在する場合は)SubCommandGroup、SubCommandの順に呼び出されます。
handler内でdjs-interactionからインポートしたAbortErrorをthrowすると、そのhandler以降のhandlerは実行されません。すなわち、CommandのhandlerでAbortErrorをthrowすると、SubCommandGroup、SubCommandのhandlerは実行されません。
//...
async handle(interaction: ChatInputCommandInteraction<'cached'>) {
if (
!interaction.member.permissions.has(PermissionFlagsBits.Administrator)
) {
await interaction.reply(
'You are not admin, so you cannot use this command.'
);
throw new AbortError();
}
}
//...サブコマンドグループ
サブコマンドをまとめるコマンドと同様に使います。ただし、CommandではなくSubCommandGroupを継承してください。
ContextMenu
ContextMenuには、ユーザーを右クリックしたときに実行されるUserApplicationCommandとメッセージを右クリックしたときに実行されるMessageApplicationCommandがあります。
基本的にはコマンド定義と同様ですが、UserApplicationCommandはUserContextMenuを、MessageApplicationCommandはMessageContextMenuを継承したクラスを作成してください。
Component
ここで、ComponentはButton、SelectMenu、Modalのことを指しています(Modalは微妙かもしれませんが)。
これらもコマンド同様、定義をconstructor内でsuperに渡すだけです。
Buttonはhandlerを定義しなくてもエラーにはなりませんが、これはstyleがLinkのときにhandlerを定義しなくてもいいためです。そうでない場合は定義すべきです。
データベースと連携させる
デフォルトではInteractionの定義はメモリに保存されるため、プログラムを終了させた時点で蒸発します。これを防ぐには、データベースなどに保存しておく必要があります。
djs-interactionでデータベースを使うには、frame.setupの実行時にdatabaseオプションを指定します。
Prismaを使う場合
まず、スキーマの例を示します。重要なのはmodel Interactionの部分だけですので、他の部分は適宜変更してください。また、列名と型が同じであればテーブル名を変えても構いません。
generator client {
provider = "prisma-client-js"
binaryTargets = ["native"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Interaction {
id String @id
classKey String
classVersion String?
data Json
}setup部分は次のように書きます。テーブル名はスキーマに合わせてください。
const prisma = new PrismaClient()
await frame.setup({
//...
database: prisma.interaction
});Prismaを使わない場合
await frame.setup({
//...
database: {
findUnique(options) {
// options.where.idがidに一致するレコードを探して返します。
// レコードの形式は次のようになっています。
// {
// id: string; // depends on what kind of idgen you use.
// classKey: string; // the key set in class or the name of the class
// classVersion: string | null; // version set in class or null
// data: JsonObject;
// }
//
// 例:
// {
// id: 'id-1',
// classKey: 'Target',
// classVersion: null,
// data: {
// type: 'MODAL',
// message: 'msg',
// data: { d: 'X' },
// },
// }
},
create(options) {
// findUniqueで説明したようなレコードがoptions.dataとして渡されるので、データベースに登録します。
// idが重複することは想定されていませんので、重複した場合は例外を投げるべきです。
}
}
});