SelectMenuを使ってみよう
#TODO セレクトメニューv2を使ったほうが良いです SelectMenuで選択されたロールをすべて持っているメンバーを取得し、embedとして送信するコードです。
https://gyazo.com/85ee547a7d448cb01c7df806030f8a41
code:index.js
const Discord = require("discord.js");
const client = new Discord.Client({
intents: Discord.Intents.FLAGS.GUILD_MEMBERS | Discord.Intents.FLAGS.GUILDS
});
/**
*
* @param {Array<Discord.Role>} roles
* @param {Array<Discord.GuildMember>} members
*/
function buildEmbeds(roles, members) {
return [
new Discord.MessageEmbed().setTitle("検索対象のロール").setDescription(roles.map(e => ${e}).join("\n")),
new Discord.MessageEmbed().setTitle("検索結果").setDescription(members.map(e => ${e}).join("\n")),
];
}
/**
*
* @param {Array<Discord.Member>} members
*/
function buildComponents(roles, customId) {
return [
new Discord.MessageActionRow().addComponents(
new Discord.MessageSelectMenu()
.setCustomId(customId)
.setMinValues(0)
.setMaxValues(roles.length)
.addOptions(
label: role.name,
value: role.id,
default: selected,
})
)
)
)
]
}
/**
*
* @param {Discord.Interaction} interaction
*/
async function onInteraction(interaction) {
if (!interaction.isCommand()) {
return;
}
if (!interaction.guildId) {
await interaction.reply({
content: "サーバーで実行する必要があります。"
});
return;
}
await interaction.reply({
ephemeral: true,
});
const guild = interaction.guild;
const allMembers = await guild.members.fetch();
const customId = Math.random().toString(36).substring(7);
/** @type {Array<Discord.GuildMember>} */
let roles = [];
await interaction.editReply({
embeds: buildEmbeds([], []),
});
const collector = new Discord.InteractionCollector(client, {
filter: (interaction) => interaction.isSelectMenu() && interaction.customId === customId,
time: 15 * 60 * 1000,
});
/**
*
* @param {Discord.SelectMenuInteraction} interaction
*/
async function onInteraction(interaction) {
roles = (interaction.values ?? []).map(id => guild.roles.resolve(id));
await interaction.update({
embeds: buildEmbeds(roles, allMembers.filter(member => roles.every(e => member.roles.cache.has(e.id)))),
})
}
collector.on("collect", (interaction) => onInteraction(interaction).catch(console.error));
}
client.on("interactionCreate", (interaction) => onInteraction(interaction).catch(console.error));
client.login(process.env.DISCORD_TOKEN).catch(console.error);
ここがダメ
ロールが25個より多いときどうする?
検索結果が4096文字を超えたときは?
対処するとまたコードが複雑になるので各自で頑張ってtig.icon
code:register.js
const { Client, ClientApplication } = require("discord.js");
/**
*
* @param {Client} client
* @param {import("discord.js").ApplicationCommandData[]} commands
* @param {import("discord.js").Snowflake} guildId
* @returns {Promise<import("@discordjs/collection").Collection<string,import("discord.js").ApplicationCommand>>}
*/
async function register(client, commands, guildId) {
if (guildId == null) {
return client.application.commands.set(commands);
}
const guild = await client.guilds.fetch(guildId);
return guild.commands.set(commands);
}
const search = {
name: "search",
description: "ロールでユーザーを検索します",
};
const client = new Client({
intents: 0,
});
client.token = process.env.DISCORD_TOKEN;
async function main() {
client.application = new ClientApplication(client, {});
await client.application.fetch();
await register(client, commands, process.argv2); console.log("registration succeed!");
}
main().catch(err=>console.error(err));
(〜v14.6.x)
clientのinteractionCreateイベントで判別する場合はinteraction.isSelectMenu()を使う。
code:js
const { Client, GatewayIntentBits, EmbedBuilder, ActionRowBuilder, SelectMenuBuilder } = require("discord.js");
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers
]
});
client.on("interactionCreate", async (interaction) => {
if (interaction.isChatInputCommand()){
if (interaction.commandName === "search"){
await interaction.reply({
embeds: [
new EmbedBuilder()
.setTitle("全メンバーを取得中")
.setDescription("しばらくお待ちください")
],
ephemeral: true
})
const roles = interaction.guild.roles.cache.first(25)
const customId = Number(interaction.id).toString(36)
const SelectMenu = new SelectMenuBuilder()
.setCustomId(customId)
.setMinValues(0)
.setMaxValues(roles.length)
.setOptions(
roles.map(role =>
(
{
name: role.name,
id: role.id
}
)
)
)
interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle("🔍検索")
.setDescription("検索対象のロールを選択してください。")
],
components: [
new ActionRowBuilder().setComponents(SelectMenu)
]
})
try {
const selected = await interaction.channel.awaitMessageComponent({
filter: interacton => interaction.customId === customId,
time: 60 * 1000
})
const members = await interaction.guild.members.fetch()
const searchResult = members.filter(member => member.roles.cache.hasAll(selected.values))
interaction.update({
embeds: [
new EmbedsBuilder()
.setTitle("🔍検索")
.setFields(
{
name: "検索対象",
value: selected.values
.map(
value => roles.get(value).toString()
)
.join("\n")
},
{
name: "検索結果"
value: searchResult
.map(member => member.toString())
.join("\n")
}
)
],
components: []
})
} catch (error) {
interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle("🔍検索")
.setDescription("60秒が経過しても反応がなかったため終了しました。")
],
components: []
})
}
}
}
})
(v14.7.0〜)
RoleSelectMenuBuilderがありますが、DocsがないためStringSelectMenuBuilderを使用しています(.addOptions()が使えなさそうだった) clientのinteractionCreateイベントで判別する場合はinteraction.is(種類)SelectMenu()を使う(文字列のセレクトメニューの場合は.isStringSelectMenu()、ロールのセレクトメニューの場合は.isRoleSelectMenu()、チャンネルのセレクトメニューの場合は.isChannelSelectMenu()、ユーザーのセレクトメニューの場合は.isUserSelectMenu()、全てのセレクトメニューを受け取りたい場合は.isAnySelectMenu()を使う)。
code:js
const { Client, GatewayIntentBits, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } = require("discord.js");
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers
]
});
client.on("interactionCreate", async (interaction) => {
if (interaction.isChatInputCommand()){
if (interaction.commandName === "search"){
await interaction.reply({
embeds: [
new EmbedBuilder()
.setTitle("全メンバーを取得中")
.setDescription("しばらくお待ちください")
],
ephemeral: true
})
const roles = interaction.guild.roles.cache.first(25)
const customId = Number(interaction.id).toString(36)
const SelectMenu = new StringSelectMenuBuilder()
.setCustomId(customId)
.setMinValues(0)
.setMaxValues(roles.length)
.setOptions(
roles.map(role =>
(
{
name: role.name,
id: role.id
}
)
)
)
interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle("🔍検索")
.setDescription("検索対象のロールを選択してください。")
],
components: [
new ActionRowBuilder().setComponents(SelectMenu)
]
})
try {
const selected = await interaction.channel.awaitMessageComponent({
filter: interacton => interaction.customId === customId,
time: 60 * 1000
})
const members = await interaction.guild.members.fetch()
const searchResult = members.filter(member => member.roles.cache.hasAll(selected.values))
interaction.update({
embeds: [
new EmbedsBuilder()
.setTitle("🔍検索")
.setFields(
{
name: "検索対象",
value: selected.values
.map(
value => roles.get(value).toString()
)
.join("\n")
},
{
name: "検索結果"
value: searchResult
.map(member => member.toString())
.join("\n")
}
)
],
components: []
})
} catch (error) {
interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle("🔍検索")
.setDescription("60秒が経過しても反応がなかったため終了しました。")
],
components: []
})
}
}
}
})