generated at
ボタンを使ってみよう
セキュリティ上の問題がありましたが現在は修正済みです
仕様
ボタンを押すと役職が付与される
すでに役職を持っている場合はエラーにする。
customId application/x-www-form-urlencoded 形式とした。
d は機能の種類を表す(ここでは rp )
rid はロールのid
100文字以下に収まるならば好きなフォーマットを使って良い

ボタンを設置するためのコード
install.js
const Discord = require("discord.js"); const client = new Discord.Client({ intents: 0 }); client.token = process.env.DISCORD_TOKEN; const GUILD_ID = process.argv[2]; const CHANNEL_ID = process.argv[3]; const ROLE_ID = process.argv[4]; async function main() { const guild = await client.guilds.fetch(GUILD_ID); /** @type {Discord.TextChannel} */ const channel = await guild.channels.fetch(CHANNEL_ID); const params = new URLSearchParams(); params.append("d", "rp"); params.append("rid", ROLE_ID); const button = new Discord.MessageButton() .setCustomId(params.toString()) .setStyle("PRIMARY") .setLabel("にゃーん") .setEmoji("🐈"); await channel.send({ content: "猫になりたい", components: [ new Discord.MessageActionRow().addComponents(button) ] }); } main().catch(err => console.error(err));
解説
send components をもつ MessageOptions を渡すとボタンなどをメッセージにつけることができる。
MessageOptions discord.js
ボタンのスタイルは必ず指定しなくてはならない。
上から PRIMARY SUCCESS SECONDARY DANGER LINK setStyle に指定することで使用できます。
最後は setEmoji を使用した場合の例です。
#TODO まともな画像に置き換える。
Button Styles Discord Developer Poral
ラベルと絵文字のどちらかは指定しなくてはならない。
customId は1から100文字の文字列である必要がある。
LINK の場合は customId の代わりに url を指定しなければならない
URLSearchParams
https://google.com/search?q=xxxx q=xxxx の部分のパースや組み立てを容易に行える。
二度目になるが特にこれを使用しなければならない理由はないので好きなフォーマットを使って良い。
本来はコマンドやウェブサイトなどから操作できて然るべきであるとは思うけどめんどくさかったtig.icon
sh
node ./install.js <GUILD_ID> <CHANNEL_ID> <ROLE_ID>
TypeError: guild.channels.fetch is not a function
discord.jsのバージョンを確認してください

ボタンが押された際の処理を行うコード
index.js
const Discord = require("discord.js"); const client = new Discord.Client({ intents: 0, partials: ["GUILD_MEMBER", "USER"] }); /** * * @param {unknown} err * @param {object} ctx * @param {Discord.ButtonInteraction} ctx.interaction * @param {Discord.Snowflake} ctx.role_id * @param {string} ctx.role_mention * @returns */ async function handleError(err, { interaction, role_id, role_mention }) { if (err instanceof Discord.DiscordAPIError) { switch (err.code) { case 10011: await interaction.followUp(`役職の付与に失敗しました。\n付与しようとした役職(id: \`${role_id}\`)は存在しません。\n(サーバ管理者へ連絡してください。)`); return; case 50013: await interaction.followUp( `${role_mention}の付与に失敗しました。\nBotに十分な権限がありません。\n(サーバ管理者へ連絡してください。)`, ); return; } } interaction.followUp(`${role_mention}の付与に失敗しました。\n時間をおいてやり直してください。`).catch(() => { }); throw err; } /** * * @param {Discord.ButtonInteraction} interaction * @param {URLSearchParams} params * @returns */ async function rolePanel(interaction, params) { /** @type {Discord.Snowflake} */ const role_id = params.get("rid"); await interaction.deferReply({ ephemeral: true }); const guild = await interaction.guild.fetch(); // APIからのメンバーオブジェクト(discord.jsのGuildMemberでないもの)がそのまま渡ってくることがあるのでfetchすることで確実にGuildMemberとする。 // interaction.member.user.idでなければならない。なぜならば、APIInteractionGuildMemberはid を直接持たないからである。 const member = await guild.members.fetch(interaction.member.user.id,{ force: true // intentsによってはGuildMemberUpdateが配信されないため }); const role_mention = `<@&${role_id}>`; if (member.roles.resolve(role_id)) { await interaction.followUp(`すでに、${role_mention}を持っています。`); return; } try { await member.roles.add(role_id); } catch (err) { await handleError(err, { interaction, role_id, role_mention }); return; } await interaction.followUp({ content: `${role_mention} を付与しました。` }); } const buttons = { rp: rolePanel }; /** * * @param {Discord.Interaction} interaction */ async function onInteraction(interaction) { if (!interaction.isButton()) { return; } const params = new URLSearchParams(interaction.customId); await buttons[params.get("d")](interaction, params); } client.once("ready", () => { console.log(`Logged in as: ${client.user.username}#${client.user.discriminator}`); // ready後にready以前に実行されたinteractionのinteractionCreateがemitされるが、そのときにはinteractionがtimeoutしておりfollowupで失敗することがよくある。 // そのようなことを避けるためready内でハンドラを登録する。 client.on("interactionCreate", (interaction) => onInteraction(interaction).catch(err => console.error(err))); }); client.login(process.env.DISCORD_TOKEN);
ButtonInteraction#deferReply
即時にbotが応答を返せない場合に後で応答を返すことをDiscord APIに伝えるために使用する。
ButtonInteraction#followUp
実際の応答を返す。

js
const { ButtonBuilder, ButtonStyle, ActionRowBuilder, Client, GatewayIntentBits, PermissionsBitField } = require("discord.js") const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers ] }) const BUTTON_ID_PREFIX = "role_" //ボタンを出す※readyイベントが発生する度にボタンが送信されるので注意 async function ButtonCreate(ChannelId, RoleId){ const channel = await client.channels.fetch(ChannelId) const Button = new ButtonBuilder() .setCustomId(`${BUTTON_ID_PREFIX}${RoleId}`) .setStyle(ButtonStyle.Primary) .setLabel("にゃーん") .setEmoji("🐈"); channel.send({ components: [ new ActionRowBuilder() .setComponents(Button) ] }) } //受信 client.on("interactionCreate", async interaction => { if (!interaction.isButton()) return if (!interaction.customId.startsWith(BUTTON_ID_PREFIX)) return const me = await interaction.guild.members.fetchMe() if (!me.permissions.has(PermissionsBitField.Flags.ManageRoles)){ return interaction.reply({ content: "botに[ロールの管理]の権限がありません。サーバーの管理者に問い合わせてください。", ephemeral: true }) } const roleId = String(interaction.customId.slice(BUTTON_ID_PREFIX.length)) const roles = await interaction.guild.roles.fetch() if (!roles.has(roleId)) { return interaction.reply({ content: "ロールが存在しません。サーバーの管理者に問い合わせてください。", ephemeral: true }) } const role = roles.get(roleId) const member = await interaction.member.fetch() if (member.roles.cache.has(roleId)) { try { await member.roles.remove(role) return interaction.reply({ content: `${role}を剥奪しました。`, ephemeral: true }) } catch (error) { console.error(error) return interaction.reply({ content: `${role}の剥奪に失敗しました。`, ephemeral: true }) } } try { await member.roles.add(role) return interaction.reply({ content: `${role}を付与しました。`, ephemeral: true }) } catch (error) { console.error(error) return interaction.reply({ content: `${role}の付与に失敗しました。`, ephemeral: true }) } }) client.on("ready", () => { console.log("準備完了!") //ChannelIdとRoleIdには任意の値を入れること。 //※ボタンが送信されたらこの部分は削除しても構いません。 ButtonCreate("ChannelId", "RoleId") })