Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: スラッシュコマンドの対応 #974

Merged
merged 28 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7d592b0
Add schemaToDiscordFormat
MikuroXina Aug 13, 2023
8a30717
Add SLASH_COMMAND feature
MikuroXina Aug 13, 2023
0e04e24
Implement registration
MikuroXina Aug 13, 2023
82de16f
Delete commands on no feature
MikuroXina Aug 13, 2023
56dea6b
Add CLIENT_ID description to README
MikuroXina Aug 13, 2023
5dc9c83
Fix to send empty array
MikuroXina Aug 13, 2023
c4a06a2
Fix to support MESSAGE
MikuroXina Aug 13, 2023
60c7bcd
Fix to add key as name
MikuroXina Aug 13, 2023
c230d27
Require description on subcommand clause
MikuroXina Aug 13, 2023
db078b4
Add description field for subcommand
MikuroXina Aug 13, 2023
b75eb87
Require description field on Schema
MikuroXina Aug 13, 2023
5879882
Add description field
MikuroXina Aug 13, 2023
e530472
Fix map of names
MikuroXina Aug 13, 2023
0270a5f
Update test sample
MikuroXina Aug 13, 2023
602e9d5
Fix to add description
MikuroXina Aug 13, 2023
358e559
Fix order of options
MikuroXina Aug 13, 2023
0a030ab
Fix test cases
MikuroXina Aug 13, 2023
753f5c4
Replace CLIENT_ID as APPLICATION_ID
MikuroXina Aug 13, 2023
80c93f4
Remove empty options
MikuroXina Aug 13, 2023
3cf0b88
Minimize options
MikuroXina Aug 13, 2023
0ee002a
Decompose into post requests
MikuroXina Aug 13, 2023
649cb3b
Add APPLICATION_ID to example
MikuroXina Aug 13, 2023
f3642b8
Improve registration
MikuroXina Aug 13, 2023
4796e4b
Compare objects with deep-equal
MikuroXina Aug 14, 2023
0b6073d
Fix displaying length
MikuroXina Aug 14, 2023
0b9c213
Fix parameter name
MikuroXina Aug 14, 2023
b23e4ec
Fix to support CHANNEL
MikuroXina Aug 14, 2023
dddbdc9
Avoid deferReply
MikuroXina Aug 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
DISCORD_TOKEN=
MAIN_CHANNEL_ID=
APPLICATION_ID=
GUILD_ID=
PREFIX=!
FEATURE=MESSAGE_CREATE,MESSAGE_UPDATE,COMMAND,VOICE_ROOM,ROLE,EMOJI
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ OreOreBot2 の [Discussions](https://github.com/approvers/OreOreBot2/discussions

起動時にデフォルト値が存在する変数の値が指定されていない場合は、そのデフォルト値が使われます。

| 変数名 | 説明 | 必須 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| `DISCORD_TOKEN` | BOT のトークン | True |
| `MAIN_CHANNEL_ID` | VoiceDiff(VC 入退室ログ)を送信する **テキスト** チャンネルの ID | True |
| `GUILD_ID` | 限界開発鯖の ID | True |
| `PREFIX` | コマンドの接頭辞、デフォルト値は `"!"` | False |
| `FEATURE` | 有効にする機能のカンマ区切り文字列、デフォルト値は全ての機能。`"MESSAGE_CREATE"`, `"MESSAGE_UPDATE"`, `"COMMAND"`, `"VOICE_ROOM"`, `"ROLE"`, `"EMOJI"` を組み合わせ可能。 | False |
| 変数名 | 説明 | 必須 |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| `DISCORD_TOKEN` | BOT のトークン | True |
| `MAIN_CHANNEL_ID` | VoiceDiff(VC 入退室ログ)を送信する **テキスト** チャンネルの ID | True |
| `APPLICATION_ID` | BOT のアプリケーション ID | True |
| `GUILD_ID` | 限界開発鯖の ID | True |
| `PREFIX` | コマンドの接頭辞、デフォルト値は `"!"` | False |
| `FEATURE` | 有効にする機能のカンマ区切り文字列、デフォルト値は全ての機能。`"MESSAGE_CREATE"`, `"MESSAGE_UPDATE"`, `"COMMAND"`, `"VOICE_ROOM"`, `"ROLE"`, `"EMOJI"`, `"SLASH_COMMAND"` を組み合わせ可能。 | False |
m1sk9 marked this conversation as resolved.
Show resolved Hide resolved

### インストールと実行

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@discordjs/voice": "^0.16.0",
"@js-temporal/polyfill": "^0.4.3",
"date-fns": "^2.29.3",
"deep-equal": "^2.2.2",
"discord.js": "^14.7.1",
"dotenv": "^16.0.3",
"fast-diff": "^1.2.0",
Expand All @@ -44,6 +45,7 @@
"devDependencies": {
"@codedependant/semantic-release-docker": "^4.1.0",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/deep-equal": "^1.0.1",
"@types/node": "^20.0.0",
"@types/yargs": "^17.0.22",
"@typescript-eslint/eslint-plugin": "^6.0.0",
Expand Down
125 changes: 125 additions & 0 deletions src/adaptor/command-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type {
Param,
Schema,
SubCommandEntries
} from '../model/command-schema.js';

const entriesToOptions = (entries: SubCommandEntries): unknown[] =>
Object.keys(entries).map((key) => {
const entry = entries[key];
if (entry.type === 'SUB_COMMAND') {
const options = entry.params?.map(paramToOption) ?? [];
return {
type: 1, // SUB_COMMAND
name: key,
description: entry.description,
options: options.length === 0 ? undefined : options
};
} else {
const options = entriesToOptions(entry.subCommands);
return {
type: 2, // SUB_COMMAND_GROUP
name: key,
description: entry.description,
options: options.length === 0 ? undefined : options
};
}
});
const paramToOption = (param: Param): unknown => {
switch (param.type) {
case 'STRING':
return {
type: 3, // STRING
name: param.name,
description: param.description,
required: param.defaultValue === undefined,
min_length: param.minLength,
max_length: param.maxLength
};
case 'INTEGER':
return {
type: 4, // INTEGER
name: param.name,
description: param.description,
required: param.defaultValue === undefined,
min_value: param.minValue,
max_value: param.maxValue
};
case 'BOOLEAN':
return {
type: 5, // BOOLEAN
name: param.name,
description: param.description,
required: param.defaultValue === undefined
};
case 'USER':
return {
type: 6, // USER
name: param.name,
description: param.description,
required: param.defaultValue === undefined
};
case 'CHANNEL':
return {
type: 7, // CHANNEL
name: param.name,
description: param.description,
required: param.defaultValue === undefined
};
case 'ROLE':
return {
type: 8, // ROLE
name: param.name,
description: param.description,
required: param.defaultValue === undefined
};
case 'MESSAGE':
return {
type: 3, // STRING
name: param.name,
description: param.description,
required: param.defaultValue === undefined
};
case 'FLOAT':
return {
type: 10, // NUMBER
name: param.name,
description: param.description,
required: param.defaultValue === undefined,
min_value: param.minValue,
max_value: param.maxValue
};
case 'CHOICES':
return {
type: 3, // STRING
name: param.name,
description: param.description,
choices: param.choices.map((choice) => ({
name: choice,
value: choice
})),
required: param.defaultValue === undefined,
autocomplete: true
};
case 'VARIADIC':
return {
type: 3, // STRING
name: param.name,
description: param.description,
required: param.defaultValue === undefined
};
default:
throw new Error('unreachable');
}
};
export const schemaToDiscordFormat = (schema: Schema): unknown[] =>
schema.names.map((name) => {
const options = (schema.params?.map(paramToOption) ?? []).concat(
entriesToOptions(schema.subCommands)
);
return {
name,
description: schema.description,
options: options.length === 0 ? undefined : options
};
});
10 changes: 4 additions & 6 deletions src/adaptor/proxy/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
Message,
type MessageActionRowComponentBuilder,
type Interaction,
type MessageReplyOptions,
type InteractionReplyOptions,
InteractionResponse,
type InteractionReplyOptions
type MessageReplyOptions
} from 'discord.js';

import { type Schema, makeError } from '../../model/command-schema.js';
Expand Down Expand Up @@ -113,8 +113,6 @@ export class DiscordCommandProxy implements CommandProxy {
return;
}

await interaction.deferReply();

const [schema, listener] = entry;

const [tag, parsedArgs] = parseOptions(
Expand All @@ -124,7 +122,7 @@ export class DiscordCommandProxy implements CommandProxy {
);
if (tag === 'Err') {
const error = makeError(parsedArgs);
await interaction.editReply(error.message);
await interaction.reply(error.message);
return;
}

Expand All @@ -142,7 +140,7 @@ export class DiscordCommandProxy implements CommandProxy {
senderName: interaction.user.username,
args: parsedArgs,
async reply(embed) {
const mes = await interaction.editReply({
const mes = await interaction.reply({
embeds: [convertEmbed(embed)]
});
return {
Expand Down
62 changes: 32 additions & 30 deletions src/adaptor/proxy/command/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { parseStrings } from './schema.js';

test('no args', () => {
const SERVER_INFO_SCHEMA = {
names: ['serverinfo'],
names: ['guildinfo', 'serverinfo', 'guild', 'server'],
description: '限界開発鯖の情報を持ってくるよ',
subCommands: {}
} as const;

Expand All @@ -20,42 +21,48 @@ test('no args', () => {
});

test('single arg', () => {
const TIME_OPTION = [
const TIME_OPTIONS = [
{ name: 'at', description: '', type: 'STRING' }
] as const;
const KAERE_SCHEMA = {
names: ['kaere'],
description: 'VC内の人類に就寝を促すよ',
subCommands: {
start: {
type: 'SUB_COMMAND'
},
bed: {
type: 'SUB_COMMAND_GROUP',
description: '強制切断モードを取り扱うよ',
subCommands: {
enable: {
type: 'SUB_COMMAND'
type: 'SUB_COMMAND',
description: '強制切断モードを有効化するよ'
},
disable: {
type: 'SUB_COMMAND'
type: 'SUB_COMMAND',
description: '強制切断モードを無効化するよ'
},
status: {
type: 'SUB_COMMAND'
type: 'SUB_COMMAND',
description: '現在の強制切断モードの設定を確認するよ'
}
}
},
reserve: {
type: 'SUB_COMMAND_GROUP',
description: '予約システムを取り扱うよ',
subCommands: {
add: {
type: 'SUB_COMMAND',
params: TIME_OPTION
description: '指定の時刻で予約するよ',
params: TIME_OPTIONS
},
cancel: {
type: 'SUB_COMMAND',
params: TIME_OPTION
description: '指定時刻の予約をキャンセルするよ',
params: TIME_OPTIONS
},
list: {
type: 'SUB_COMMAND'
type: 'SUB_COMMAND',
description: '現在の予約を一覧するよ'
}
}
}
Expand All @@ -72,21 +79,6 @@ test('single arg', () => {
}
]);

const oneParamRes = parseStrings(['kaere', 'start'], KAERE_SCHEMA);

expect(oneParamRes).toStrictEqual([
'Ok',
{
name: 'kaere',
params: [],
subCommand: {
name: 'start',
type: 'PARAMS',
params: []
}
}
]);

const subCommandRes = parseStrings(
['kaere', 'reserve', 'add', '01:12'],
KAERE_SCHEMA
Expand All @@ -113,10 +105,20 @@ test('single arg', () => {
test('multi args', () => {
const ROLE_CREATE_SCHEMA = {
names: ['rolecreate'],
description: 'ロールを作成するよ',
subCommands: {},
params: [
{ type: 'USER', name: 'target', description: '' },
{ type: 'STRING', name: 'color', description: '', defaultValue: 'random' }
{
type: 'STRING',
name: 'name',
description: '作成するロールの名前を指定してね'
},
{
type: 'STRING',
name: 'color',
description:
'作成するロールの色を[HEX](https://htmlcolorcodes.com/)で指定してね'
}
]
} as const;

Expand All @@ -125,15 +127,15 @@ test('multi args', () => {
expect(noParamRes).toStrictEqual(['Err', ['NEED_MORE_ARGS']]);

const oneParamRes = parseStrings(
['rolecreate', '0123456789'],
['rolecreate', '0123456789', '#bedead'],
ROLE_CREATE_SCHEMA
);

expect(oneParamRes).toStrictEqual([
'Ok',
{
name: 'rolecreate',
params: ['0123456789', 'random']
params: ['0123456789', '#bedead']
}
]);
});
Loading