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