本記事は
Discordのチャット画面からランチの候補をkintoneに登録してみよう
の続きとして、Discordのモーダルフォームを使ってkintoneに登録したランチ候補を絞り込み、Discordにリスト表示する方法を紹介します。
そのため
Discordのチャット画面からランチの候補をkintoneに登録してみよう
のサンプルアプリが作成されていることを前提とします。
kintone APIトークンのアクセス権追加
固定リンクがコピーされました
前回作成したkintoneアプリの「APIトークン」の設定で、「アクセス権」に「レコード閲覧」を追加し、「保存」します。
コマンドプログラムの追加
固定リンクがコピーされました
前回作成したサンプルアプリと同じプロジェクトを使用し、コマンドプログラムファイルを追加します。
作成方法の詳細は、前回の
コマンドプログラムの作成
を参照してください。
次のコードを参考にプログラムを作成します。
「list-lunch.js」のファイル名で「utility」サブフォルダーの中に保存します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/*
* Discord bot slash commands sample scripts
* Copyright (c) 2024 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
const {ModalBuilder, ActionRowBuilder, TextInputBuilder, TextInputStyle, SlashCommandBuilder} = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('list-lunch')
.setDescription('条件に合った日本橋のランチ候補の店名リストを表示します'),
async execute(interaction) {
// モーダルを作成します
const modal = new ModalBuilder()
.setCustomId('listLunchModal')
.setTitle('List Lunch Modal');
// モーダルにコンポーネントを追加します
// テキスト入力コンポーネントを作成します
const couisineInput = new TextInputBuilder()
.setCustomId('couisineInput')
.setLabel('どんなランチの気分ですか?(カフェ、和食、イタリアン、中華、その他 から選択)')
.setStyle(TextInputStyle.Short)
.setRequired(true);
// アクション行にテキスト入力コンポーネントを設定します
const actionRow = new ActionRowBuilder().addComponents(couisineInput);
// 入力コンポーネントをモダルに追加します
modal.addComponents(actionRow);
// モーダルを表示します
await interaction.showModal(modal);
},
};
|
コマンドプログラムの解説
固定リンクがコピーされました
スラッシュコマンドでモーダルを表示するためのdiscord.jsクラスのモジュールをロードします。
1
|
const {ModalBuilder, ActionRowBuilder, TextInputBuilder, TextInputStyle, SlashCommandBuilder} = require('discord.js');
|
スラッシュコマンドを定義します。
1
2
3
|
data: new SlashCommandBuilder()
.setName('list-lunch')
.setDescription('条件に合った日本橋のランチ候補の店名リストを表示します')
|
スラッシュコマンドの実行時のインタラクションにレスポンスする関数を定義します。
1
2
3
4
|
async execute(interaction) {
// モーダルを表示します
await interaction.showModal(modal);
},
|
モーダルを定義します。
モーダルに追加するコンポーネントを定義し、アクション行としてモーダルに追加します。
モーダルに追加できるコンポーネントは、1行テキストShort
か複数行のテキストParagraph
のみです。
また、アクション行にはひとつのテキストコンポーネントのみ指定でき、最大5行までです。
スラッシュコマンドのレスポンスとしてモーダルを表示します。
モーダルの設定の詳細については
discord.js guide のモーダルの項
を参考にしてください。
スラッシュコマンドの設定に関する詳細は、discord.js guideの
Creating slash commands
の項を参照してください。
モーダルの設定に関する詳細は、discord.js guideの
Modals
の項を参照してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// モーダルを作成します
const modal = new ModalBuilder()
.setCustomId('listLunchModal')
.setTitle('List Lunch Modal');
// モーダルにコンポーネントを追加します
// テキスト入力コンポーネントを作成します
const couisineInput = new TextInputBuilder()
.setCustomId('couisineInput')
.setLabel('どんなランチの気分ですか?(カフェ、和食、イタリアン、中華、その他 から選択)')
.setStyle(TextInputStyle.Short)
.setRequired(true);
// アクション行にテキスト入力コンポーネントを設定します
const actionRow = new ActionRowBuilder().addComponents(couisineInput);
// 入力コンポーネントをモダルに追加します
modal.addComponents(actionRow);
// モーダルを表示します
await interaction.showModal(modal);
|
コマンドハンドラープログラムの修正
固定リンクがコピーされました
前回作成したサンプルアプリと同じプロジェクトを使用し、コマンドハンドラープログラムファイルを修正します。
作成方法の詳細は、前回の
コマンドハンドラープログラムの作成
を参照してください。
以下のコードを参考に「index.js」プログラムを修正します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
/*
* Discord bot command handler sample scripts
* Copyright (c) 2024 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
// 必要なdiscord.jsクラスを読み込みます
const fs = require('node:fs');
const path = require('node:path');
const {Client, Collection, Events, GatewayIntentBits, EmbedBuilder} = require('discord.js');
const {token, kintoneDomain, kintoneToken, kintoneAppId} = require('./config.json');
const {KintoneRestAPIClient} = require('@kintone/rest-api-client');
const kintoneClient = new KintoneRestAPIClient({
baseUrl: kintoneDomain,
// Use API Token authentication
auth: {apiToken: kintoneToken}
});
// クライアントインスタンスを新規作成します
const client = new Client({intents: [GatewayIntentBits.Guilds]});
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) {
const commandsPath = path.join(foldersPath, folder);
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
// コレクションの新規アイテムにキーをコマンド名、また、値をエクスポートモジュールとしてセットします
if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
} else {
console.log(`[警告] ${filePath} のコマンドに必要な "data" あるいは "execute" のプロパティーが抜けています`);
}
}
}
// クライアントインスタンスの準備が整った時、一度だけこのコードを実行します
client.once(Events.ClientReady, readyClient => {
console.log(`準備完了! ${readyClient.user.tag} としてログインしました`);
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
const command = interaction.client.commands.get(interaction.commandName);
if (!command) {
console.error(`${interaction.commandName} とマッチするコマンドが見つかりませんでした`);
return;
}
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
if (interaction.replied || interaction.deferred) {
await interaction.followUp({content: 'コマンド実行中にエラーが発生しました!', ephemeral: true});
} else {
await interaction.reply({content: 'コマンド実行中にエラーが発生しました!', ephemeral: true});
}
}
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isModalSubmit()) return;
if (interaction.customId === 'addLunchModal') {
const name = interaction.fields.getTextInputValue('nameInput');
const type = interaction.fields.getTextInputValue('typeInput');
const web = interaction.fields.getTextInputValue('siteInput');
const recordData = {
restaurant_name: {
value: name
},
couisine: {
value: type
},
url_link: {
value: web
}
};
kintoneClient.record.addRecord({app: kintoneAppId, record: recordData})
.then(async resp => {
console.log(resp);
await interaction.reply({content: `${name} がkintoneに登録されました。${web}`});
})
.catch(err => {
console.log(err);
});
} else if (interaction.customId === 'listLunchModal') {
const couisine = interaction.fields.getTextInputValue('couisineInput');
const query = 'couisine in ("' + couisine + '") order by restaurant_name asc limit 100';
kintoneClient.record.getRecords({app: kintoneAppId, query: query})
.then(async resp => {
console.log(resp);
if (resp.records.length > 0) {
const embed = new EmbedBuilder()
.setTitle('ランチ候補リスト')
.addFields(
{name: '種類:', value: '\u200B', inline: true},
{name: couisine, value: '\u200B', inline: true},
{name: '\u200B', value: '\u200B', inline: true}
);
resp.records.forEach(element => {
embed
.addFields(
{name: '店名', value: element.restaurant_name.value, inline: true},
{name: 'URL', value: element.url_link.value, inline: true},
{name: '\u200B', value: '\u200B', inline: true}
);
});
await interaction.reply({embeds: [embed]});
} else {
await interaction.reply({content: '該当するランチ情報はありませんでした'});
}
})
.catch(err => {
console.log(err);
});
}
});
// トークンにて Discord にログインします
client.login(token);
|
コマンドハンドラープログラムの解説
固定リンクがコピーされました
必要なdiscord.jsクラスを読み込みます。
今回は「EmbedBuilder」を追加しています。
1
|
const {Client, Collection, Events, GatewayIntentBits, EmbedBuilder} = require('discord.js');
|
受け取ったインタラクションのモーダルフォームのIDが「listLunchModal」の場合、モーダルに入力されたデータを取得します。
詳細は、discord.js guideの
Extracting data from modal submissions
の項を参照してください。
1
2
3
4
5
6
7
|
if (interaction.customId === 'addLunchModal') {
// Add Lunch モーダルの処理
} else if (interaction.customId === 'listLunchModal') {
const couisine = interaction.fields.getTextInputValue('couisineInput');
const query = 'couisine in ("' + couisine + '") order by restaurant_name asc limit 100';
}
|
入力された「ランチの種類」からkintoneにレストランのリストを問い合わせます。
取得したレストランの「店名」、「URL」をリスト表示します。
モーダル送信のレスポンスの詳細は、discord.js guideの
Responding to modal submissions
の項を参照してください。
今回はランチのリストを表示するのにEmbeds
を使用しています。
Embeds
の詳細は、discord.js guideの
Embeds
の項を参照してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
kintoneClient.record.getRecords({app: kintoneAppId, query: query})
.then(async resp => {
if (resp.records.length > 0) {
const embed = new EmbedBuilder()
.setTitle('ランチ候補リスト')
.addFields(
{name: '種類:', value: '\u200B', inline: true},
{name: couisine, value: '\u200B', inline: true},
{name: '\u200B', value: '\u200B', inline: true}
);
resp.records.forEach(element => {
embed
.addFields(
{name: '店名', value: element.restaurant_name.value, inline: true},
{name: 'URL', value: element.url_link.value, inline: true},
{name: '\u200B', value: '\u200B', inline: true}
);
});
await interaction.reply({embeds: [embed]});
} else {
await interaction.reply({content: '該当するランチ情報はありませんでした'});
}
})
.catch(err => {
console.log(err);
});
|
コマンドデプロイメントプログラムの作成
固定リンクがコピーされました
コマンドデプロイメントプログラムは前回の記事で使用したプログラムと同様です。
作成方法の詳細は、前回の
コマンドデプロイメントプログラムの作成
を参照してください。
コマンドデプロイメントプログラムの実行
固定リンクがコピーされました
コマンドデプロイメントプログラムの詳細は、discord.js guideの
Registering slash commands
の項を参照してください。
すべてのプログラムの作成が完了したら、以下のコマンドを実行してDiscordにコマンドを再登録し、アップデートします。
1
|
node deploy-commands.js
|
コマンドターミナルで次のコマンドを実行し、Discord Botプログラムを実行します。
起動したLunch BotがDiscordアプリ上でオンライン表示されているのを確認します。
/list-lunch
コマンドをDiscordアプリで実行します。
モーダルフォームが表示されるのでランチの種類を入力し(カフェ、和食、イタリアン、中華、その他から選択)、送信ボタンをクリックします。
入力したランチの種類でkintoneのアプリに登録されているレストラン情報が表示されていれば成功です。
今回も、サンプルアプリのため、ローカルサーバーを起動して動作確認を行いましたが、「Google Cloud Run」や「Digital Ocean」等のクラウドサービスプロバイダーにデプロイして実運用してください。
本記事では、前回紹介した
Discordのチャット画面からランチの候補をkintoneに登録してみよう
を活用して、kintoneアプリに登録されているランチ候補のレストランを表示するプログラムを作成しました。
プログラム内でコマンドを定義すれば、他にもいろいろなレコード情報を取得できます。
業務アプリに応用すれば、直接kintoneアプリにログインしなくても、Discordのチャット上で、必要な情報を獲得できます。