ボタンを使ってみよう
セキュリティ上の問題がありましたが現在は修正済みです
仕様
ボタンを押すと役職が付与される
すでに役職を持っている場合はエラーにする。
customIdはapplication/x-www-form-urlencoded形式とした。
dは機能の種類を表す(ここではrp)
ridはロールのid
100文字以下に収まるならば好きなフォーマットを使って良い
https://gyazo.com/9e77e9fcac3d3f8386fd139a940cd551
ボタンを設置するためのコード
code:install.js
const Discord = require("discord.js");
const client = new Discord.Client({
intents: 0
});
client.token = process.env.DISCORD_TOKEN;
const GUILD_ID = process.argv2; const CHANNEL_ID = process.argv3; const ROLE_ID = process.argv4; 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を渡すとボタンなどをメッセージにつけることができる。
ボタンのスタイルは必ず指定しなくてはならない。
https://gyazo.com/20944be3a6b0470521e9d6101b23a577
上からPRIMARY、SUCCESS、SECONDARY、DANGER、LINKをsetStyleに指定することで使用できます。
最後はsetEmojiを使用した場合の例です。
ラベルと絵文字のどちらかは指定しなくてはならない。
customIdは1から100文字の文字列である必要がある。
LINKの場合はcustomIdの代わりにurlを指定しなければならない
URLSearchParams
https://google.com/search?q=xxxx のq=xxxxの部分のパースや組み立てを容易に行える。
二度目になるが特にこれを使用しなければならない理由はないので好きなフォーマットを使って良い。
本来はコマンドやウェブサイトなどから操作できて然るべきであるとは思うけどめんどくさかったtig.icon。
code:sh
node ./install.js <GUILD_ID> <CHANNEL_ID> <ROLE_ID>
TypeError: guild.channels.fetch is not a function
discord.jsのバージョンを確認してください
ボタンが押された際の処理を行うコード
code: index.js
const Discord = require("discord.js");
/**
*
* @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);
}
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
実際の応答を返す。
code: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")
})