From 111ae4331789887b74ee1bd0572f73eb8ea1074f Mon Sep 17 00:00:00 2001 From: minarin0179 Date: Sat, 5 Aug 2023 11:12:01 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=87=E3=82=A3=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E8=A6=8F=E7=B4=84=E3=81=AB=E6=BA=96=E6=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agenda.ts | 15 +- src/bot.ts | 16 +- src/commands/contextmenu/deleteAfter.ts | 22 +- src/commands/contextmenu/deleteBefore.ts | 22 +- src/commands/contextmenu/edit.ts | 15 +- src/commands/contextmenu/fix_archive.ts | 19 +- src/commands/contextmenu/resetNickname.ts | 17 +- src/commands/contextmenu/transferContext.ts | 41 ++-- src/commands/slashcommands/archive.ts | 170 ++++++++------- src/commands/slashcommands/cleanup.ts | 61 +++--- src/commands/slashcommands/copy.ts | 102 +++++---- src/commands/slashcommands/delete.ts | 88 ++++---- src/commands/slashcommands/dice.ts | 40 ++-- src/commands/slashcommands/gather.ts | 40 ++-- src/commands/slashcommands/open.ts | 44 ++-- src/commands/slashcommands/output.ts | 67 +++--- src/commands/slashcommands/ping.ts | 11 +- src/commands/slashcommands/remind.ts | 198 ++++++++--------- src/commands/slashcommands/rename.ts | 58 ++--- src/commands/slashcommands/role.ts | 201 +++++++++--------- src/commands/slashcommands/select.ts | 191 +++++++++-------- src/commands/slashcommands/server.ts | 22 +- src/commands/slashcommands/setup.ts | 188 +++++++--------- src/commands/slashcommands/sync.ts | 34 +-- src/commands/slashcommands/transfer.ts | 56 +++-- src/components/buttons/deleteRemind.ts | 21 +- src/components/buttons/diceroll.ts | 25 +-- src/components/buttons/open.ts | 32 +-- src/components/buttons/roleAdd.ts | 39 ++-- src/components/buttons/roleChange.ts | 43 ++-- src/components/buttons/roleGet.ts | 30 ++- src/components/buttons/roleRelease.ts | 30 ++- src/components/buttons/roleRemove.ts | 33 +-- src/components/buttons/select.ts | 34 +-- src/components/buttons/selectAgregate.ts | 17 +- src/components/buttons/transfer.ts | 62 +++--- src/components/buttons/unehemeral.ts | 19 +- src/components/modal/editModal.ts | 69 +++--- src/components/selectmenu/roleList.ts | 43 ++-- src/components/selectmenu/transferList.ts | 43 ++-- .../selectmenu/transferListImmed.ts | 47 ++-- src/events/interactionCreate.ts | 84 ++++---- src/events/ready.ts | 8 +- src/structures/Button.ts | 30 +-- src/structures/Client.ts | 83 ++++---- src/structures/Command.ts | 34 ++- src/structures/Component.ts | 30 +-- src/structures/ContextMenu.ts | 36 ++-- src/structures/Events.ts | 8 +- src/structures/Modal.ts | 30 +-- src/structures/SelectMenu.ts | 30 +-- src/structures/SlashCommand.ts | 38 ++-- src/utils/ArraySplit.ts | 2 +- src/utils/ButtonToRow.ts | 9 +- src/utils/DeleteMultiMessages.ts | 52 +++-- src/utils/FetchAllMessages.ts | 61 ++---- src/utils/Reply.ts | 30 ++- src/utils/SplitMessage.ts | 18 +- src/utils/isCategory.ts | 6 +- src/utils/isEditable.ts | 4 +- src/utils/transferMessage.ts | 127 ++++++----- 61 files changed, 1556 insertions(+), 1489 deletions(-) diff --git a/src/agenda.ts b/src/agenda.ts index 1d1f52a..a7e37d1 100644 --- a/src/agenda.ts +++ b/src/agenda.ts @@ -1,11 +1,10 @@ import Agenda from "agenda"; -import 'dotenv/config' -import { client } from './bot' +import "dotenv/config"; -export const agenda = new Agenda({ db: { address: process.env.mongodb ?? '' } }); +export const agenda = new Agenda({ db: { address: process.env.mongodb ?? "" } }); -agenda.on("ready", async ()=> { - agenda.start() - agenda.purge() - console.log(new Date(), 'agenda started'); -}) \ No newline at end of file +agenda.on("ready", async () => { + agenda.start(); + agenda.purge(); + console.log(new Date(), "agenda started"); +}); diff --git a/src/bot.ts b/src/bot.ts index b86d9db..528cea3 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,14 +1,10 @@ -import { GatewayIntentBits, Options } from "discord.js" -import { ExtendedClient } from "./structures/Client" +import { GatewayIntentBits } from "discord.js"; +import { ExtendedClient } from "./structures/Client"; export const client: ExtendedClient = new ExtendedClient({ - shards: 'auto', - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildVoiceStates - ], + shards: "auto", + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildVoiceStates], rest: { timeout: 60000 }, -}) +}); -client.start() \ No newline at end of file +client.start(); diff --git a/src/commands/contextmenu/deleteAfter.ts b/src/commands/contextmenu/deleteAfter.ts index 3b3b2cc..ed98710 100644 --- a/src/commands/contextmenu/deleteAfter.ts +++ b/src/commands/contextmenu/deleteAfter.ts @@ -6,21 +6,21 @@ import { fetchAllMessages } from "../../utils/FetchAllMessages"; export default new ContextMenu({ data: new ContextMenuCommandBuilder() - .setName('これ以降を削除') + .setName("これ以降を削除") .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(0) .setDMPermission(false), execute: async ({ interaction }) => { - if (!interaction.isMessageContextMenuCommand()) return - await interaction.deferReply({ ephemeral: true }) + if (!interaction.isMessageContextMenuCommand()) return; + await interaction.deferReply({ ephemeral: true }); - const message = interaction.targetMessage - const { channel } = message - const after: Snowflake | undefined = message.id + const message = interaction.targetMessage; + const { channel } = message; + const after: Snowflake | undefined = message.id; - const messages = await fetchAllMessages(channel, { after }) - await deleteMultiMessages(channel, messages) + const messages = await fetchAllMessages(channel, { after }); + await deleteMultiMessages(channel, messages); - await reply(interaction, "メッセージを削除しました") - } -}) \ No newline at end of file + await reply(interaction, "メッセージを削除しました"); + }, +}); diff --git a/src/commands/contextmenu/deleteBefore.ts b/src/commands/contextmenu/deleteBefore.ts index 03dfb98..cf6841a 100644 --- a/src/commands/contextmenu/deleteBefore.ts +++ b/src/commands/contextmenu/deleteBefore.ts @@ -6,21 +6,21 @@ import { fetchAllMessages } from "../../utils/FetchAllMessages"; export default new ContextMenu({ data: new ContextMenuCommandBuilder() - .setName('これ以前を削除') + .setName("これ以前を削除") .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(0) .setDMPermission(false), execute: async ({ interaction }) => { - if (!interaction.isMessageContextMenuCommand()) return - await interaction.deferReply({ ephemeral: true }) + if (!interaction.isMessageContextMenuCommand()) return; + await interaction.deferReply({ ephemeral: true }); - const message = interaction.targetMessage - const { channel } = message - const before: Snowflake | undefined = message.id + const message = interaction.targetMessage; + const { channel } = message; + const before: Snowflake | undefined = message.id; - const messages = await fetchAllMessages(channel, { before }) - await deleteMultiMessages(channel, messages) + const messages = await fetchAllMessages(channel, { before }); + await deleteMultiMessages(channel, messages); - await reply(interaction, "メッセージを削除しました") - } -}) \ No newline at end of file + await reply(interaction, "メッセージを削除しました"); + }, +}); diff --git a/src/commands/contextmenu/edit.ts b/src/commands/contextmenu/edit.ts index f249211..a5dc345 100644 --- a/src/commands/contextmenu/edit.ts +++ b/src/commands/contextmenu/edit.ts @@ -5,18 +5,17 @@ import { reply } from "../../utils/Reply"; export default new ContextMenu({ data: new ContextMenuCommandBuilder() - .setName('メッセージを編集') + .setName("メッセージを編集") .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(0) .setDMPermission(false), execute: async ({ interaction, args }) => { - if (!interaction.isMessageContextMenuCommand()) return + if (!interaction.isMessageContextMenuCommand()) return; - const message = interaction.targetMessage + const message = interaction.targetMessage; - if (!message.editable) return reply(interaction, 'このメッセージは編集できません') + if (!message.editable) return reply(interaction, "このメッセージは編集できません"); - - await interaction.showModal(editModal.build({ message })) - } -}) \ No newline at end of file + await interaction.showModal(editModal.build({ message })); + }, +}); diff --git a/src/commands/contextmenu/fix_archive.ts b/src/commands/contextmenu/fix_archive.ts index 925e8a1..f3a478e 100644 --- a/src/commands/contextmenu/fix_archive.ts +++ b/src/commands/contextmenu/fix_archive.ts @@ -1,29 +1,28 @@ import { ApplicationCommandType, ContextMenuCommandBuilder, Embed, EmbedBuilder } from "discord.js"; import { ContextMenu } from "../../structures/ContextMenu"; -import editModal from "../../components/modal/editModal"; import { reply } from "../../utils/Reply"; export default new ContextMenu({ data: new ContextMenuCommandBuilder() - .setName('/archiveを修正') + .setName("/archiveを修正") .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(0) .setDMPermission(false), execute: async ({ interaction, args }) => { - if (!interaction.isMessageContextMenuCommand()) return + if (!interaction.isMessageContextMenuCommand()) return; - const message = interaction.targetMessage + const message = interaction.targetMessage; - const embed_old: Embed = message.embeds[0] + const embed_old: Embed = message.embeds[0]; const embed: EmbedBuilder = new EmbedBuilder(embed_old.toJSON()); - if (!message.editable || !embed_old?.description) return reply(interaction, 'このメッセージは編集できません') + if (!message.editable || !embed_old?.description) return reply(interaction, "このメッセージは編集できません"); - embed.setDescription(embed_old?.description.replace(/\[\\?\# /g, "[_#_ ")) + embed.setDescription(embed_old?.description.replace(/\[\\?\# /g, "[_#_ ")); message.edit({ embeds: [embed] }); - reply(interaction, '修正が完了しました'); - } -}) \ No newline at end of file + reply(interaction, "修正が完了しました"); + }, +}); diff --git a/src/commands/contextmenu/resetNickname.ts b/src/commands/contextmenu/resetNickname.ts index 6ec6bda..76e43c6 100644 --- a/src/commands/contextmenu/resetNickname.ts +++ b/src/commands/contextmenu/resetNickname.ts @@ -4,17 +4,18 @@ import { reply } from "../../utils/Reply"; export default new ContextMenu({ data: new ContextMenuCommandBuilder() - .setName('ニックネームをリセット') + .setName("ニックネームをリセット") .setType(ApplicationCommandType.User) .setDefaultMemberPermissions(PermissionFlagsBits.ChangeNickname) .setDMPermission(false), execute: async ({ interaction }) => { - if (!interaction.isUserContextMenuCommand()) return + if (!interaction.isUserContextMenuCommand()) return; - const member = interaction.targetMember as GuildMember + const member = interaction.targetMember as GuildMember; - await member.setNickname(null) - .then(async () => await reply(interaction, 'ニックネームをリセットしました')) - .catch(async () => await reply(interaction, 'ニックネームを変更できませんでした')) - } -}) \ No newline at end of file + await member + .setNickname(null) + .then(async () => await reply(interaction, "ニックネームをリセットしました")) + .catch(async () => await reply(interaction, "ニックネームを変更できませんでした")); + }, +}); diff --git a/src/commands/contextmenu/transferContext.ts b/src/commands/contextmenu/transferContext.ts index 64ce64d..52c2097 100644 --- a/src/commands/contextmenu/transferContext.ts +++ b/src/commands/contextmenu/transferContext.ts @@ -1,28 +1,41 @@ -import { ApplicationCommandType, CategoryChannel, ChannelType, ContextMenuCommandBuilder, discordSort, GuildTextBasedChannel, NewsChannel, TextChannel } from "discord.js"; +import { + ApplicationCommandType, + CategoryChannel, + ChannelType, + ContextMenuCommandBuilder, + discordSort, + GuildTextBasedChannel, + NewsChannel, + TextChannel, +} from "discord.js"; import { ContextMenu } from "../../structures/ContextMenu"; import transferListImmed from "../../components/selectmenu/transferListImmed"; import { reply } from "../../utils/Reply"; export default new ContextMenu({ data: new ContextMenuCommandBuilder() - .setName('メッセージを転送') + .setName("メッセージを転送") .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(0) .setDMPermission(false), execute: async ({ interaction }) => { - if (!interaction.isMessageContextMenuCommand()) return + if (!interaction.isMessageContextMenuCommand()) return; - const channel = interaction.channel as GuildTextBasedChannel - const category = channel?.parent?.parent ?? channel?.parent as CategoryChannel - const channels = category?.children.cache ?? interaction.guild?.channels.cache - .filter((channel): channel is TextChannel | NewsChannel => - !channel.parent && (channel.type == ChannelType.GuildText || channel.type == ChannelType.GuildNews)) + const channel = interaction.channel as GuildTextBasedChannel; + const category = channel?.parent?.parent ?? (channel?.parent as CategoryChannel); + const channels = + category?.children.cache ?? + interaction.guild?.channels.cache.filter( + (channel): channel is TextChannel | NewsChannel => + !channel.parent && + (channel.type == ChannelType.GuildText || channel.type == ChannelType.GuildAnnouncement) + ); - if (!channels) return + if (!channels) return; await reply(interaction, { - content: '転送先のチャンネルを選択してください', - components: transferListImmed.build([...discordSort(channels).values()], interaction.targetMessage) - }) - } -}) \ No newline at end of file + content: "転送先のチャンネルを選択してください", + components: transferListImmed.build([...discordSort(channels).values()], interaction.targetMessage), + }); + }, +}); diff --git a/src/commands/slashcommands/archive.ts b/src/commands/slashcommands/archive.ts index 35fac71..ef7737d 100644 --- a/src/commands/slashcommands/archive.ts +++ b/src/commands/slashcommands/archive.ts @@ -1,80 +1,102 @@ -import { CategoryChannel, ChannelType, Collection, discordSort, EmbedBuilder, GuildTextBasedChannel, Message, SlashCommandBuilder, TextChannel } from "discord.js"; +import { + CategoryChannel, + ChannelType, + Collection, + discordSort, + EmbedBuilder, + GuildTextBasedChannel, + Message, + SlashCommandBuilder, + TextChannel, +} from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; import { fetchAllMessages } from "../../utils/FetchAllMessages"; import { reply } from "../../utils/Reply"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('archive') - .setDescription('チャンネルをスレッドにして保存します') + .setName("archive") + .setDescription("チャンネルをスレッドにして保存します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .setName('保存するカテゴリ') - .addChannelTypes(ChannelType.GuildText, ChannelType.GuildCategory) - .setDescription('保存するカテゴリ') - .setRequired(true) - ).addChannelOption(option => option - .addChannelTypes(ChannelType.GuildText) - .setName('保存先') - .setDescription('保存先のチャンネル') - .setRequired(false) + .addChannelOption(option => + option + .setName("保存するカテゴリ") + .addChannelTypes(ChannelType.GuildText, ChannelType.GuildCategory) + .setDescription("保存するカテゴリ") + .setRequired(true) + ) + .addChannelOption(option => + option + .addChannelTypes(ChannelType.GuildText) + .setName("保存先") + .setDescription("保存先のチャンネル") + .setRequired(false) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); - await interaction.deferReply({ ephemeral: true }) + const targetCategory = args.getChannel("保存するカテゴリ", true) as CategoryChannel | TextChannel; - const targetCategory = args.getChannel('保存するカテゴリ', true) as CategoryChannel | TextChannel; - - const logChannel = (args.getChannel('保存先') ?? await interaction.guild?.channels.create({ - name: `ログ ${targetCategory.name}`, - type: ChannelType.GuildText, - permissionOverwrites: targetCategory.permissionOverwrites.cache - })) as TextChannel + const logChannel = (args.getChannel("保存先") ?? + (await interaction.guild?.channels.create({ + name: `ログ ${targetCategory.name}`, + type: ChannelType.GuildText, + permissionOverwrites: targetCategory.permissionOverwrites.cache, + }))) as TextChannel; const children = (() => { if (targetCategory instanceof CategoryChannel) { - return discordSort(targetCategory.children.cache.filter((ch): ch is TextChannel => ch.type === ChannelType.GuildText)) + return discordSort( + targetCategory.children.cache.filter((ch): ch is TextChannel => ch.type === ChannelType.GuildText) + ); } - return new Collection([[targetCategory.id, targetCategory]]) + return new Collection([[targetCategory.id, targetCategory]]); })(); if (children.size == 0) { - return reply(interaction, { content: '保存するチャンネルがありません', ephemeral: true }) + return reply(interaction, { content: "保存するチャンネルがありません", ephemeral: true }); } - const descriptions = await Promise.all(children.map(async child => { - - let description = '' + const descriptions = await Promise.all( + children.map(async child => { + let description = ""; - description += await RunArchive(child, logChannel) + description += await RunArchive(child, logChannel); - const threads = (await child.threads.fetchActive()).threads.concat((await child.threads.fetchArchived()).threads) - if (threads.size > 0) { - description += '\n' + (await Promise.all(threads.map(async thread => await RunArchive(thread, logChannel)))).join('\n') - } - return description - })) + const threads = (await child.threads.fetchActive()).threads.concat( + (await child.threads.fetchArchived()).threads + ); + if (threads.size > 0) { + description += `\n${( + await Promise.all(threads.map(async thread => await RunArchive(thread, logChannel))) + ).join("\n")}`; + } + return description; + }) + ); await logChannel.send({ - embeds: [new EmbedBuilder() - .setTitle(targetCategory.name) - .setColor([47, 49, 54]) - .setDescription(descriptions.join('\n')) - ] - }) - - await reply(interaction, `「${targetCategory.name}」の保存が完了しました`) - } -}) + embeds: [ + new EmbedBuilder() + .setTitle(targetCategory.name) + .setColor([47, 49, 54]) + .setDescription(descriptions.join("\n")), + ], + }); + + await reply(interaction, `「${targetCategory.name}」の保存が完了しました`); + }, +}); const RunArchive = async (source: GuildTextBasedChannel, destination: TextChannel): Promise => { - const messages = [...(await fetchAllMessages(source)).reverse().values()] + const messages = [...(await fetchAllMessages(source)).reverse().values()]; const slicedMessages: Message[][] = []; - const destinationThread = await destination.threads.create({ name: source.name }) + const destinationThread = await destination.threads.create({ name: source.name }); - const embedSize = ((message: Message) => message.content.length + (message.member?.nickname || message.author.username).length + 16) + const embedSize = (message: Message) => + message.content.length + (message.member?.nickname || message.author.username).length + 16; let tail = 0; let length = 0; messages.map((message, index) => { @@ -83,45 +105,51 @@ const RunArchive = async (source: GuildTextBasedChannel, destination: TextChanne tail = index + 1; length = 0; } - length += embedSize(message) - }) + length += embedSize(message); + }); if (tail < messages.length) { slicedMessages.push(messages.slice(tail)); } for await (const messages of slicedMessages) { - - await destinationThread.sendTyping() - - const embeds = messages.filter(message => message.content != '').map(message => { - const date = new Date(message.createdAt) - const timeStamp = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${('0' + date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}` - - return new EmbedBuilder() - .setAuthor({ - name: message.member?.nickname || message.author.username, - iconURL: message.author.avatarURL() ?? undefined - }) - .setColor([47, 49, 54]) - .setDescription(message.content) - .setFooter({ text: timeStamp }) - }) + await destinationThread.sendTyping(); + + const embeds = messages + .filter(message => message.content != "") + .map(message => { + const date = new Date(message.createdAt); + const timeStamp = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${( + "0" + date.getHours() + ).slice(-2)}:${("0" + date.getMinutes()).slice(-2)}`; + + return new EmbedBuilder() + .setAuthor({ + name: message.member?.nickname || message.author.username, + iconURL: message.author.avatarURL() ?? undefined, + }) + .setColor([47, 49, 54]) + .setDescription(message.content) + .setFooter({ text: timeStamp }); + }); if (embeds.length > 0) { await destinationThread.send({ embeds: embeds }); } - const files = messages.slice(-1)[0].attachments.filter(attachment => attachment.size <= 8388608).map(attachment => attachment.url); + const files = messages + .slice(-1)[0] + .attachments.filter(attachment => attachment.size <= 8388608) + .map(attachment => attachment.url); //ファイルを一括で送るとメモリを食う for await (const file of files) { - await destinationThread.sendTyping() + await destinationThread.sendTyping(); await destinationThread.send({ files: [file] }); } } - (await destinationThread.fetchStarterMessage())?.delete().catch(() => { }) - await destinationThread.setArchived(true) + (await destinationThread.fetchStarterMessage())?.delete().catch(() => {}); + await destinationThread.setArchived(true); - return (source.isThread() ? '┗' : '') + `[_#_ ${destinationThread.name}](${destinationThread.url})` -} \ No newline at end of file + return (source.isThread() ? "┗" : "") + `[_#_ ${destinationThread.name}](${destinationThread.url})`; +}; diff --git a/src/commands/slashcommands/cleanup.ts b/src/commands/slashcommands/cleanup.ts index 2e9c1e1..e2b3ef3 100644 --- a/src/commands/slashcommands/cleanup.ts +++ b/src/commands/slashcommands/cleanup.ts @@ -1,47 +1,56 @@ -import { ChannelType, GuildChannel, GuildTextBasedChannel, NewsChannel, SlashCommandBuilder, TextChannel, VoiceChannel } from "discord.js"; +import { + ChannelType, + GuildChannel, + GuildTextBasedChannel, + NewsChannel, + SlashCommandBuilder, + TextChannel, + VoiceChannel, +} from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; -import { arraySplit } from "../../utils/ArraySplit"; import { deleteMultiMessages } from "../../utils/DeleteMultiMessages"; import { fetchAllMessages } from "../../utils/FetchAllMessages"; import { isCategory } from "../../utils/isCategory"; import { reply } from "../../utils/Reply"; - export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('cleanup') - .setDescription('メッセージをすべて削除します') + .setName("cleanup") + .setDescription("メッセージをすべて削除します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .setName('対象') - .setDescription('指定しなかった場合はコマンドが送信されたチャンネル') - .setRequired(false) - .addChannelTypes(ChannelType.GuildText, ChannelType.GuildCategory) + .addChannelOption(option => + option + .setName("対象") + .setDescription("指定しなかった場合はコマンドが送信されたチャンネル") + .setRequired(false) + .addChannelTypes(ChannelType.GuildText, ChannelType.GuildCategory) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); - await interaction.deferReply({ ephemeral: true }) - - const targetChannel = (args.getChannel('対象') ?? interaction.channel) as GuildChannel - if (!targetChannel) return + const targetChannel = (args.getChannel("対象") ?? interaction.channel) as GuildChannel; + if (!targetChannel) return; if (targetChannel.isTextBased()) { - await deleteAllMessages(targetChannel) + await deleteAllMessages(targetChannel); } else if (isCategory(targetChannel)) { - const channels = targetChannel.children.cache - .filter((channel): channel is NewsChannel | TextChannel | VoiceChannel => channel.isTextBased()) - await Promise.all(channels.map(async channel => { - await deleteAllMessages(channel) - })) + const channels = targetChannel.children.cache.filter( + (channel): channel is NewsChannel | TextChannel | VoiceChannel => channel.isTextBased() + ); + await Promise.all( + channels.map(async channel => { + await deleteAllMessages(channel); + }) + ); } - await reply(interaction, `「${targetChannel.name}」内のメッセージを削除しました`) - } -}) + await reply(interaction, `「${targetChannel.name}」内のメッセージを削除しました`); + }, +}); const deleteAllMessages = async (channel: GuildTextBasedChannel) => { - const messages = await fetchAllMessages(channel) - return await deleteMultiMessages(channel, messages) -} + const messages = await fetchAllMessages(channel); + return await deleteMultiMessages(channel, messages); +}; diff --git a/src/commands/slashcommands/copy.ts b/src/commands/slashcommands/copy.ts index bf904ea..bff1a85 100644 --- a/src/commands/slashcommands/copy.ts +++ b/src/commands/slashcommands/copy.ts @@ -6,43 +6,44 @@ import { transferAllMessages } from "../../utils/transferMessage"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('copy') - .setDescription('チャンネルを複製します(メッセージを含む)') + .setName("copy") + .setDescription("チャンネルを複製します(メッセージを含む)") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .setName('対象') - .setDescription('コピーするチャンネル/カテゴリー') - .addChannelTypes(ChannelType.GuildCategory, ChannelType.GuildText) - .setRequired(false) + .addChannelOption(option => + option + .setName("対象") + .setDescription("コピーするチャンネル/カテゴリー") + .addChannelTypes(ChannelType.GuildCategory, ChannelType.GuildText) + .setRequired(false) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); - await interaction.deferReply({ ephemeral: true }) - - const originalChannel = (args.getChannel('対象') ?? interaction.channel) as GuildChannel - const newChannels = await copyChannel(originalChannel) + const originalChannel = (args.getChannel("対象") ?? interaction.channel) as GuildChannel; + const newChannels = await copyChannel(originalChannel); if (newChannels.length === 1) { - const [from, to] = newChannels[0] + const [from, to] = newChannels[0]; if (from.isTextBased() && to.isTextBased()) { - await transferAllMessages(from, to, { allowedMentions: { parse: [] } }) + await transferAllMessages(from, to, { allowedMentions: { parse: [] } }); } } else { - const updates = Object.fromEntries(newChannels.map(([from, to]) => [from.id, to])) - await Promise.all(newChannels.map(async newChannel => { - const [from, to] = newChannel - if (from.isTextBased() && to.isTextBased()) { - await transferAllMessages(from, to, { allowedMentions: { parse: [] }, updates }) - } - })) + const updates = Object.fromEntries(newChannels.map(([from, to]) => [from.id, to])); + await Promise.all( + newChannels.map(async newChannel => { + const [from, to] = newChannel; + if (from.isTextBased() && to.isTextBased()) { + await transferAllMessages(from, to, { allowedMentions: { parse: [] }, updates }); + } + }) + ); } - - await reply(interaction, `「${originalChannel.name}」のコピーが完了しました`) - } -}) + await reply(interaction, `「${originalChannel.name}」のコピーが完了しました`); + }, +}); /** * originalChannel:GuildChannel -> [[originalChannel,newChannel]] @@ -51,28 +52,39 @@ export default new SlashCommand({ const copyChannel = async (originalChannel: GuildChannel, option?: any): Promise => { if (originalChannel.isVoiceBased()) { - return [[originalChannel, await originalChannel.clone({ - name: `(copy) ${originalChannel.name}`, - ...option - })]] + return [ + [ + originalChannel, + await originalChannel.clone({ + name: `(copy) ${originalChannel.name}`, + ...option, + }), + ], + ]; } else if (originalChannel.isTextBased()) { - return [[originalChannel, await originalChannel.clone({ - name: `(copy) ${originalChannel.name}`, - // @ts-ignore voicebasedでVoiceChannelを弾いているためtopicプロパティは存在する - topic: originalChannel.topic || '', - nsfw: originalChannel.nsfw, - rateLimitPerUser: originalChannel.rateLimitPerUser || 0, - ...option - })]] - } - else if (isCategory(originalChannel)) { - const newCategory = await originalChannel.clone({ + return [ + [ + originalChannel, + await originalChannel.clone({ + name: `(copy) ${originalChannel.name}`, + // @ts-ignore voicebasedでVoiceChannelを弾いているためtopicプロパティは存在する + topic: originalChannel.topic || "", + nsfw: originalChannel.nsfw, + rateLimitPerUser: originalChannel.rateLimitPerUser || 0, + ...option, + }), + ], + ]; + } else if (isCategory(originalChannel)) { + const newCategory = (await originalChannel.clone({ name: `(copy) ${originalChannel.name}`, - }) as CategoryChannel + })) as CategoryChannel; - return await Promise.all(originalChannel.children.cache.map(async c => - (await copyChannel(c, { name: c.name, parent: newCategory }))[0] - )) + return await Promise.all( + originalChannel.children.cache.map( + async c => (await copyChannel(c, { name: c.name, parent: newCategory }))[0] + ) + ); } - return [] -} \ No newline at end of file + return []; +}; diff --git a/src/commands/slashcommands/delete.ts b/src/commands/slashcommands/delete.ts index 76d772b..544ee9a 100644 --- a/src/commands/slashcommands/delete.ts +++ b/src/commands/slashcommands/delete.ts @@ -4,53 +4,59 @@ import { reply } from "../../utils/Reply"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('delete') - .setDescription('カテゴリを削除します(カテゴリに含まれるチャンネルも削除されます)') + .setName("delete") + .setDescription("カテゴリを削除します(カテゴリに含まれるチャンネルも削除されます)") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .addChannelTypes(ChannelType.GuildCategory) - .setName('削除するカテゴリ') - .setDescription('削除するカテゴリ') - .setRequired(true) + .addChannelOption(option => + option + .addChannelTypes(ChannelType.GuildCategory) + .setName("削除するカテゴリ") + .setDescription("削除するカテゴリ") + .setRequired(true) ) - .addStringOption(option => option - .setName('ロールの削除') - .setDescription('カテゴリーに付与されたロールを一緒に削除しますか?(デフォルトはいいえ)') - .setRequired(false) - .addChoices( - { name: 'はい', value: 'true' }, - { name: 'いいえ', value: 'false' } - ) + .addStringOption(option => + option + .setName("ロールの削除") + .setDescription("カテゴリーに付与されたロールを一緒に削除しますか?(デフォルトはいいえ)") + .setRequired(false) + .addChoices({ name: "はい", value: "true" }, { name: "いいえ", value: "false" }) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { - - await interaction.deferReply({ ephemeral: true }) - - const category = args.getChannel('削除するカテゴリ', true) as CategoryChannel - const deleteRoleIds = new Set() - - await Promise.all(category.children.cache.map(async ch => { - ch.permissionOverwrites.cache - .filter(perm => perm.type == OverwriteType.Role) - .map(perm => deleteRoleIds.add(perm.id)) - - await ch.delete() - })) - - await category.delete() - - if (args.getString('ロールの削除') == 'true') { - await Promise.all([...deleteRoleIds].map(async id => { - const role = await interaction.guild?.roles.fetch(id) - if (role == interaction.guild?.roles.everyone) return - else if (role?.editable) await role.delete() - else await reply(interaction, `「${role}」はマダミナリンクより上位の役職のため削除できませんでした`).catch(() => { }) - })) + await interaction.deferReply({ ephemeral: true }); + + const category = args.getChannel("削除するカテゴリ", true) as CategoryChannel; + const deleteRoleIds = new Set(); + + await Promise.all( + category.children.cache.map(async ch => { + ch.permissionOverwrites.cache + .filter(perm => perm.type == OverwriteType.Role) + .map(perm => deleteRoleIds.add(perm.id)); + + await ch.delete(); + }) + ); + + await category.delete(); + + if (args.getString("ロールの削除") == "true") { + await Promise.all( + [...deleteRoleIds].map(async id => { + const role = await interaction.guild?.roles.fetch(id); + if (role == interaction.guild?.roles.everyone) return; + else if (role?.editable) await role.delete(); + else + await reply( + interaction, + `「${role}」はマダミナリンクより上位の役職のため削除できませんでした` + ).catch(() => {}); + }) + ); } //コマンドを入力したチャンネルが削除されている場合がある - await reply(interaction, `「${category.name}」の削除が完了しました`).catch(() => { }) - } -}) \ No newline at end of file + await reply(interaction, `「${category.name}」の削除が完了しました`).catch(() => {}); + }, +}); diff --git a/src/commands/slashcommands/dice.ts b/src/commands/slashcommands/dice.ts index 76a4036..5826ef1 100644 --- a/src/commands/slashcommands/dice.ts +++ b/src/commands/slashcommands/dice.ts @@ -6,37 +6,33 @@ import { reply } from "../../utils/Reply"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('dice') - .setDescription('ダイスを振るボタン作成します(xdy)') + .setName("dice") + .setDescription("ダイスを振るボタン作成します(xdy)") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addIntegerOption(option => option - .setName('ダイスの数') - .setDescription('ダイスの数') - .setRequired(true) - .setMinValue(1) - .setMaxValue(100) + .addIntegerOption(option => + option.setName("ダイスの数").setDescription("ダイスの数").setRequired(true).setMinValue(1).setMaxValue(100) ) - .addIntegerOption(option => option - .setName('ダイスの面数') - .setDescription('ダイスの面数') - .setRequired(true) - .setMinValue(2) - .setMaxValue(10000) + .addIntegerOption(option => + option + .setName("ダイスの面数") + .setDescription("ダイスの面数") + .setRequired(true) + .setMinValue(2) + .setMaxValue(10000) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { - await interaction.channel?.send({ content: `ボタンをクリックしてダイスロール🎲`, components: buttonToRow( dicebutton.build({ - x: args.getInteger('ダイスの数', true), - y: args.getInteger('ダイスの面数', true) + x: args.getInteger("ダイスの数", true), + y: args.getInteger("ダイスの面数", true), }) - ) - }) + ), + }); - await reply(interaction, 'ダイスを作成しました') - } -}) \ No newline at end of file + await reply(interaction, "ダイスを作成しました"); + }, +}); diff --git a/src/commands/slashcommands/gather.ts b/src/commands/slashcommands/gather.ts index be530fb..e77eec8 100644 --- a/src/commands/slashcommands/gather.ts +++ b/src/commands/slashcommands/gather.ts @@ -3,32 +3,30 @@ import { SlashCommand } from "../../structures/SlashCommand"; import { reply } from "../../utils/Reply"; - export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('gather') - .setDescription('メンバーを自分がいるVCに移動させます') + .setName("gather") + .setDescription("メンバーを自分がいるVCに移動させます") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addRoleOption(option => option - .setName('対象') - .setDescription('指定しなかった場合は@everyone') - .setRequired(false) + .addRoleOption(option => + option.setName("対象").setDescription("指定しなかった場合は@everyone").setRequired(false) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); + const executor = interaction.member; + if (!(executor instanceof GuildMember)) return; + const voiceChannel = await (await executor.fetch()).voice.channel?.fetch(); + if (!voiceChannel) return await reply(interaction, `あなたはVCに参加していません`); + const targetRole = (args.getRole("対象") ?? interaction.guild?.roles.everyone) as Role; + await Promise.all( + targetRole.members.map(async member => { + if (!(member instanceof GuildMember) || !member.voice.channel) return; + await member.voice.setChannel(voiceChannel); + }) + ); - await interaction.deferReply({ ephemeral: true }) - const executor = interaction.member - if (!(executor instanceof GuildMember)) return - const voiceChannel = await (await executor.fetch()).voice.channel?.fetch() - if (!voiceChannel) return await reply(interaction, `あなたはVCに参加していません`) - const targetRole = (args.getRole('対象') ?? interaction.guild?.roles.everyone) as Role - await Promise.all(targetRole.members.map(async member => { - if (!(member instanceof GuildMember) || !member.voice.channel) return - await member.voice.setChannel(voiceChannel) - })) - - await reply(interaction, `「${targetRole}」のメンバーを「${voiceChannel}」に移動させました`) - } -}) \ No newline at end of file + await reply(interaction, `「${targetRole}」のメンバーを「${voiceChannel}」に移動させました`); + }, +}); diff --git a/src/commands/slashcommands/open.ts b/src/commands/slashcommands/open.ts index b2d0d4d..1841dd2 100644 --- a/src/commands/slashcommands/open.ts +++ b/src/commands/slashcommands/open.ts @@ -6,46 +6,40 @@ import { buttonToRow } from "../../utils/ButtonToRow"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('open') - .setDescription('チャンネルを特定のロールに対して公開します') + .setName("open") + .setDescription("チャンネルを特定のロールに対して公開します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addMentionableOption(option => option - .setName('公開相手') - .setDescription('誰に対して公開しますか?') - .setRequired(true) + .addMentionableOption(option => + option.setName("公開相手").setDescription("誰に対して公開しますか?").setRequired(true) ) - .addChannelOption(option => option - .setName('チャンネル') - .setDescription('どのチャンネルを公開しますか?') - .setRequired(false) + .addChannelOption(option => + option.setName("チャンネル").setDescription("どのチャンネルを公開しますか?").setRequired(false) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + const mentionable = args.getMentionable("公開相手"); + const channel = args.getChannel("チャンネル") ?? interaction.channel; - const mentionable = args.getMentionable('公開相手') - const channel = args.getChannel('チャンネル') ?? interaction.channel + if (!mentionable) return reply(interaction, "ロール/メンバーが存在しません"); - if (!mentionable) return reply(interaction, 'ロール/メンバーが存在しません') + if (!channel) return reply(interaction, "チャンネルが見つかりませんでした"); - if (!channel) return reply(interaction, 'チャンネルが見つかりませんでした') + if (!("permissionOverwrites" in channel)) return reply(interaction, "権限を編集できません"); - if (!('permissionOverwrites' in channel)) return reply(interaction, '権限を編集できません') + if (!("id" in mentionable)) return; //getMentionableのバグが直ったら削除 - if (!('id' in mentionable)) return //getMentionableのバグが直ったら削除 + await channel?.permissionOverwrites.edit(mentionable.id, { ViewChannel: false }); - await channel?.permissionOverwrites.edit(mentionable.id, { ViewChannel: false }) + await interaction.channel?.send(openMessage(channel, mentionable)); - await interaction.channel?.send(openMessage(channel, mentionable)) - - await reply(interaction, 'ボタンを作成しました') - - } -}) + await reply(interaction, "ボタンを作成しました"); + }, +}); export const openMessage = (channel: GuildChannel, mentionable: GuildMember | Role | APIRole | User) => { return { content: `ボタンを押すと${channel}を${mentionable}に公開します`, components: buttonToRow(openButton.build({ channel, mentionable })), - } -} \ No newline at end of file + }; +}; diff --git a/src/commands/slashcommands/output.ts b/src/commands/slashcommands/output.ts index 96d01c4..4671eff 100644 --- a/src/commands/slashcommands/output.ts +++ b/src/commands/slashcommands/output.ts @@ -1,51 +1,50 @@ -import { ChannelType, GuildChannel, SlashCommandBuilder, CategoryChannel, TextChannel, Attachment } from "discord.js"; +import { ChannelType, SlashCommandBuilder, TextChannel, Attachment } from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; -import { isCategory } from "../../utils/isCategory"; import { reply } from "../../utils/Reply"; -import { transferAllMessages } from "../../utils/transferMessage"; -import fs from 'fs' +import fs from "fs"; export default new SlashCommand({ dev: true, data: new SlashCommandBuilder() - .setName('outpu') - .setDescription('チャンネルをファイルとして出力します') + .setName("outpu") + .setDescription("チャンネルをファイルとして出力します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .setName('対象') - .setDescription('出力するチャンネル/カテゴリー') - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ).addAttachmentOption(option => option - .setName('入力') - .setDescription('作成するチャンネル') + .addChannelOption(option => + option + .setName("対象") + .setDescription("出力するチャンネル/カテゴリー") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + .addAttachmentOption(option => + option.setName("入力").setDescription("作成するチャンネル") ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); - await interaction.deferReply({ ephemeral: true }) + const originalChannel = args.getChannel("対象", true) as TextChannel; - const originalChannel = args.getChannel('対象', true) as TextChannel + const messages = JSON.stringify( + (await originalChannel.messages.fetch()).map(message => { + return { + content: message.content, + files: message.attachments.map((file: Attachment) => file.url), + components: message.components, + embeds: message.embeds, + }; + }), + null, + " " + ); - const messages = JSON.stringify((await originalChannel.messages.fetch()).map(message => { - return { - content: message.content, - files: message.attachments.map((file: Attachment) => file.url), - components: message.components, - embeds: message.embeds, - } - }), null, " ") - - - - console.log(messages) - fs.writeFileSync('output.json', messages) + fs.writeFileSync("output.json", messages); await reply(interaction, { - files: ['./output.json'] - }) + files: ["./output.json"], + }); - await reply(interaction, `「${originalChannel.name}」のコピーが完了しました`) - } -}) + await reply(interaction, `「${originalChannel.name}」のコピーが完了しました`); + }, +}); diff --git a/src/commands/slashcommands/ping.ts b/src/commands/slashcommands/ping.ts index b6d5dd0..d1f48f4 100644 --- a/src/commands/slashcommands/ping.ts +++ b/src/commands/slashcommands/ping.ts @@ -2,13 +2,12 @@ import { SlashCommandBuilder } from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; import { reply } from "../../utils/Reply"; - export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('ping') - .setDescription('マダミナリンクの稼働状況を確認します') + .setName("ping") + .setDescription("マダミナリンクの稼働状況を確認します") .setDMPermission(true), execute: async ({ client, interaction }) => { - await reply(interaction, `マダミナリンクは現在稼働中です (${client.ws.ping}ms)`) - } -}) \ No newline at end of file + await reply(interaction, `マダミナリンクは現在稼働中です (${client.ws.ping}ms)`); + }, +}); diff --git a/src/commands/slashcommands/remind.ts b/src/commands/slashcommands/remind.ts index 42a4c3e..f9c7794 100644 --- a/src/commands/slashcommands/remind.ts +++ b/src/commands/slashcommands/remind.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelType, EmbedBuilder, SlashCommandBuilder } from "discord.js"; +import { ChannelType, EmbedBuilder, SlashCommandBuilder } from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; import { reply } from "../../utils/Reply"; import { agenda } from "../../agenda"; @@ -8,133 +8,139 @@ import { client } from "../../bot"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('remind') - .setDescription('リマインダーの登録や削除を行います') + .setName("remind") + .setDescription("リマインダーの登録や削除を行います") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addSubcommand(subcommand => subcommand - .setName('set') - .setDescription('新たなリマインダーを登録します') - .addIntegerOption(option => option - .setName('月') - .setDescription('月を入力してください') - .setMinValue(1) - .setMaxValue(12) - .setRequired(true) - ).addIntegerOption(option => option - .setName('日') - .setDescription('日付を入力してください') - .setMinValue(1) - .setMaxValue(31) - .setRequired(true) - ).addIntegerOption(option => option - .setName('時') - .setDescription('時を入力してください') - .setMinValue(0) - .setMaxValue(23) - .setRequired(true) - ).addIntegerOption(option => option - .setName('分') - .setDescription('分を入力してください') - .setMinValue(0) - .setMaxValue(59) - .setRequired(true) - ).addStringOption(option => option - .setName('本文') - .setDescription('送信するメッセージを入力して下さい') - .setRequired(true) - ).addChannelOption(option => option - .setName('送信先') - .setDescription('送信先のチャンネルを選択してください(指定しなかった場合は入力したチャンネルに送信されます)') - .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) - .setRequired(false) - ) - ).addSubcommand(subcommand => subcommand - .setName('list') - .setDescription('登録されているリマインダーを確認します') + .addSubcommand(subcommand => + subcommand + .setName("set") + .setDescription("新たなリマインダーを登録します") + .addIntegerOption(option => + option + .setName("月") + .setDescription("月を入力してください") + .setMinValue(1) + .setMaxValue(12) + .setRequired(true) + ) + .addIntegerOption(option => + option + .setName("日") + .setDescription("日付を入力してください") + .setMinValue(1) + .setMaxValue(31) + .setRequired(true) + ) + .addIntegerOption(option => + option + .setName("時") + .setDescription("時を入力してください") + .setMinValue(0) + .setMaxValue(23) + .setRequired(true) + ) + .addIntegerOption(option => + option + .setName("分") + .setDescription("分を入力してください") + .setMinValue(0) + .setMaxValue(59) + .setRequired(true) + ) + .addStringOption(option => + option.setName("本文").setDescription("送信するメッセージを入力して下さい").setRequired(true) + ) + .addChannelOption(option => + option + .setName("送信先") + .setDescription( + "送信先のチャンネルを選択してください(指定しなかった場合は入力したチャンネルに送信されます)" + ) + .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) + .setRequired(false) + ) + ) + .addSubcommand(subcommand => + subcommand.setName("list").setDescription("登録されているリマインダーを確認します") ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { - - if (args.getSubcommand() === 'set') { - + if (args.getSubcommand() === "set") { let year = new Date().getFullYear(); - const month = args.getInteger('月', true) - const day = args.getInteger('日', true) - const hour = args.getInteger('時', true) - const minute = args.getInteger('分', true) - const content = args.getString('本文', true) - const destination = args.getChannel('送信先') ?? interaction.channel + const month = args.getInteger("月", true); + const day = args.getInteger("日", true); + const hour = args.getInteger("時", true); + const minute = args.getInteger("分", true); + const content = args.getString("本文", true); + const destination = args.getChannel("送信先") ?? interaction.channel; process.env.TZ = "Asia/Tokyo"; - const date = new Date(year, month - 1, day, hour, minute) - const now = new Date() - const lim = new Date() + const date = new Date(year, month - 1, day, hour, minute); + const now = new Date(); + const lim = new Date(); - lim.setMonth(lim.getMonth() + 3) + lim.setMonth(lim.getMonth() + 3); - if (!destination) return reply(interaction, '送信先が見つかりませんでした') + if (!destination) return reply(interaction, "送信先が見つかりませんでした"); if (date.getTime() < now.getTime()) { - date.setFullYear(++year) //過去の日付だったら1年後 + date.setFullYear(++year); //過去の日付だったら1年後 + } else if (date.getTime() > lim.getTime()) { + return reply( + interaction, + "日時の入力が正しくありません\n過去の日付や3ヶ月以上先の日付は設定できません" + ); } - else if (date.getTime() > lim.getTime()) { - return reply(interaction, '日時の入力が正しくありません\n過去の日付や3ヶ月以上先の日付は設定できません') - } + const channelId = destination.id; + const authorId = interaction.user.id; - const channelId = destination.id - const authorId = interaction.user.id - - const job = await agenda.schedule(date, 'send remind', { channelId, authorId, content }) + const job = await agenda.schedule(date, "send remind", { channelId, authorId, content }); await reply(interaction, { - content: 'リマインダーを設定しました', + content: "リマインダーを設定しました", embeds: [buildEmbed(content, date, channelId)], - components: buttonToRow(deleteRemindButton.build({ objectId: job.attrs._id?.toHexString() })) - }) - - } else if (args.getSubcommand() === 'list') { - - await reply(interaction, 'リマインダーの一覧を表示します') - const jobs = await agenda.jobs({ name: 'send remind', 'data.authorId': interaction.user.id }) - if (jobs.length == 0) return reply(interaction, '登録されているリマインダーはありません') + components: buttonToRow(deleteRemindButton.build({ objectId: job.attrs._id?.toHexString() })), + }); + } else if (args.getSubcommand() === "list") { + await reply(interaction, "リマインダーの一覧を表示します"); + const jobs = await agenda.jobs({ name: "send remind", "data.authorId": interaction.user.id }); + if (jobs.length == 0) return reply(interaction, "登録されているリマインダーはありません"); jobs.map(job => { - if (!job.attrs.data) return - const date = new Date(job.attrs.nextRunAt ?? '') - const { content, channelId } = job.attrs.data + if (!job.attrs.data) return; + const date = new Date(job.attrs.nextRunAt ?? ""); + const { content, channelId } = job.attrs.data; return reply(interaction, { embeds: [buildEmbed(content, date, channelId)], - components: buttonToRow(deleteRemindButton.build({ objectId: job.attrs._id?.toHexString() })) - }) - }) + components: buttonToRow(deleteRemindButton.build({ objectId: job.attrs._id?.toHexString() })), + }); + }); } - } -}) + }, +}); const buildEmbed = (content: string, date: Date, channelId: string) => new EmbedBuilder().addFields( - { name: '本文', value: content }, - { name: '日時', value: date.toLocaleString(), inline: true }, - { name: '送信先', value: `<#${channelId}>`, inline: true } - ) + { name: "本文", value: content }, + { name: "日時", value: date.toLocaleString(), inline: true }, + { name: "送信先", value: `<#${channelId}>`, inline: true } + ); - -agenda.define('send remind', async (job: any) => { +agenda.define("send remind", async (job: any) => { const { channelId, authorId, content } = job.attrs.data; - const channel = await client.channels.fetch(channelId) - const author = await client.users.fetch(authorId) + const channel = await client.channels.fetch(channelId); + const author = await client.users.fetch(authorId); - if (!channel || !channel.isTextBased()) return + if (!channel || !channel.isTextBased()) return; try { - await channel.send(content) - await job.remove() + await channel.send(content); + await job.remove(); } catch (e) { - console.log(e) + //何もしない } - -}); \ No newline at end of file +}); diff --git a/src/commands/slashcommands/rename.ts b/src/commands/slashcommands/rename.ts index 7dcf340..490a99d 100644 --- a/src/commands/slashcommands/rename.ts +++ b/src/commands/slashcommands/rename.ts @@ -4,47 +4,49 @@ import { reply } from "../../utils/Reply"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('rename') - .setDescription('ニックネームを一括で変更します') + .setName("rename") + .setDescription("ニックネームを一括で変更します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addRoleOption(option => option - .setName('ロール') - .setDescription('ニックネームをリセットするロール') - .setRequired(true) - ).addStringOption(option => option - .setName('先頭につける文字') - .setDescription('「先頭につける文字@ユーザー名」の形式に変更します(指定しなかった場合はニックネームをリセット)') - .setRequired(false) + .addRoleOption(option => + option.setName("ロール").setDescription("ニックネームをリセットするロール").setRequired(true) + ) + .addStringOption(option => + option + .setName("先頭につける文字") + .setDescription( + "「先頭につける文字@ユーザー名」の形式に変更します(指定しなかった場合はニックネームをリセット)" + ) + .setRequired(false) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); - await interaction.deferReply({ ephemeral: true }) + await interaction.guild?.members.fetch(); - await interaction.guild?.members.fetch() + const role = args.getRole("ロール", true) as Role; + const prefix = args.getString("先頭につける文字"); + const failed: string[] = []; - const role = args.getRole('ロール', true) as Role - const prefix = args.getString('先頭につける文字') - const failed: string[] = [] + await Promise.all( + role.members.map(async member => { + await rename(member, prefix).catch(() => failed.push(`${member}`)); + }) + ); - - await Promise.all(role.members.map(async member => { - await rename(member, prefix).catch(() => failed.push(`${member}`)) - })) - - await reply(interaction, 'ニックネームのリセットが完了しました') + await reply(interaction, "ニックネームのリセットが完了しました"); if (failed.length > 0) { - await reply(interaction, `ニックネームの変更に失敗したメンバー: ${failed.join(', ')}`) + await reply(interaction, `ニックネームの変更に失敗したメンバー: ${failed.join(", ")}`); } - } -}) + }, +}); export const rename = async (member: GuildMember, prefix?: string | null) => { - const nickname = member.nickname?.replace('@', '@') + const nickname = member.nickname?.replace("@", "@"); //@より後ろの名前 - const name = nickname?.substring(nickname.lastIndexOf('@') + 1) || member.user.username + const name = nickname?.substring(nickname.lastIndexOf("@") + 1) || member.user.username; - return await member.setNickname(!prefix ? name : `${prefix}@${name}`) -} \ No newline at end of file + return await member.setNickname(!prefix ? name : `${prefix}@${name}`); +}; diff --git a/src/commands/slashcommands/role.ts b/src/commands/slashcommands/role.ts index 52313f7..7a9a145 100644 --- a/src/commands/slashcommands/role.ts +++ b/src/commands/slashcommands/role.ts @@ -12,130 +12,133 @@ import { arraySplit } from "../../utils/ArraySplit"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('role') - .setDescription('ロールの付与や解除を行うボタンを作成します') + .setName("role") + .setDescription("ロールの付与や解除を行うボタンを作成します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addSubcommand(subcommand => subcommand - .setName('add') - .setDescription('対象のロールを持つメンバーにロールを付与します') - .addRoleOption(option => option - .setName('対象') - .setDescription('ロールを付与する対象を選択してください') - .setRequired(true) - ).addRoleOption(option => option - .setName('付与するロール') - .setDescription('ロールを付与する対象を選択してください') - .setRequired(true) - ).addStringOption(option => option - .setName('ボタンを他の人からも見えるようにする') - .setDescription('ボタンが他の人からも見えるようになり、押せるようになります') - .setRequired(false) - .addChoices( - { name: 'はい', value: 'true' }, - { name: 'いいえ', value: 'false' } + .addSubcommand(subcommand => + subcommand + .setName("add") + .setDescription("対象のロールを持つメンバーにロールを付与します") + .addRoleOption(option => + option.setName("対象").setDescription("ロールを付与する対象を選択してください").setRequired(true) ) - ) - ).addSubcommand(subcommand => subcommand - .setName('remove') - .setDescription('選択したロールをすべてのメンバーから解除します') - .addRoleOption(option => option - .setName('解除するロール') - .setDescription('解除するロールを選択してください') - .setRequired(true) - ).addStringOption(option => option - .setName('ボタンを他の人からも見えるようにする') - .setDescription('ボタンが他の人からも見えるようになり、押せるようになります') - .setRequired(false) - .addChoices( - { name: 'はい', value: 'true' }, - { name: 'いいえ', value: 'false' } + .addRoleOption(option => + option + .setName("付与するロール") + .setDescription("ロールを付与する対象を選択してください") + .setRequired(true) ) - ) - ).addSubcommand(subcommand => subcommand - .setName('change') - .setDescription('対象のロールを別のロールに付け替えます') - .addRoleOption(option => option - .setName('解除するロール') - .setDescription('解除するロールを選択してください') - .setRequired(true) - ).addRoleOption(option => option - .setName('付与するロール') - .setDescription('付与するロールを選択してください') - .setRequired(true) - ).addStringOption(option => option - .setName('ボタンを他の人からも見えるようにする') - .setDescription('ボタンが他の人からも見えるようになり、押せるようになります(非推奨)') - .setRequired(false) - .addChoices( - { name: 'はい', value: 'true' }, - { name: 'いいえ', value: 'false' } + .addStringOption(option => + option + .setName("ボタンを他の人からも見えるようにする") + .setDescription("ボタンが他の人からも見えるようになり、押せるようになります") + .setRequired(false) + .addChoices({ name: "はい", value: "true" }, { name: "いいえ", value: "false" }) ) - ) - ).addSubcommand(subcommand => subcommand - .setName('self') - .setDescription('ロールの取得や解除を行うボタンを作成します') - .addRoleOption(option => option - .setName('付与するロール') - .setDescription('ロールを選択してください') - ) + ) + .addSubcommand(subcommand => + subcommand + .setName("remove") + .setDescription("選択したロールをすべてのメンバーから解除します") + .addRoleOption(option => + option + .setName("解除するロール") + .setDescription("解除するロールを選択してください") + .setRequired(true) + ) + .addStringOption(option => + option + .setName("ボタンを他の人からも見えるようにする") + .setDescription("ボタンが他の人からも見えるようになり、押せるようになります") + .setRequired(false) + .addChoices({ name: "はい", value: "true" }, { name: "いいえ", value: "false" }) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName("change") + .setDescription("対象のロールを別のロールに付け替えます") + .addRoleOption(option => + option + .setName("解除するロール") + .setDescription("解除するロールを選択してください") + .setRequired(true) + ) + .addRoleOption(option => + option + .setName("付与するロール") + .setDescription("付与するロールを選択してください") + .setRequired(true) + ) + .addStringOption(option => + option + .setName("ボタンを他の人からも見えるようにする") + .setDescription("ボタンが他の人からも見えるようになり、押せるようになります(非推奨)") + .setRequired(false) + .addChoices({ name: "はい", value: "true" }, { name: "いいえ", value: "false" }) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName("self") + .setDescription("ロールの取得や解除を行うボタンを作成します") + .addRoleOption(option => option.setName("付与するロール").setDescription("ロールを選択してください")) ) as SlashCommandBuilder, - execute: async ({ client, interaction, args }) => { - const subCommand = args.getSubcommand() + execute: async ({ interaction, args }) => { + const subCommand = args.getSubcommand(); - const target = args.getRole('対象') - const roleAdd = args.getRole('付与するロール') - const roleRemove = args.getRole('解除するロール') - const ephemeral = args.getString('ボタンを他の人からも見えるようにする') === 'true' ? false : true + const target = args.getRole("対象"); + const roleAdd = args.getRole("付与するロール"); + const roleRemove = args.getRole("解除するロール"); + const ephemeral = args.getString("ボタンを他の人からも見えるようにする") === "true" ? false : true; switch (subCommand) { - case 'add': + case "add": await reply(interaction, { components: buttonToRow(roleAddButton.build({ target: target, role: roleAdd })), - ephemeral - }) - break - case 'remove': + ephemeral, + }); + break; + case "remove": await reply(interaction, { components: buttonToRow(roleRemoveButton.build({ role: roleRemove })), - ephemeral - }) - break - case 'change': + ephemeral, + }); + break; + case "change": await reply(interaction, { components: buttonToRow(roleChangeButton.build({ before: roleRemove, after: roleAdd })), - ephemeral - }) - break - case 'self': + ephemeral, + }); + break; + case "self": if (!roleAdd) { - const roleManager = interaction.guild?.roles - const roles = (await roleManager?.fetch())?.filter(role => !role.managed && role != roleManager?.everyone) - if (!roles || roles?.size == 0) return reply(interaction, 'ロールが見つかりませんでした') + const roleManager = interaction.guild?.roles; + const roles = (await roleManager?.fetch())?.filter( + role => !role.managed && role != roleManager?.everyone + ); + if (!roles || roles?.size == 0) return reply(interaction, "ロールが見つかりませんでした"); - roles.sort((roleA, roleB) => roleB.rawPosition - roleA.rawPosition) + roles.sort((roleA, roleB) => roleB.rawPosition - roleA.rawPosition); for await (const [index, rolesSplited] of arraySplit([...roles.values()], 125).entries()) { await reply(interaction, { components: roleList.build(rolesSplited, index * 5 + 1), - }) + }); } - return + return; } - await interaction.channel?.send(buildRoleRow(roleAdd)) - await reply(interaction, 'ボタンを作成しました') - break + await interaction.channel?.send(buildRoleRow(roleAdd)); + await reply(interaction, "ボタンを作成しました"); + break; } - } -}) + }, +}); export const buildRoleRow = (role: Role | APIRole) => { return { content: `${role}`, - components: buttonToRow([ - ...roleGetButton.build({ role }), - ...roleReleaseButton.build({ role }) - ]), + components: buttonToRow([...roleGetButton.build({ role }), ...roleReleaseButton.build({ role })]), allowedMentions: { parse: [] }, - - } -} \ No newline at end of file + }; +}; diff --git a/src/commands/slashcommands/select.ts b/src/commands/slashcommands/select.ts index 83cb0ec..9760121 100644 --- a/src/commands/slashcommands/select.ts +++ b/src/commands/slashcommands/select.ts @@ -1,4 +1,12 @@ -import { ButtonInteraction, Collection, ComponentType, EmbedBuilder, GuildMember, Role, SlashCommandBuilder } from "discord.js"; +import { + ButtonInteraction, + Collection, + ComponentType, + EmbedBuilder, + GuildMember, + Role, + SlashCommandBuilder, +} from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; import { reply } from "../../utils/Reply"; import selectButton from "../../components/buttons/select"; @@ -9,159 +17,158 @@ import { rename } from "./rename"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('select') - .setDescription('キャラ選択/犯人投票を行います') + .setName("select") + .setDescription("キャラ選択/犯人投票を行います") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addStringOption(option => option - .setName('投票モード') - .setDescription('集計結果をPLに非表示にし、得票数を表示します') - .setRequired(true) - .addChoices( - { name: 'キャラ選択', value: 'char' }, - { name: '犯人投票', value: 'vote' } - ) - ).addStringOption(option => option - .setName('選択肢') - .setDescription('選択肢をスペース区切りで入力して下さい') - .setRequired(true) - ).addNumberOption(option => option - .setName('制限時間') - .setDescription('制限時間を分単位で入力して下さい(デフォルトは5分)') - .setRequired(false) - .setMaxValue(1440) - .setMinValue(1) + .addStringOption(option => + option + .setName("投票モード") + .setDescription("集計結果をPLに非表示にし、得票数を表示します") + .setRequired(true) + .addChoices({ name: "キャラ選択", value: "char" }, { name: "犯人投票", value: "vote" }) + ) + .addStringOption(option => + option.setName("選択肢").setDescription("選択肢をスペース区切りで入力して下さい").setRequired(true) + ) + .addNumberOption(option => + option + .setName("制限時間") + .setDescription("制限時間を分単位で入力して下さい(デフォルトは5分)") + .setRequired(false) + .setMaxValue(1440) + .setMinValue(1) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { - - const choices = args.getString('選択肢', true).split(/[  ,、]/).filter(choice => choice !== '').map(choice => { - const roleId = choice.match(/<@&(.*)>/) - const role = roleId ? interaction.guild?.roles.cache.get(roleId[1]) : null - if (role) return role - return choice - }) - - if (choices.length > 24) return reply(interaction, { content: '選択肢の数が多すぎます(最大24個)', ephemeral: true }) - - const content = (args.getString('投票モード', true) === 'char') ? 'キャラクターを選択して下さい' : '投票先を選択してください' + const choices = args + .getString("選択肢", true) + .split(/[  ,、]/) + .filter(choice => choice !== "") + .map(choice => { + const roleId = choice.match(/<@&(.*)>/); + const role = roleId ? interaction.guild?.roles.cache.get(roleId[1]) : null; + if (role) return role; + return choice; + }); + + if (choices.length > 24) + return reply(interaction, { content: "選択肢の数が多すぎます(最大24個)", ephemeral: true }); + + const content = + args.getString("投票モード", true) === "char" ? "キャラクターを選択して下さい" : "投票先を選択してください"; const message = await interaction.channel?.send({ content, - components: buttonToRow([ - ...selectButton.build({ choices }), - ...selectAgregate.build()]), - }) + components: buttonToRow([...selectButton.build({ choices }), ...selectAgregate.build()]), + }); - const timeLimit = args.getNumber('制限時間') ?? 5 + const timeLimit = args.getNumber("制限時間") ?? 5; - if (!message) return reply(interaction, 'メッセージの送信に失敗しました') + if (!message) return reply(interaction, "メッセージの送信に失敗しました"); - await reply(interaction, `集計を開始します\n投票は${timeLimit}分後に自動で集計されます`) + await reply(interaction, `集計を開始します\n投票は${timeLimit}分後に自動で集計されます`); - const filter = (interaction: ButtonInteraction) => interaction.customId.startsWith('select') + const filter = (interaction: ButtonInteraction) => interaction.customId.startsWith("select"); const collector = message.createMessageComponentCollector({ filter, time: 1000 * 60 * timeLimit, //5分 componentType: ComponentType.Button, - }) + }); - collector.on('collect', async (i: ButtonInteraction) => { - const customId = getArgs(i)[0] - if (customId === 'selectAgregate') { + collector.on("collect", async (i: ButtonInteraction) => { + const customId = getArgs(i)[0]; + if (customId === "selectAgregate") { if (interaction.user.id === i.user.id) { - collector.stop() - } - else { - await reply(i, '集計は投票を開始した人のみが行えます') + collector.stop(); + } else { + await reply(i, "集計は投票を開始した人のみが行えます"); } - } - else if (collector.collected.filter(c => c.user.id === i.user.id).size > 1) { + } else if (collector.collected.filter(c => c.user.id === i.user.id).size > 1) { await reply(i, `${getArgs(i)[2]}に変更しました`); - } - else { + } else { await reply(i, `${getArgs(i)[2]}を選択しました`); if (interaction.user.id !== i.user.id) { - await reply(interaction, `${i.member}が選択しました`); + await reply(interaction, `${i.member}が選択しました`).catch(() => {}); } } - }) + }); + + collector.on("end", async collected => { + const result = new Collection(); //誰が何番目に入れたか + const voter = new Collection(); //何番目の選択肢に誰が入れたか - collector.on('end', async collected => { - const result = new Collection()//誰が何番目に入れたか - const voter = new Collection() //何番目の選択肢に誰が入れたか - //誰が何番目に入れたかの集計(重複は無視) - collected.filter(c => c.customId.startsWith('select:')).map(i => result.set(i.member as GuildMember, Number(getArgs(i)[1]))) + collected + .filter(c => c.customId.startsWith("select:")) + .map(i => result.set(i.member as GuildMember, Number(getArgs(i)[1]))); //何番目の選択肢に誰が入れたかの集計 for (const [member, value] of result.entries()) { if (voter.has(value)) { - voter.get(value)?.push(member) + voter.get(value)?.push(member); } else { - voter.set(value, [member]) + voter.set(value, [member]); } } - if (args.getString('投票モード') === 'char') { - let content = '' + if (args.getString("投票モード") === "char") { + let content = ""; let warn = false; for (const [member, value] of result.entries()) { - const choice = choices[value] + const choice = choices[value]; if (voter.get(value)?.length === 1) { - content += `${member} → ${choice} ✅\n` + content += `${member} → ${choice} ✅\n`; if (choice instanceof Role) { - await member.roles.add(choice) - await reply(interaction, `${member}に${choice}を付与しました`) - await rename(member, choice.name).catch(() => { }) + await member.roles.add(choice); + await reply(interaction, `${member}に${choice}を付与しました`); + await rename(member, choice.name).catch(() => {}); } else { - await rename(member, choice).catch(() => { }) + await rename(member, choice).catch(() => {}); } } else { - content += `${member} → ${choices[value]} ⚠️\n` - warn = true + content += `${member} → ${choices[value]} ⚠️\n`; + warn = true; } } const embed = new EmbedBuilder() - .addFields({ name: '集計結果', value: content || 'なし' }) - .setColor(warn ? 0xFFCC4D : 0x77B255) + .addFields({ name: "集計結果", value: content || "なし" }) + .setColor(warn ? 0xffcc4d : 0x77b255); await interaction.channel?.send({ embeds: [embed], - allowedMentions: { parse: [] } - }) - - } else if (args.getString('投票モード') === 'vote') { - let numOfVotes = '' - voter.sort((a, b) => b.length - a.length) + allowedMentions: { parse: [] }, + }); + } else if (args.getString("投票モード") === "vote") { + let numOfVotes = ""; + voter.sort((a, b) => b.length - a.length); for (const [key, value] of voter.entries()) { - numOfVotes += `${choices[key]} : ${value.length} 票\n` + numOfVotes += `${choices[key]} : ${value.length} 票\n`; } - let vote = '' + let vote = ""; for (const [member, value] of result.entries()) { - vote += `${member} → ${choices[value]}\n` + vote += `${member} → ${choices[value]}\n`; } const embed = new EmbedBuilder() .addFields( - { name: '投票数', value: numOfVotes || 'なし' }, - { name: '投票先', value: vote || 'なし' } + { name: "投票数", value: numOfVotes || "なし" }, + { name: "投票先", value: vote || "なし" } ) - .setColor(0x3B88C3) + .setColor(0x3b88c3); await reply(interaction, { embeds: [embed], allowedMentions: { parse: [] }, components: buttonToRow(unehemeralButton.build()), - }) + }); } - await message.delete().catch(() => { }) - }) - - } -}) + await message.delete().catch(() => {}); + }); + }, +}); -const getArgs = (interaction: ButtonInteraction): string[] => interaction.customId.split(/[:,]/) \ No newline at end of file +const getArgs = (interaction: ButtonInteraction): string[] => interaction.customId.split(/[:,]/); diff --git a/src/commands/slashcommands/server.ts b/src/commands/slashcommands/server.ts index c40a0d1..ed0f64f 100644 --- a/src/commands/slashcommands/server.ts +++ b/src/commands/slashcommands/server.ts @@ -2,23 +2,25 @@ import { SlashCommandBuilder } from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; import { reply } from "../../utils/Reply"; - export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('server') - .setDescription('メンバー数、ロール数、チャンネル数を表示します') + .setName("server") + .setDescription("メンバー数、ロール数、チャンネル数を表示します") .setDMPermission(false), execute: async ({ interaction }) => { - const guild = interaction.guild - if (!guild) return + const guild = interaction.guild; + if (!guild) return; - const members = await guild.members.fetch() + const members = await guild.members.fetch(); const [bot, user] = members.partition(member => member.user.bot); - await reply(interaction, ` + await reply( + interaction, + ` メンバー数 : ${user.size}人 (+ Bot${bot.size}台) ロール数 : ${(await guild.roles.fetch()).size}個 (上限250個) -チャンネル数 : ${(await guild.channels.fetch()).size}個 (上限500個)`) - } -}) \ No newline at end of file +チャンネル数 : ${(await guild.channels.fetch()).size}個 (上限500個)` + ); + }, +}); diff --git a/src/commands/slashcommands/setup.ts b/src/commands/slashcommands/setup.ts index eb19839..da0c824 100644 --- a/src/commands/slashcommands/setup.ts +++ b/src/commands/slashcommands/setup.ts @@ -4,173 +4,133 @@ import { reply } from "../../utils/Reply"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('setup') - .setDescription('新規プレイ用のカテゴリーを作成します') + .setName("setup") + .setDescription("新規プレイ用のカテゴリーを作成します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addStringOption(option => option - .setName('シナリオ名') - .setDescription('シナリオ名') - .setRequired(true) + .addStringOption(option => option.setName("シナリオ名").setDescription("シナリオ名").setRequired(true)) + .addIntegerOption(option => + option.setName("プレイヤー数").setDescription("プレイヤー数").setRequired(true).setMinValue(0) ) - .addIntegerOption(option => option - .setName('プレイヤー数') - .setDescription('プレイヤー数') - .setRequired(true) - .setMinValue(0) + .addIntegerOption(option => + option.setName("密談チャンネル数").setDescription("密談チャンネル数").setRequired(true).setMinValue(0) ) - .addIntegerOption(option => option - .setName('密談チャンネル数') - .setDescription('密談チャンネル数') - .setRequired(true) - .setMinValue(0) + .addRoleOption(option => + option + .setName("ロールの作成位置") + .setDescription("指定したロールの下に新規ロール作成します") + .setRequired(false) ) - .addRoleOption(option => option - .setName('ロールの作成位置') - .setDescription('指定したロールの下に新規ロール作成します') - .setRequired(false) - ) - .addStringOption(option => option - .setName('個別ロールを作成しない') - .setDescription('個別のチャンネルは作られますがロールは作成されません') - .setRequired(false) - .addChoices( - { name: 'はい', value: 'true' }, - { name: 'いいえ', value: 'false' } - ) + .addStringOption(option => + option + .setName("個別ロールを作成しない") + .setDescription("個別のチャンネルは作られますがロールは作成されません") + .setRequired(false) + .addChoices({ name: "はい", value: "true" }, { name: "いいえ", value: "false" }) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + const guild = interaction.guild as Guild; + const Bot = guild.members.me as GuildMember; + const everyone = guild?.roles.everyone; + const title = args.getString("シナリオ名", true); + const playerCount = args.getInteger("プレイヤー数", true); + const privateCount = args.getInteger("密談チャンネル数", true); - const guild = interaction.guild as Guild - const Bot = guild.members.me as GuildMember - const everyone = guild?.roles.everyone - const title = args.getString('シナリオ名', true) - const playerCount = args.getInteger('プレイヤー数', true) - const privateCount = args.getInteger('密談チャンネル数', true) - - const role = args.getRole('ロールの作成位置') ?? everyone - - const rolePosition = role?.position - - if (playerCount + privateCount + 5 >= 50) return reply(interaction, 'カテゴリーに入り切りません\nチャンネル数を減らしてください') + const role = args.getRole("ロールの作成位置") ?? everyone; - await interaction.deferReply({ ephemeral: true }) + const rolePosition = role?.position; - const GM = await guild.roles.create({ name: `${title} GM`, position: rolePosition }) - const PL = await guild.roles.create({ name: `${title} PL`, position: rolePosition }) - const SP = await guild.roles.create({ name: `${title} 観戦`, position: rolePosition }) + if (playerCount + privateCount + 5 >= 50) + return reply(interaction, "カテゴリーに入り切りません\nチャンネル数を減らしてください"); + await interaction.deferReply({ ephemeral: true }); - const invisible = { deny: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect] } //見えない - const visible = { allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect], deny: [PermissionFlagsBits.SendMessages] } //見るだけ(書き込めない) - const writable = { allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect] } //書き込める - + const GM = await guild.roles.create({ name: `${title} GM`, position: rolePosition }); + const PL = await guild.roles.create({ name: `${title} PL`, position: rolePosition }); + const SP = await guild.roles.create({ name: `${title} 観戦`, position: rolePosition }); + const invisible = { deny: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect] }; //見えない + const visible = { + allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect], + deny: [PermissionFlagsBits.SendMessages], + }; //見るだけ(書き込めない) + const writable = { allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect] }; //書き込める const defaultPerm = [ { id: everyone.id, ...invisible }, { id: Bot.id, ...writable }, - { id: GM.id, ...writable } - ] + { id: GM.id, ...writable }, + ]; const category = await guild.channels.create({ name: title, type: ChannelType.GuildCategory, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...invisible }, - { id: SP.id, ...visible }, - ] - }) + permissionOverwrites: [...defaultPerm, { id: PL.id, ...invisible }, { id: SP.id, ...visible }], + }); await guild.channels.create({ - name: '一般', + name: "一般", type: ChannelType.GuildText, parent: category, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...writable }, - { id: SP.id, ...writable }, - ] - }) + permissionOverwrites: [...defaultPerm, { id: PL.id, ...writable }, { id: SP.id, ...writable }], + }); await guild.channels.create({ - name: '共通情報', + name: "共通情報", type: ChannelType.GuildText, parent: category, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...visible }, - { id: SP.id, ...visible }, - ] - }) + permissionOverwrites: [...defaultPerm, { id: PL.id, ...visible }, { id: SP.id, ...visible }], + }); await guild.channels.create({ - name: '観戦', + name: "観戦", type: ChannelType.GuildText, parent: category, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...invisible }, - { id: SP.id, ...writable }, - ] + permissionOverwrites: [...defaultPerm, { id: PL.id, ...invisible }, { id: SP.id, ...writable }], }); for (let i = 0; i < playerCount; i++) { - const perm = [ - ...defaultPerm, - { id: SP.id, ...visible }, - ] - - if (!(interaction.options.getString('個別ロールを作成しない') === 'true')) { - const PL_i = await guild.roles.create({ name: `${title} PL${i + 1}`, position: rolePosition }) - perm.push({ id: PL_i.id, ...writable }) + const perm = [...defaultPerm, { id: SP.id, ...visible }]; + + if (!(interaction.options.getString("個別ロールを作成しない") === "true")) { + const PL_i = await guild.roles.create({ name: `${title} PL${i + 1}`, position: rolePosition }); + perm.push({ id: PL_i.id, ...writable }); } await guild.channels.create({ name: `pc${i + 1}`, type: ChannelType.GuildText, parent: category, - permissionOverwrites: perm - }) + permissionOverwrites: perm, + }); } await guild.channels.create({ - name: '解説', + name: "解説", type: ChannelType.GuildText, parent: category, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...invisible }, - { id: SP.id, ...visible }, - ] - }) + permissionOverwrites: [...defaultPerm, { id: PL.id, ...invisible }, { id: SP.id, ...visible }], + }); await guild.channels.create({ - name: '全体会議', + name: "全体会議", type: ChannelType.GuildVoice, parent: category, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...writable }, - { id: SP.id, ...writable }, - ] + permissionOverwrites: [...defaultPerm, { id: PL.id, ...writable }, { id: SP.id, ...writable }], }); - await Promise.all([...Array(privateCount)].map(async (_, i) => { - await guild.channels.create({ - name: `密談場所${i + 1}`, - type: ChannelType.GuildVoice, - parent: category, - permissionOverwrites: [ - ...defaultPerm, - { id: PL.id, ...writable }, - { id: SP.id, ...writable }, - ] + await Promise.all( + [...Array(privateCount)].map(async (_, i) => { + await guild.channels.create({ + name: `密談場所${i + 1}`, + type: ChannelType.GuildVoice, + parent: category, + permissionOverwrites: [...defaultPerm, { id: PL.id, ...writable }, { id: SP.id, ...writable }], + }); }) - })) + ); - await reply(interaction, `「${title}」の作成が完了しました`) - } -}) \ No newline at end of file + await reply(interaction, `「${title}」の作成が完了しました`); + }, +}); diff --git a/src/commands/slashcommands/sync.ts b/src/commands/slashcommands/sync.ts index 1b695e0..97416d1 100644 --- a/src/commands/slashcommands/sync.ts +++ b/src/commands/slashcommands/sync.ts @@ -4,27 +4,29 @@ import { reply } from "../../utils/Reply"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('sync') - .setDescription('カテゴリ内のすべてのチャンネルの権限をカテゴリと同期します') + .setName("sync") + .setDescription("カテゴリ内のすべてのチャンネルの権限をカテゴリと同期します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .addChannelTypes(ChannelType.GuildCategory) - .setName('同期するカテゴリ') - .setDescription('同期するカテゴリ') - .setRequired(true) + .addChannelOption(option => + option + .addChannelTypes(ChannelType.GuildCategory) + .setName("同期するカテゴリ") + .setDescription("同期するカテゴリ") + .setRequired(true) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { + await interaction.deferReply({ ephemeral: true }); - await interaction.deferReply({ ephemeral: true }) + const category = args.getChannel("同期するカテゴリ", true) as CategoryChannel; - const category = args.getChannel('同期するカテゴリ', true) as CategoryChannel + await Promise.all( + category.children.cache.map(async ch => { + await ch.lockPermissions(); + }) + ); - await Promise.all(category.children.cache.map(async ch => { - await ch.lockPermissions() - })) - - await reply(interaction, `「${category.name}」内のチャンネルの権限を同期しました`) - } -}) \ No newline at end of file + await reply(interaction, `「${category.name}」内のチャンネルの権限を同期しました`); + }, +}); diff --git a/src/commands/slashcommands/transfer.ts b/src/commands/slashcommands/transfer.ts index 58b0933..f160333 100644 --- a/src/commands/slashcommands/transfer.ts +++ b/src/commands/slashcommands/transfer.ts @@ -1,4 +1,13 @@ -import { CategoryChannel, ChannelType, discordSort, GuildTextBasedChannel, NewsChannel, SlashCommandBuilder, TextChannel, VoiceChannel } from "discord.js"; +import { + CategoryChannel, + ChannelType, + discordSort, + GuildTextBasedChannel, + NewsChannel, + SlashCommandBuilder, + TextChannel, + VoiceChannel, +} from "discord.js"; import { SlashCommand } from "../../structures/SlashCommand"; import transferButton from "../../components/buttons/transfer"; import transferList from "../../components/selectmenu/transferList"; @@ -7,40 +16,41 @@ import { buttonToRow } from "../../utils/ButtonToRow"; export default new SlashCommand({ data: new SlashCommandBuilder() - .setName('transfer') - .setDescription('メッセージを転送するボタンを作成します') + .setName("transfer") + .setDescription("メッセージを転送するボタンを作成します") .setDMPermission(false) .setDefaultMemberPermissions(0) - .addChannelOption(option => option - .setName('転送先') - .setDescription('転送先のチャンネルを選択してください') - .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) - .setRequired(false) + .addChannelOption(option => + option + .setName("転送先") + .setDescription("転送先のチャンネルを選択してください") + .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) + .setRequired(false) ) as SlashCommandBuilder, execute: async ({ interaction, args }) => { - - const destination = args.getChannel('転送先') + const destination = args.getChannel("転送先"); if (!destination) { - const channel = interaction.channel as GuildTextBasedChannel - const category = channel.parent?.parent ?? channel.parent as CategoryChannel | undefined - const channels = (category?.children.cache ?? interaction.guild?.channels.cache.filter(ch => !ch.parent)) - ?.filter((channel): channel is TextChannel | NewsChannel | VoiceChannel => channel.isTextBased()) + const channel = interaction.channel as GuildTextBasedChannel; + const category = channel.parent?.parent ?? (channel.parent as CategoryChannel | undefined); + const channels = ( + category?.children.cache ?? interaction.guild?.channels.cache.filter(ch => !ch.parent) + )?.filter((channel): channel is TextChannel | NewsChannel | VoiceChannel => channel.isTextBased()); - if (!channels) return + if (!channels) return; await reply(interaction, { - content: '転送先のチャンネルを選択してください', - components: transferList.build([...discordSort(channels).values()]) - }) - return + content: "転送先のチャンネルを選択してください", + components: transferList.build([...discordSort(channels).values()]), + }); + return; } - await reply(interaction, `「${destination}」に転送するメッセージと同じリアクションを付けてください`) + await reply(interaction, `「${destination}」に転送するメッセージと同じリアクションを付けてください`); await interaction.channel?.send({ components: buttonToRow(transferButton.build({ destination })), - }) - } -}) \ No newline at end of file + }); + }, +}); diff --git a/src/components/buttons/deleteRemind.ts b/src/components/buttons/deleteRemind.ts index f6bd067..25fa268 100644 --- a/src/components/buttons/deleteRemind.ts +++ b/src/components/buttons/deleteRemind.ts @@ -1,19 +1,18 @@ -import { ButtonBuilder, ButtonStyle, GuildChannel, Role, User } from "discord.js"; +import { ButtonBuilder, ButtonStyle } from "discord.js"; import { ObjectId } from "mongodb"; import { agenda } from "../../agenda"; import { Button } from "../../structures/Button"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'deleteRemind', - build: ({ objectId }: { objectId: ObjectId }) => [new ButtonBuilder() - .setCustomId(`deleteRemind:${objectId}`) - .setLabel('削除') - .setStyle(ButtonStyle.Danger)] - , + customId: "deleteRemind", + build: ({ objectId }: { objectId: ObjectId }) => [ + new ButtonBuilder().setCustomId(`deleteRemind:${objectId}`).setLabel("削除").setStyle(ButtonStyle.Danger), + ], execute: async ({ interaction, args }) => { const [objectId] = args; - agenda.cancel({ _id: new ObjectId(objectId) }) - .then(() => reply(interaction, 'リマインドを削除しました')) - .catch(() => reply(interaction, 'リマインドの削除に失敗しました')) + agenda + .cancel({ _id: new ObjectId(objectId) }) + .then(() => reply(interaction, "リマインドを削除しました")) + .catch(() => reply(interaction, "リマインドの削除に失敗しました")); }, -}) \ No newline at end of file +}); diff --git a/src/components/buttons/diceroll.ts b/src/components/buttons/diceroll.ts index 31899d0..2bd7467 100644 --- a/src/components/buttons/diceroll.ts +++ b/src/components/buttons/diceroll.ts @@ -2,27 +2,24 @@ import { ButtonBuilder, ButtonStyle } from "discord.js"; import { Button } from "../../structures/Button"; export default new Button({ - customId: 'diceroll', - build: ({ x, y }: { x: number, y: number }) => [new ButtonBuilder() - .setCustomId(`diceroll:${x},${y}`) - .setLabel(`${x}d${y}`) - .setStyle(ButtonStyle.Primary)] - , + customId: "diceroll", + build: ({ x, y }: { x: number; y: number }) => [ + new ButtonBuilder().setCustomId(`diceroll:${x},${y}`).setLabel(`${x}d${y}`).setStyle(ButtonStyle.Primary), + ], execute: async ({ interaction, args }) => { - const [x, y] = args.map(Number) - await interaction.reply("ダイスロールを実行中...") - await interaction.channel?.send(`${interaction.member} 🎲 ${diceRole(x, y)}`) - await interaction.deleteReply() + const [x, y] = args.map(Number); + await interaction.reply("ダイスロールを実行中..."); + await interaction.channel?.send(`${interaction.member} 🎲 ${diceRole(x, y)}`); + await interaction.deleteReply(); }, -}) - +}); const diceRole = (x: number, y: number): string => { if (x == 1) { return `${x}d${y} → ${getRandomInt(y)}`; } - const result = [...Array(x)].map(() => getRandomInt(y)) + const result = [...Array(x)].map(() => getRandomInt(y)); return `${x}d${y} → [${result.join(",")}] → ${result.reduce((a, b) => a + b)}`; -} +}; const getRandomInt = (max: number) => Math.floor(Math.random() * max) + 1; diff --git a/src/components/buttons/open.ts b/src/components/buttons/open.ts index 28373c1..f223f49 100644 --- a/src/components/buttons/open.ts +++ b/src/components/buttons/open.ts @@ -2,23 +2,25 @@ import { ButtonBuilder, ButtonStyle, GuildChannel, Role, User } from "discord.js import { Button } from "../../structures/Button"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'open', - build: ({ channel, mentionable }: { channel: GuildChannel, mentionable: Role | User }) => [new ButtonBuilder() - .setCustomId(`open:${channel.id},${mentionable.id}`) - .setLabel('公開') - .setStyle(ButtonStyle.Primary)] - , + customId: "open", + build: ({ channel, mentionable }: { channel: GuildChannel; mentionable: Role | User }) => [ + new ButtonBuilder() + .setCustomId(`open:${channel.id},${mentionable.id}`) + .setLabel("公開") + .setStyle(ButtonStyle.Primary), + ], execute: async ({ interaction, args }) => { const [channelId, mentionableId] = args; const channel = interaction.guild?.channels.cache.get(channelId); - const mentionable = interaction.guild?.roles.cache.get(mentionableId) || interaction.guild?.members.cache.get(mentionableId); - - if (!channel) return reply(interaction, 'チャンネルが見つかりませんでした。') - if (!mentionable) return reply(interaction, '公開相手が見つかりませんでした。') - if (!('permissionOverwrites' in channel)) return reply(interaction, '権限を編集できません') + const mentionable = + interaction.guild?.roles.cache.get(mentionableId) || interaction.guild?.members.cache.get(mentionableId); - await channel?.permissionOverwrites.edit(mentionable.id, { ViewChannel: true }) - await interaction.message.delete() - await reply(interaction, 'チャンネルを公開しました'); + if (!channel) return reply(interaction, "チャンネルが見つかりませんでした。"); + if (!mentionable) return reply(interaction, "公開相手が見つかりませんでした。"); + if (!("permissionOverwrites" in channel)) return reply(interaction, "権限を編集できません"); + + await channel?.permissionOverwrites.edit(mentionable.id, { ViewChannel: true }); + await interaction.message.delete(); + await reply(interaction, "チャンネルを公開しました"); }, -}) \ No newline at end of file +}); diff --git a/src/components/buttons/roleAdd.ts b/src/components/buttons/roleAdd.ts index 03fdfaa..8d1d369 100644 --- a/src/components/buttons/roleAdd.ts +++ b/src/components/buttons/roleAdd.ts @@ -3,29 +3,28 @@ import { Button } from "../../structures/Button"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'roleAdd', - build: ({ target, role }: { target: Role, role: Role }) => [new ButtonBuilder() - .setCustomId(`roleAdd:${target.id},${role.id}`) - .setLabel(`「@${target.name}」に「@${role.name}」を付与`) - .setStyle(ButtonStyle.Success)] - , + customId: "roleAdd", + build: ({ target, role }: { target: Role; role: Role }) => [ + new ButtonBuilder() + .setCustomId(`roleAdd:${target.id},${role.id}`) + .setLabel(`「@${target.name}」に「@${role.name}」を付与`) + .setStyle(ButtonStyle.Success), + ], execute: async ({ interaction, args }) => { + const [targetId, roleId] = args; + const target = await interaction.guild?.roles.fetch(targetId); + const role = await interaction.guild?.roles.fetch(roleId); + if (!target || !role) return reply(interaction, "ロールが見つかりません"); - const [targetId, roleId] = args - const target = await interaction.guild?.roles.fetch(targetId) - const role = await interaction.guild?.roles.fetch(roleId) + await interaction.deferReply({ ephemeral: true }); - if (!target || !role) return reply(interaction, 'ロールが見つかりません') + await interaction.guild?.members.fetch(); - await interaction.deferReply({ ephemeral: true }) + const { members } = target; + if (members.size === 0) return reply(interaction, `${target}を持つメンバーがいません`); - await interaction.guild?.members.fetch() - - const { members } = target - if (members.size === 0) return reply(interaction, `${target}を持つメンバーがいません`) - - await Promise.all(members.map(async member => await member.roles.add(role))) - await reply(interaction, `${[...members.values()].join(', ')}に${role}を付与しました`) - } -}) \ No newline at end of file + await Promise.all(members.map(async member => await member.roles.add(role))); + await reply(interaction, `${[...members.values()].join(", ")}に${role}を付与しました`); + }, +}); diff --git a/src/components/buttons/roleChange.ts b/src/components/buttons/roleChange.ts index 4399a5a..42f9ec1 100644 --- a/src/components/buttons/roleChange.ts +++ b/src/components/buttons/roleChange.ts @@ -3,30 +3,33 @@ import { Button } from "../../structures/Button"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'roleChange', - build: ({ before, after }: { before: Role, after: Role }) => [new ButtonBuilder() - .setCustomId(`roleChange:${before.id},${after.id}`) - .setLabel(`「@${before.name}」を「@${after.name}」に付け替え`) - .setStyle(ButtonStyle.Primary)] - , + customId: "roleChange", + build: ({ before, after }: { before: Role; after: Role }) => [ + new ButtonBuilder() + .setCustomId(`roleChange:${before.id},${after.id}`) + .setLabel(`「@${before.name}」を「@${after.name}」に付け替え`) + .setStyle(ButtonStyle.Primary), + ], execute: async ({ interaction, args }) => { - const [beforeId, afterId] = args - const before = await interaction.guild?.roles.fetch(beforeId) - const after = await interaction.guild?.roles.fetch(afterId) + const [beforeId, afterId] = args; + const before = await interaction.guild?.roles.fetch(beforeId); + const after = await interaction.guild?.roles.fetch(afterId); - if (!before || !after) return reply(interaction, 'ロールが見つかりません') - await interaction.deferReply({ ephemeral: true }) + if (!before || !after) return reply(interaction, "ロールが見つかりません"); + await interaction.deferReply({ ephemeral: true }); - await interaction.guild?.members.fetch() + await interaction.guild?.members.fetch(); - const { members } = before - if (members.size === 0) return reply(interaction, `${before}を持つメンバーがいません`) + const { members } = before; + if (members.size === 0) return reply(interaction, `${before}を持つメンバーがいません`); - await Promise.all(members.map(async member => { - await member.roles.add(after) - await member.roles.remove(before) - })) + await Promise.all( + members.map(async member => { + await member.roles.add(after); + await member.roles.remove(before); + }) + ); - await reply(interaction, `${[...members.values()].join(', ')}の${before}を${after}に変更しました`) + await reply(interaction, `${[...members.values()].join(", ")}の${before}を${after}に変更しました`); }, -}) \ No newline at end of file +}); diff --git a/src/components/buttons/roleGet.ts b/src/components/buttons/roleGet.ts index 27800f0..2af1c9d 100644 --- a/src/components/buttons/roleGet.ts +++ b/src/components/buttons/roleGet.ts @@ -4,27 +4,23 @@ import { isEditable } from "../../utils/isEditable"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'roleGet', - build: ({ role }: { role: Role }) => [new ButtonBuilder() - .setCustomId(`roleGet:${role.id}`) - .setLabel('取得') - .setStyle(ButtonStyle.Success)] - , + customId: "roleGet", + build: ({ role }: { role: Role }) => [ + new ButtonBuilder().setCustomId(`roleGet:${role.id}`).setLabel("取得").setStyle(ButtonStyle.Success), + ], execute: async ({ interaction, args }) => { + const [roleId] = args; + const role = await interaction.guild?.roles.fetch(roleId); + if (!role) return reply(interaction, "ロールが見つかりません"); + if (!isEditable(role)) return reply(interaction, "マダミナリンクより上位のロールは付与できません"); - const [roleId] = args - const role = await interaction.guild?.roles.fetch(roleId) - - if (!role) return reply(interaction, 'ロールが見つかりません') - if (!isEditable(role)) return reply(interaction, 'マダミナリンクより上位のロールは付与できません') - - const member = interaction.member + const member = interaction.member; if (!(member instanceof GuildMember)) return; - await member.roles.add(role) + await member.roles.add(role); - await reply(interaction, `${role}を付与しました`) - } -}) \ No newline at end of file + await reply(interaction, `${role}を付与しました`); + }, +}); diff --git a/src/components/buttons/roleRelease.ts b/src/components/buttons/roleRelease.ts index e4df3b5..071ed55 100644 --- a/src/components/buttons/roleRelease.ts +++ b/src/components/buttons/roleRelease.ts @@ -4,27 +4,23 @@ import { isEditable } from "../../utils/isEditable"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'roleRelease', - build: ({ role }: { role: Role }) => [new ButtonBuilder() - .setCustomId(`roleRelease:${role.id}`) - .setLabel('解除') - .setStyle(ButtonStyle.Danger)] - , + customId: "roleRelease", + build: ({ role }: { role: Role }) => [ + new ButtonBuilder().setCustomId(`roleRelease:${role.id}`).setLabel("解除").setStyle(ButtonStyle.Danger), + ], execute: async ({ interaction, args }) => { + const [roleId] = args; + const role = await interaction.guild?.roles.fetch(roleId); + if (!role) return reply(interaction, "ロールが見つかりません"); + if (!isEditable(role)) return reply(interaction, "マダミナリンクより上位のロールは解除できません"); - const [roleId] = args - const role = await interaction.guild?.roles.fetch(roleId) - - if (!role) return reply(interaction, 'ロールが見つかりません') - if (!isEditable(role)) return reply(interaction, 'マダミナリンクより上位のロールは解除できません') - - const member = interaction.member + const member = interaction.member; if (!(member instanceof GuildMember)) return; - await member.roles.remove(role) + await member.roles.remove(role); - await reply(interaction, `${role}を解除しました`) - } -}) \ No newline at end of file + await reply(interaction, `${role}を解除しました`); + }, +}); diff --git a/src/components/buttons/roleRemove.ts b/src/components/buttons/roleRemove.ts index d365dc6..193ca80 100644 --- a/src/components/buttons/roleRemove.ts +++ b/src/components/buttons/roleRemove.ts @@ -3,25 +3,26 @@ import { Button } from "../../structures/Button"; import { reply } from "../../utils/Reply"; export default new Button({ - customId: 'roleRemove', - build: ({ role }: { role: Role }) => [new ButtonBuilder() - .setCustomId(`roleRemove:${role.id}`) - .setLabel(`「@${role.name}」を解除`) - .setStyle(ButtonStyle.Danger)] - , + customId: "roleRemove", + build: ({ role }: { role: Role }) => [ + new ButtonBuilder() + .setCustomId(`roleRemove:${role.id}`) + .setLabel(`「@${role.name}」を解除`) + .setStyle(ButtonStyle.Danger), + ], execute: async ({ interaction, args }) => { - const [roleId] = args - const role = await interaction.guild?.roles.fetch(roleId) + const [roleId] = args; + const role = await interaction.guild?.roles.fetch(roleId); - if (!role) return reply(interaction, 'ロールが見つかりません') - await interaction.deferReply({ ephemeral: true }) + if (!role) return reply(interaction, "ロールが見つかりません"); + await interaction.deferReply({ ephemeral: true }); - await interaction.guild?.members.fetch() + await interaction.guild?.members.fetch(); - const { members } = role - if (members.size === 0) return reply(interaction, `${role}を持つメンバーがいません`) + const { members } = role; + if (members.size === 0) return reply(interaction, `${role}を持つメンバーがいません`); - await Promise.all(members.map(async member => await member.roles.remove(role))) - await reply(interaction, `${[...members.values()].join(', ')}から${role}を解除しました`) + await Promise.all(members.map(async member => await member.roles.remove(role))); + await reply(interaction, `${[...members.values()].join(", ")}から${role}を解除しました`); }, -}) \ No newline at end of file +}); diff --git a/src/components/buttons/select.ts b/src/components/buttons/select.ts index 592d574..0a7d5eb 100644 --- a/src/components/buttons/select.ts +++ b/src/components/buttons/select.ts @@ -1,19 +1,23 @@ -import { ButtonBuilder, ButtonStyle, Guild, GuildChannel, Role, User } from "discord.js"; +import { ButtonBuilder, ButtonStyle, Guild, Role } from "discord.js"; import { Button } from "../../structures/Button"; export default new Button({ - customId: 'select', - build: ({ choices }: { choices: (string | Role)[], guild: Guild }) => + customId: "select", + build: ({ choices }: { choices: (string | Role)[]; guild: Guild }) => choices.map((choice, index) => { - if (choice instanceof Role) return new ButtonBuilder() - .setCustomId(`select:${index},${choice.name},${choice.id}`) - .setLabel(choice.name) - .setStyle(ButtonStyle.Primary) - return new ButtonBuilder() - .setCustomId(`select:${index},${choice}`) - .setLabel(choice) - .setStyle(ButtonStyle.Primary) - }) - , - execute: async () => {/* Collectorで処理するのでこちらからは何もしない */ }, -}) \ No newline at end of file + if (choice instanceof Role) + return new ButtonBuilder() + .setCustomId(`select:${index},${choice.name},${choice.id}`) + .setLabel(choice.name) + .setStyle(ButtonStyle.Primary); + else { + return new ButtonBuilder() + .setCustomId(`select:${index},${choice}`) + .setLabel(choice) + .setStyle(ButtonStyle.Primary); + } + }), + execute: async () => { + /* Collectorで処理するのでこちらからは何もしない */ + }, +}); diff --git a/src/components/buttons/selectAgregate.ts b/src/components/buttons/selectAgregate.ts index d39e7de..b2d537d 100644 --- a/src/components/buttons/selectAgregate.ts +++ b/src/components/buttons/selectAgregate.ts @@ -1,13 +1,10 @@ -import { ButtonBuilder, ButtonStyle, Guild, GuildChannel, Role, User } from "discord.js"; +import { ButtonBuilder, ButtonStyle } from "discord.js"; import { Button } from "../../structures/Button"; export default new Button({ - customId: 'selectAgregate', - build: () => [new ButtonBuilder() - .setCustomId(`selectAgregate`) - .setLabel('集計') - .setStyle(ButtonStyle.Danger)] - , - - execute: async () => {/* Collectorで処理するのでこちらからは何もしない */ }, -}) \ No newline at end of file + customId: "selectAgregate", + build: () => [new ButtonBuilder().setCustomId(`selectAgregate`).setLabel("集計").setStyle(ButtonStyle.Danger)], + execute: async () => { + /* Collectorで処理するのでこちらからは何もしない */ + }, +}); diff --git a/src/components/buttons/transfer.ts b/src/components/buttons/transfer.ts index 058e200..ebd1795 100644 --- a/src/components/buttons/transfer.ts +++ b/src/components/buttons/transfer.ts @@ -1,42 +1,54 @@ -import { ButtonBuilder, ButtonStyle, TextChannel, GuildTextBasedChannel, NewsChannel, VoiceChannel, TextBasedChannel } from "discord.js"; +import { + ButtonBuilder, + ButtonStyle, + TextChannel, + GuildTextBasedChannel, + NewsChannel, + VoiceChannel, + TextBasedChannel, +} from "discord.js"; import { Button } from "../../structures/Button"; import { fetchAllMessages } from "../../utils/FetchAllMessages"; import { reply } from "../../utils/Reply"; import { transferMessage } from "../../utils/transferMessage"; export default new Button({ - customId: 'transfer', - build: ({ destination }: { destination: GuildTextBasedChannel }) => [new ButtonBuilder() - .setCustomId(`transfer:${destination.id}`) - .setLabel(`「#${destination.name}」へ転送`) - .setStyle(ButtonStyle.Primary)] - , + customId: "transfer", + build: ({ destination }: { destination: GuildTextBasedChannel }) => [ + new ButtonBuilder() + .setCustomId(`transfer:${destination.id}`) + .setLabel(`「#${destination.name}」へ転送`) + .setStyle(ButtonStyle.Primary), + ], execute: async ({ interaction, args }) => { - await interaction.deferReply({ ephemeral: true }) + await interaction.deferReply({ ephemeral: true }); - const destinations: GuildTextBasedChannel[] = args.map(id => interaction.guild?.channels.cache.get(id)) - .filter((channel): channel is NewsChannel | TextChannel | VoiceChannel => channel?.isTextBased() ?? false) - interaction.channel?.messages.cache.clear() - const reactions = (await interaction.message.fetch()).reactions.cache + const destinations: GuildTextBasedChannel[] = args + .map(id => interaction.guild?.channels.cache.get(id)) + .filter((channel): channel is NewsChannel | TextChannel | VoiceChannel => channel?.isTextBased() ?? false); + interaction.channel?.messages.cache.clear(); + const reactions = (await interaction.message.fetch()).reactions.cache; const messages = (await fetchAllMessages(interaction.channel as TextBasedChannel)).reverse().filter(message => { - const customId = message.components[0]?.components[0]?.customId - return !customId?.startsWith('transfer') //転送用のボタンが付いたメッセージは無視 - }) + const customId = message.components[0]?.components[0]?.customId; + return !customId?.startsWith("transfer"); //転送用のボタンが付いたメッセージは無視 + }); - if (!messages) return + if (!messages) return; - let count = 0 + let count = 0; for await (const message of messages.values()) { - const keys = message.reactions.cache.keys() + const keys = message.reactions.cache.keys(); if (reactions.hasAny(...keys)) { - await Promise.all(destinations.map(async destination => { - await transferMessage(message, destination, { noReaction: true }) - })) - count++ + await Promise.all( + destinations.map(async destination => { + await transferMessage(message, destination, { noReaction: true }); + }) + ); + count++; } } - if (count > 0) await reply(interaction, `${count}件のメッセージを転送しました`) - else await reply(interaction, '転送するメッセージがありませんでした') + if (count > 0) await reply(interaction, `${count}件のメッセージを転送しました`); + else await reply(interaction, "転送するメッセージがありませんでした"); }, -}) \ No newline at end of file +}); diff --git a/src/components/buttons/unehemeral.ts b/src/components/buttons/unehemeral.ts index 5dfef1a..9ce7464 100644 --- a/src/components/buttons/unehemeral.ts +++ b/src/components/buttons/unehemeral.ts @@ -1,17 +1,12 @@ -import { ButtonBuilder, ButtonStyle, Guild, GuildChannel, Role, User } from "discord.js"; +import { ButtonBuilder, ButtonStyle } from "discord.js"; import { Button } from "../../structures/Button"; export default new Button({ - customId: 'unehemeral', - build: () => [new ButtonBuilder() - .setCustomId(`unehemeral`) - .setLabel("公開") - .setStyle(ButtonStyle.Primary) - ], + customId: "unehemeral", + build: () => [new ButtonBuilder().setCustomId(`unehemeral`).setLabel("公開").setStyle(ButtonStyle.Primary)], execute: async ({ interaction }) => { - const { content, embeds, components } = interaction.message - console.log(components[0].toJSON()) - await interaction.update({ content, embeds, components: [] }) - await interaction.channel?.send({ content, embeds }) + const { content, embeds } = interaction.message; + await interaction.update({ content, embeds, components: [] }); + await interaction.channel?.send({ content, embeds }); }, -}) \ No newline at end of file +}); diff --git a/src/components/modal/editModal.ts b/src/components/modal/editModal.ts index 6062d29..8877848 100644 --- a/src/components/modal/editModal.ts +++ b/src/components/modal/editModal.ts @@ -1,40 +1,47 @@ -import { ActionRowBuilder, ModalActionRowComponentBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js" -import { Modal } from "../../structures/Modal" -import { reply } from "../../utils/Reply" - +import { + ActionRowBuilder, + ModalActionRowComponentBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; +import { Modal } from "../../structures/Modal"; +import { reply } from "../../utils/Reply"; export default new Modal({ - customId: 'edit', - build: ({ message }) => new ModalBuilder() - .setCustomId(`edit:${message.id}`) - .setTitle('メッセージを編集') - .addComponents( - new ActionRowBuilder().addComponents(((): TextInputBuilder => { - const textInput = new TextInputBuilder() - .setCustomId('content') - .setLabel('編集内容') - .setStyle(TextInputStyle.Paragraph) - .setMinLength(1) - .setMaxLength(2000) - .setPlaceholder('編集後のメッセージを入力') - .setRequired(true) + customId: "edit", + build: ({ message }) => + new ModalBuilder() + .setCustomId(`edit:${message.id}`) + .setTitle("メッセージを編集") + .addComponents( + new ActionRowBuilder().addComponents( + ((): TextInputBuilder => { + const textInput = new TextInputBuilder() + .setCustomId("content") + .setLabel("編集内容") + .setStyle(TextInputStyle.Paragraph) + .setMinLength(1) + .setMaxLength(2000) + .setPlaceholder("編集後のメッセージを入力") + .setRequired(true); - if (message.content.length > 0) { - textInput.setValue(message.content) - } + if (message.content.length > 0) { + textInput.setValue(message.content); + } - return textInput - })()) - ) - , + return textInput; + })() + ) + ), execute: async ({ interaction, args }) => { - const [messageId] = args - const message = await interaction.channel?.messages.fetch(messageId) + const [messageId] = args; + const message = await interaction.channel?.messages.fetch(messageId); - await interaction.deferReply({ ephemeral: true }) + await interaction.deferReply({ ephemeral: true }); - await message?.edit(interaction.fields.getTextInputValue('content')) + await message?.edit(interaction.fields.getTextInputValue("content")); - await reply(interaction, '編集が完了しました') + await reply(interaction, "編集が完了しました"); }, -}) +}); diff --git a/src/components/selectmenu/roleList.ts b/src/components/selectmenu/roleList.ts index 7a9ff19..0634302 100644 --- a/src/components/selectmenu/roleList.ts +++ b/src/components/selectmenu/roleList.ts @@ -5,31 +5,32 @@ import { arraySplit } from "../../utils/ArraySplit"; import { reply } from "../../utils/Reply"; export default new SelectMenu({ - customId: 'roleList', - build: (roles: Role[], startIndex) => arraySplit(roles, 25).map((roles, index) => - new ActionRowBuilder() - .addComponents(new StringSelectMenuBuilder() - .setCustomId(`roleList:${startIndex + index}`) - .setPlaceholder(`ロールを選択 (ページ${startIndex + index})`) - .setMinValues(1) - .setMaxValues(roles.length) - .addOptions( - roles.map(role => ({ - label: role.name, - value: role.id - })) - ) + customId: "roleList", + build: (roles: Role[], startIndex) => + arraySplit(roles, 25).map((roles, index) => + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId(`roleList:${startIndex + index}`) + .setPlaceholder(`ロールを選択 (ページ${startIndex + index})`) + .setMinValues(1) + .setMaxValues(roles.length) + .addOptions( + roles.map(role => ({ + label: role.name, + value: role.id, + })) + ) ) - ), + ), execute: async ({ interaction }) => { - await interaction.deferReply({ ephemeral: true }) + await interaction.deferReply({ ephemeral: true }); for await (const id of interaction.values) { - const role = interaction.guild?.roles.cache.get(id) - if (!role) continue - await interaction.channel?.send(buildRoleRow(role)) + const role = interaction.guild?.roles.cache.get(id); + if (!role) continue; + await interaction.channel?.send(buildRoleRow(role)); } - await reply(interaction, 'ボタンを作成しました') + await reply(interaction, "ボタンを作成しました"); }, -}) \ No newline at end of file +}); diff --git a/src/components/selectmenu/transferList.ts b/src/components/selectmenu/transferList.ts index 4e71c02..0dc3589 100644 --- a/src/components/selectmenu/transferList.ts +++ b/src/components/selectmenu/transferList.ts @@ -6,30 +6,31 @@ import { reply } from "../../utils/Reply"; import transferButton from "../buttons/transfer"; export default new SelectMenu({ - customId: 'transferList', - build: (channels: GuildTextBasedChannel[]) => arraySplit(channels, 25).map((channels, index) => - new ActionRowBuilder() - .addComponents(new StringSelectMenuBuilder() - .setCustomId(`transferList:${index}`) - .setPlaceholder(`転送先を選択 (ページ${index + 1})`) - .setMinValues(1) - .setMaxValues(channels.length) - .addOptions( - channels.map(channel => ({ - label: channel.name, - value: channel.id - })) - ) + customId: "transferList", + build: (channels: GuildTextBasedChannel[]) => + arraySplit(channels, 25).map((channels, index) => + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId(`transferList:${index}`) + .setPlaceholder(`転送先を選択 (ページ${index + 1})`) + .setMinValues(1) + .setMaxValues(channels.length) + .addOptions( + channels.map(channel => ({ + label: channel.name, + value: channel.id, + })) + ) ) - ), + ), execute: async ({ interaction }) => { for await (const id of interaction.values) { - const destination = interaction.guild?.channels.cache.get(id) - if (!destination) continue - await reply(interaction, `「${destination}」に転送するメッセージと同じリアクションを付けてください`) + const destination = interaction.guild?.channels.cache.get(id); + if (!destination) continue; + await reply(interaction, `「${destination}」に転送するメッセージと同じリアクションを付けてください`); await interaction.channel?.send({ - components: buttonToRow(transferButton.build({ destination })) - }) + components: buttonToRow(transferButton.build({ destination })), + }); } }, -}) \ No newline at end of file +}); diff --git a/src/components/selectmenu/transferListImmed.ts b/src/components/selectmenu/transferListImmed.ts index 98eb43d..54e18fa 100644 --- a/src/components/selectmenu/transferListImmed.ts +++ b/src/components/selectmenu/transferListImmed.ts @@ -5,35 +5,36 @@ import { reply } from "../../utils/Reply"; import { transferMessage } from "../../utils/transferMessage"; export default new SelectMenu({ - customId: 'transferListImmed', - build: (channels: GuildChannel[], message: Message) => arraySplit(channels, 25).map((channels, index) => - new ActionRowBuilder() - .addComponents(new StringSelectMenuBuilder() - .setCustomId(`transferListImmed:${message.id},${index}`) - .setPlaceholder(`転送先を選択 (ページ${index + 1})`) - .setMinValues(1) - .setMaxValues(channels.length) - .addOptions( - channels.map(channel => ({ - label: channel.name, - value: channel.id - })) - ) + customId: "transferListImmed", + build: (channels: GuildChannel[], message: Message) => + arraySplit(channels, 25).map((channels, index) => + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId(`transferListImmed:${message.id},${index}`) + .setPlaceholder(`転送先を選択 (ページ${index + 1})`) + .setMinValues(1) + .setMaxValues(channels.length) + .addOptions( + channels.map(channel => ({ + label: channel.name, + value: channel.id, + })) + ) ) - ), + ), execute: async ({ interaction, args }) => { - await interaction.deferReply({ ephemeral: true }) + await interaction.deferReply({ ephemeral: true }); - const message = await interaction.channel?.messages.fetch(args[0]) + const message = await interaction.channel?.messages.fetch(args[0]); - if (!message) return + if (!message) return; for await (const id of interaction.values) { - const destination = interaction.guild?.channels.cache.get(id) - if (!destination?.isTextBased()) continue - transferMessage(message, destination) + const destination = interaction.guild?.channels.cache.get(id); + if (!destination?.isTextBased()) continue; + transferMessage(message, destination); } - await reply(interaction, '転送が完了しました') + await reply(interaction, "転送が完了しました"); }, -}) \ No newline at end of file +}); diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index eedcd43..010cc5b 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,52 +1,62 @@ -import { Colors, CommandInteraction, EmbedBuilder, Events, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js" -import { client } from "../bot" -import { Event } from "../structures/Events" -import { reply } from "../utils/Reply" +import { + Colors, + CommandInteraction, + EmbedBuilder, + Events, + MessageComponentInteraction, + ModalSubmitInteraction, +} from "discord.js"; +import { client } from "../bot"; +import { Event } from "../structures/Events"; +import { reply } from "../utils/Reply"; -export default new Event(Events.InteractionCreate, async (interaction) => { - - if (interaction.isAutocomplete()) return +export default new Event(Events.InteractionCreate, async interaction => { + if (interaction.isAutocomplete()) return; try { //スラッシュコマンド if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { - const command = client.commands.get(interaction.commandName) - if (!command) return - await command.execute({ client, interaction, args: interaction.options }) - + const command = client.commands.get(interaction.commandName); + if (!command) return; + await command.execute({ client, interaction, args: interaction.options }); } //コンポーネント else if (interaction.isMessageComponent() || interaction.isModalSubmit()) { - const [customId, ...args] = interaction.customId.split(/[;:,]/) - const component = client.components.get(customId) - if (!component) return - await component.execute({ client, interaction, args }) + const [customId, ...args] = interaction.customId.split(/[;:,]/); + const component = client.components.get(customId); + if (!component) return; + await component.execute({ client, interaction, args }); } } catch (e: any) { - await showError(interaction, e).catch(console.log) + await showError(interaction, e).catch(console.log); } -}) +}); -const showError = async (interaction: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, e: any) => { - console.log(e); - let description = '' +const showError = async ( + interaction: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, + e: any +) => { + let description = ""; switch (e.code) { - case 50013: description = "マダミナリンクに十分な権限がありません\n権限の設定を確認してください\n(マダミナリンクより上位のロールは操作できません)"; break //Missing Permissions - case 30005: description = "ロール数が上限に達しています\nロールを減らしてから再度お試しください"; break //Maximum number of roles reached - case 30013: description = "チャンネル数が上限に達しています\nチャンネルを減らしてから再度お試しください"; break //Maximum number of channels reached - default: return reply(interaction, { - content: 'エラーが発生しました、下記のエラーメッセージを添えて報告して下さい \n 【サポートサーバー】 → https://discord.gg/6by68EJ3e7', - embeds: [new EmbedBuilder() - .setColor(Colors.Red) - .setDescription(e.stack) - ] - }) + case 50013: + description = + "マダミナリンクに十分な権限がありません\n権限の設定を確認してください\n(マダミナリンクより上位のロールは操作できません)"; + break; //Missing Permissions + case 30005: + description = "ロール数が上限に達しています\nロールを減らしてから再度お試しください"; + break; //Maximum number of roles reached + case 30013: + description = "チャンネル数が上限に達しています\nチャンネルを減らしてから再度お試しください"; + break; //Maximum number of channels reached + default: + return reply(interaction, { + content: + "エラーが発生しました、下記のエラーメッセージを添えて報告して下さい \n 【サポートサーバー】 → https://discord.gg/6by68EJ3e7", + embeds: [new EmbedBuilder().setColor(Colors.Red).setDescription(e.stack)], + }); } - console.log(interaction, e) + console.log(interaction, e); return reply(interaction, { - embeds: [new EmbedBuilder() - .setColor(Colors.Red) - .setDescription(description) - ] - }) -} \ No newline at end of file + embeds: [new EmbedBuilder().setColor(Colors.Red).setDescription(description)], + }); +}; diff --git a/src/events/ready.ts b/src/events/ready.ts index ddb46f7..caa2a57 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,6 +1,6 @@ -import { Events } from "discord.js" -import { Event } from "../structures/Events" +import { Events } from "discord.js"; +import { Event } from "../structures/Events"; export default new Event(Events.ClientReady, async () => { - console.log('Ready!') -}) \ No newline at end of file + console.log("Ready!"); +}); diff --git a/src/structures/Button.ts b/src/structures/Button.ts index acb5160..30b412d 100644 --- a/src/structures/Button.ts +++ b/src/structures/Button.ts @@ -1,28 +1,28 @@ -import { ButtonBuilder, ButtonInteraction } from "discord.js" -import { ExtendedClient } from "./Client" -import { Component, ComponentType, RunOptions } from "./Component" +import { ButtonBuilder, ButtonInteraction } from "discord.js"; +import { ExtendedClient } from "./Client"; +import { Component, ComponentType, RunOptions } from "./Component"; -type ButtonBuildFunction = (options?: any) => ButtonBuilder[] +type ButtonBuildFunction = (options?: any) => ButtonBuilder[]; interface ButtonRunOptions extends RunOptions { - client: ExtendedClient, - interaction: ButtonInteraction, - args: string[] + client: ExtendedClient; + interaction: ButtonInteraction; + args: string[]; } interface ButtonType extends ComponentType { - customId: string - build: ButtonBuildFunction - execute: (options: ButtonRunOptions) => Promise + customId: string; + build: ButtonBuildFunction; + execute: (options: ButtonRunOptions) => Promise; } export class Button extends Component implements ButtonType { - build: ButtonBuildFunction + build: ButtonBuildFunction; execute: (options: ButtonRunOptions) => Promise; constructor(button: ButtonType) { - super(button) - this.build = button.build - this.execute = button.execute + super(button); + this.build = button.build; + this.execute = button.execute; } -} \ No newline at end of file +} diff --git a/src/structures/Client.ts b/src/structures/Client.ts index 6179c2e..8b1d06a 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -1,5 +1,5 @@ import { ApplicationCommandDataResolvable, Client, ClientEvents, ClientOptions, Collection } from "discord.js"; -import 'dotenv/config' +import "dotenv/config"; import * as path from "path"; import * as fs from "fs"; import { Event } from "./Events"; @@ -7,73 +7,72 @@ import { Component } from "./Component"; import { Command } from "./Command"; export class ExtendedClient extends Client { - - commands: Collection = new Collection() //コマンド名で参照 - components: Collection = new Collection() //customIdで参照 + commands: Collection = new Collection(); //コマンド名で参照 + components: Collection = new Collection(); //customIdで参照 constructor(option: ClientOptions) { - super(option) + super(option); } start() { - this.registerModules() - this.login(process.env.TOKEN) + this.registerModules(); + this.login(process.env.TOKEN); } async importfile(filePath: string) { - return (await import(filePath))?.default + return (await import(filePath))?.default; } async registerModules() { - const main = path.dirname(require.main?.filename ?? __filename) - const commandsPath = path.join(main, 'commands') + const main = path.dirname(require.main?.filename ?? __filename); + const commandsPath = path.join(main, "commands"); - const commandsDirs = fs.readdirSync(commandsPath) - const commandDatas: ApplicationCommandDataResolvable[] = [] - const commandDatasDev: ApplicationCommandDataResolvable[] = [] + const commandsDirs = fs.readdirSync(commandsPath); + const commandDatas: ApplicationCommandDataResolvable[] = []; + const commandDatasDev: ApplicationCommandDataResolvable[] = []; commandsDirs.forEach(async (dir: string) => { - const dirPath = path.join(commandsPath, dir) - const commandFiles = fs.readdirSync(dirPath).filter((file: string) => file.endsWith('.ts')) + const dirPath = path.join(commandsPath, dir); + const commandFiles = fs.readdirSync(dirPath).filter((file: string) => file.endsWith(".ts")); commandFiles.forEach(async (file: string) => { - const filePath = path.join(dirPath, file) - const command: Command = await this.importfile(filePath) - this.commands.set(command.data.name, command) + const filePath = path.join(dirPath, file); + const command: Command = await this.importfile(filePath); + this.commands.set(command.data.name, command); if (command.dev) { - commandDatasDev.push(command.data.toJSON()) + commandDatasDev.push(command.data.toJSON()); } else { - commandDatas.push(command.data.toJSON()) + commandDatas.push(command.data.toJSON()); } - }) - }) + }); + }); - this.on('ready', () => { - this.application?.commands.set(commandDatas) + this.on("ready", () => { + this.application?.commands.set(commandDatas); if (process.env.DEV_SERVER_ID) { - this.application?.commands.set(commandDatasDev, process.env.DEV_SERVER_ID) + this.application?.commands.set(commandDatasDev, process.env.DEV_SERVER_ID); } - }) + }); - const eventsPath = path.join(main, 'events') - const eventFiles = fs.readdirSync(eventsPath).filter((file: string) => file.endsWith('.ts')) + const eventsPath = path.join(main, "events"); + const eventFiles = fs.readdirSync(eventsPath).filter((file: string) => file.endsWith(".ts")); eventFiles.forEach(async (file: string) => { - const filePath = path.join(eventsPath, file) - const event: Event = await this.importfile(filePath) - this.on(event.name, event.execute) - }) + const filePath = path.join(eventsPath, file); + const event: Event = await this.importfile(filePath); + this.on(event.name, event.execute); + }); - const componentsPath = path.join(main, 'components') - const componentsDirs = fs.readdirSync(componentsPath) + const componentsPath = path.join(main, "components"); + const componentsDirs = fs.readdirSync(componentsPath); componentsDirs.forEach(async (directory: string) => { - const dirPath = path.join(componentsPath, directory) - const componentFiles = fs.readdirSync(dirPath).filter((file: string) => file.endsWith('.ts')) + const dirPath = path.join(componentsPath, directory); + const componentFiles = fs.readdirSync(dirPath).filter((file: string) => file.endsWith(".ts")); componentFiles.forEach(async (file: string) => { - const filePath = path.join(dirPath, file) - const component: Component = await this.importfile(filePath) - this.components.set(component.customId, component) - }) - }) + const filePath = path.join(dirPath, file); + const component: Component = await this.importfile(filePath); + this.components.set(component.customId, component); + }); + }); } -} \ No newline at end of file +} diff --git a/src/structures/Command.ts b/src/structures/Command.ts index 88c98f5..b1feb75 100644 --- a/src/structures/Command.ts +++ b/src/structures/Command.ts @@ -1,30 +1,28 @@ -import { CommandInteractionOptionResolver, CommandInteraction } from "discord.js" -import { ExtendedClient } from "./Client" - +import { CommandInteractionOptionResolver, CommandInteraction } from "discord.js"; +import { ExtendedClient } from "./Client"; export interface RunOptions { - client: ExtendedClient, - interaction: CommandInteraction, - args: CommandInteractionOptionResolver + client: ExtendedClient; + interaction: CommandInteraction; + args: CommandInteractionOptionResolver; } -type RunFunction = (options: any) => Promise +type RunFunction = (options: any) => Promise; interface CommandType { - data: any, - execute: RunFunction - dev?: boolean + data: any; + execute: RunFunction; + dev?: boolean; } export class Command implements CommandType { - - data: any - execute: RunFunction - dev?: boolean | undefined + data: any; + execute: RunFunction; + dev?: boolean | undefined; constructor(command: CommandType) { - this.data = command.data - this.execute = command.execute - this.dev = command.dev + this.data = command.data; + this.execute = command.execute; + this.dev = command.dev; } -} \ No newline at end of file +} diff --git a/src/structures/Component.ts b/src/structures/Component.ts index f78bfd6..1f89a8b 100644 --- a/src/structures/Component.ts +++ b/src/structures/Component.ts @@ -1,28 +1,28 @@ -import { ComponentBuilder, MessageComponentInteraction, ModalBuilder, ModalSubmitInteraction } from "discord.js" -import { ExtendedClient } from "./Client" +import { ComponentBuilder, MessageComponentInteraction, ModalBuilder, ModalSubmitInteraction } from "discord.js"; +import { ExtendedClient } from "./Client"; -type ComponentBuildFunction = (options?: any) => ComponentBuilder[] | ModalBuilder +type ComponentBuildFunction = (options?: any) => ComponentBuilder[] | ModalBuilder; export interface RunOptions { - client: ExtendedClient, - interaction: MessageComponentInteraction | ModalSubmitInteraction, - args: string[] + client: ExtendedClient; + interaction: MessageComponentInteraction | ModalSubmitInteraction; + args: string[]; } export interface ComponentType { - customId: string - build: ComponentBuildFunction - execute: (options: any) => Promise + customId: string; + build: ComponentBuildFunction; + execute: (options: any) => Promise; } export class Component implements ComponentType { - customId: string - build: ComponentBuildFunction + customId: string; + build: ComponentBuildFunction; execute: (options: any) => Promise; constructor(component: ComponentType) { - this.customId = component.customId - this.build = component.build - this.execute = component.execute + this.customId = component.customId; + this.build = component.build; + this.execute = component.execute; } -} \ No newline at end of file +} diff --git a/src/structures/ContextMenu.ts b/src/structures/ContextMenu.ts index 2ad520d..45682f6 100644 --- a/src/structures/ContextMenu.ts +++ b/src/structures/ContextMenu.ts @@ -1,31 +1,29 @@ -import { CommandInteractionOptionResolver, ContextMenuCommandInteraction, ContextMenuCommandBuilder } from "discord.js" -import { ExtendedClient } from "./Client" -import { Command, RunOptions } from "./Command" - +import { CommandInteractionOptionResolver, ContextMenuCommandInteraction, ContextMenuCommandBuilder } from "discord.js"; +import { ExtendedClient } from "./Client"; +import { Command, RunOptions } from "./Command"; interface ContextMenuRunOptions extends RunOptions { - client: ExtendedClient, - interaction: ContextMenuCommandInteraction, - args: CommandInteractionOptionResolver + client: ExtendedClient; + interaction: ContextMenuCommandInteraction; + args: CommandInteractionOptionResolver; } -type RunFunction = (options: ContextMenuRunOptions) => Promise +type RunFunction = (options: ContextMenuRunOptions) => Promise; interface ContextMenuType { - data: ContextMenuCommandBuilder, - execute: RunFunction - dev?: boolean | undefined + data: ContextMenuCommandBuilder; + execute: RunFunction; + dev?: boolean | undefined; } export class ContextMenu extends Command implements ContextMenuType { - - data: ContextMenuCommandBuilder - execute: RunFunction + data: ContextMenuCommandBuilder; + execute: RunFunction; constructor(command: ContextMenuType) { - super(command) - this.data = command.data - this.execute = command.execute - this.dev = command.dev + super(command); + this.data = command.data; + this.execute = command.execute; + this.dev = command.dev; } -} \ No newline at end of file +} diff --git a/src/structures/Events.ts b/src/structures/Events.ts index 7f6552b..6a63287 100644 --- a/src/structures/Events.ts +++ b/src/structures/Events.ts @@ -1,8 +1,8 @@ -import { ClientEvents } from "discord.js" +import { ClientEvents } from "discord.js"; -export class Event{ +export class Event { constructor( public name: key, public execute: (...args: ClientEvents[key]) => Promise - ) { } -} \ No newline at end of file + ) {} +} diff --git a/src/structures/Modal.ts b/src/structures/Modal.ts index 13e45e5..8a212cf 100644 --- a/src/structures/Modal.ts +++ b/src/structures/Modal.ts @@ -1,28 +1,28 @@ -import { ModalBuilder, ModalSubmitInteraction } from "discord.js" -import { ExtendedClient } from "./Client" -import { Component, ComponentType, RunOptions } from "./Component" +import { ModalBuilder, ModalSubmitInteraction } from "discord.js"; +import { ExtendedClient } from "./Client"; +import { Component, ComponentType, RunOptions } from "./Component"; -type ModalBuildFunction = (options: any) => ModalBuilder +type ModalBuildFunction = (options: any) => ModalBuilder; interface ModalRunOptions extends RunOptions { - client: ExtendedClient, - interaction: ModalSubmitInteraction, - args: string[] + client: ExtendedClient; + interaction: ModalSubmitInteraction; + args: string[]; } interface ModalType extends ComponentType { - customId: string - build: ModalBuildFunction - execute: (options: ModalRunOptions) => Promise + customId: string; + build: ModalBuildFunction; + execute: (options: ModalRunOptions) => Promise; } export class Modal extends Component implements ModalType { - build: ModalBuildFunction + build: ModalBuildFunction; execute: (options: ModalRunOptions) => Promise; constructor(modal: ModalType) { - super(modal) - this.build = modal.build - this.execute = modal.execute + super(modal); + this.build = modal.build; + this.execute = modal.execute; } -} \ No newline at end of file +} diff --git a/src/structures/SelectMenu.ts b/src/structures/SelectMenu.ts index a09e8bf..c9c0633 100644 --- a/src/structures/SelectMenu.ts +++ b/src/structures/SelectMenu.ts @@ -1,28 +1,28 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, SelectMenuBuilder, SelectMenuInteraction } from "discord.js" -import { ExtendedClient } from "./Client" -import { Component, ComponentType, RunOptions } from "./Component" +import { ActionRowBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction } from "discord.js"; +import { ExtendedClient } from "./Client"; +import { Component, ComponentType, RunOptions } from "./Component"; -type SelectMenuBuildFunction = (...options: any) => ActionRowBuilder[] +type SelectMenuBuildFunction = (...options: any) => ActionRowBuilder[]; interface SelectMenuRunOptions extends RunOptions { - client: ExtendedClient, - interaction: SelectMenuInteraction, - args: string[] + client: ExtendedClient; + interaction: StringSelectMenuInteraction; + args: string[]; } interface SelectMenuType extends ComponentType { - customId: string - build: SelectMenuBuildFunction - execute: (options: SelectMenuRunOptions) => Promise + customId: string; + build: SelectMenuBuildFunction; + execute: (options: SelectMenuRunOptions) => Promise; } export class SelectMenu extends Component implements SelectMenuType { - build: SelectMenuBuildFunction + build: SelectMenuBuildFunction; execute: (options: SelectMenuRunOptions) => Promise; constructor(selectMenu: SelectMenuType) { - super(selectMenu) - this.build = selectMenu.build - this.execute = selectMenu.execute + super(selectMenu); + this.build = selectMenu.build; + this.execute = selectMenu.execute; } -} \ No newline at end of file +} diff --git a/src/structures/SlashCommand.ts b/src/structures/SlashCommand.ts index bfe9f0e..cf20f1c 100644 --- a/src/structures/SlashCommand.ts +++ b/src/structures/SlashCommand.ts @@ -1,32 +1,30 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder, CommandInteractionOptionResolver } from "discord.js" -import { ExtendedClient } from "./Client" -import { Command, RunOptions } from "./Command" - +import { ChatInputCommandInteraction, SlashCommandBuilder, CommandInteractionOptionResolver } from "discord.js"; +import { ExtendedClient } from "./Client"; +import { Command, RunOptions } from "./Command"; interface SlashCommandRunOptions extends RunOptions { - client: ExtendedClient, - interaction: ChatInputCommandInteraction, - args: CommandInteractionOptionResolver + client: ExtendedClient; + interaction: ChatInputCommandInteraction; + args: CommandInteractionOptionResolver; } -type RunFunction = (options: SlashCommandRunOptions) => Promise +type RunFunction = (options: SlashCommandRunOptions) => Promise; interface SlashCommandType { - data: SlashCommandBuilder, - execute: RunFunction - dev?: boolean + data: SlashCommandBuilder; + execute: RunFunction; + dev?: boolean; } export class SlashCommand extends Command implements SlashCommandType { - - data: SlashCommandBuilder - execute: RunFunction - dev?: boolean | undefined + data: SlashCommandBuilder; + execute: RunFunction; + dev?: boolean | undefined; constructor(command: SlashCommandType) { - super(command) - this.data = command.data - this.execute = command.execute - this.dev = command.dev + super(command); + this.data = command.data; + this.execute = command.execute; + this.dev = command.dev; } -} \ No newline at end of file +} diff --git a/src/utils/ArraySplit.ts b/src/utils/ArraySplit.ts index 3932557..601a2ae 100644 --- a/src/utils/ArraySplit.ts +++ b/src/utils/ArraySplit.ts @@ -1,3 +1,3 @@ export const arraySplit = (array: T[], n: number): T[][] => { return array.reduce((acc: T[][], _, i: number) => (i % n ? acc : [...acc, ...[array.slice(i, i + n)]]), []); -} \ No newline at end of file +}; diff --git a/src/utils/ButtonToRow.ts b/src/utils/ButtonToRow.ts index dc7016f..9d11c73 100644 --- a/src/utils/ButtonToRow.ts +++ b/src/utils/ButtonToRow.ts @@ -4,8 +4,9 @@ import { arraySplit } from "./ArraySplit"; export const buttonToRow = (buttons: ButtonBuilder[]): ActionRowBuilder[] => { return arraySplit(buttons, 5).map(buttons => { const row = new ActionRowBuilder(); - buttons.map(button => { row.addComponents(button) }); + buttons.map(button => { + row.addComponents(button); + }); return row; - }) - -} \ No newline at end of file + }); +}; diff --git a/src/utils/DeleteMultiMessages.ts b/src/utils/DeleteMultiMessages.ts index 9ce6f52..c181df0 100644 --- a/src/utils/DeleteMultiMessages.ts +++ b/src/utils/DeleteMultiMessages.ts @@ -1,31 +1,43 @@ -import { AnyThreadChannel, Collection, DMChannel, GuildTextBasedChannel, Message, PartialDMChannel, ThreadChannel } from "discord.js"; +import { AnyThreadChannel, Collection, DMChannel, GuildTextBasedChannel, Message, PartialDMChannel } from "discord.js"; import { arraySplit } from "./ArraySplit"; -export const deleteMultiMessages = async (channel: GuildTextBasedChannel | DMChannel | PartialDMChannel, messages: Collection>) => { +export const deleteMultiMessages = async ( + channel: GuildTextBasedChannel | DMChannel | PartialDMChannel, + messages: Collection> +) => { + const threads = messages.filter(m => m.hasThread).map(m => m.thread as AnyThreadChannel); + const [recentMessages, oldMessages] = messages.partition( + message => Date.now() - message.createdTimestamp < 1_209_600_000 + ); - const threads = messages.filter(m => m.hasThread).map(m => m.thread as AnyThreadChannel) - const [recentMessages, oldMessages] = messages.partition(message => (Date.now() - message.createdTimestamp) < 1_209_600_000); - - if (!('bulkDelete' in channel)) { + if (!("bulkDelete" in channel)) { //bulkDeleteがない場合は一つずつ削除 - await Promise.all(messages.map(async message => { - await message.delete(); - })); - return + await Promise.all( + messages.map(async message => { + await message.delete(); + }) + ); + return; } //bulkDeleteで100件ずつ削除 - await Promise.all(arraySplit([...recentMessages.values()], 100).map(async messagesSliced => { - await channel.bulkDelete(messagesSliced) - })) + await Promise.all( + arraySplit([...recentMessages.values()], 100).map(async messagesSliced => { + await channel.bulkDelete(messagesSliced); + }) + ); //2週間以上前のメッセージを順番に削除(遅い) - await Promise.all(oldMessages.map(async message => { - await message.delete(); - })); + await Promise.all( + oldMessages.map(async message => { + await message.delete(); + }) + ); //メッセージに付属するスレッドも削除 - await Promise.all(threads.map(async thread => { - await thread.delete() - })) -} \ No newline at end of file + await Promise.all( + threads.map(async thread => { + await thread.delete(); + }) + ); +}; diff --git a/src/utils/FetchAllMessages.ts b/src/utils/FetchAllMessages.ts index e1abc23..6736eb1 100644 --- a/src/utils/FetchAllMessages.ts +++ b/src/utils/FetchAllMessages.ts @@ -1,56 +1,29 @@ -import { Collection, Message, Snowflake, TextBasedChannel } from "discord.js" +import { Collection, Message, Snowflake, TextBasedChannel } from "discord.js"; type fetchOptions = { - before?: Snowflake, - after?: Snowflake, -} + before?: Snowflake; + after?: Snowflake; +}; export const fetchAllMessages = async (channel: TextBasedChannel, { before, after }: fetchOptions = {}) => { - const border = before ?? after ?? undefined - let messages: Collection> = new Collection(border ? [[border, await channel.messages.fetch(border)]] : undefined) + const border = before ?? after ?? undefined; + let messages: Collection> = new Collection( + border ? [[border, await channel.messages.fetch(border)]] : undefined + ); while (true) { - channel.messages.cache.clear() + channel.messages.cache.clear(); //afterが指定されている場合は、beforeが無視される - const fetched: Collection = await channel.messages.fetch({ limit: 100, before, after }) - if (fetched.size == 0) break + const fetched: Collection = await channel.messages.fetch({ limit: 100, before, after }); + if (fetched.size == 0) break; if (after) { - messages = fetched.concat(messages) - after = fetched.first()?.id + messages = fetched.concat(messages); + after = fetched.first()?.id; } else { - messages = messages.concat(fetched) - before = fetched.last()?.id + messages = messages.concat(fetched); + before = fetched.last()?.id; } } - return messages -} -/* -export const fetchBeforeMessages = async (channel: TextBasedChannel, before?: Message) => { - let messages = new Collection(before ? [[before.id, before]] : undefined) - let lastId: string | undefined = before?.id - while (true) { - channel.messages.cache.clear() - const fetched: Collection = await channel.messages.fetch({ limit: 100, before: lastId }) - if (fetched.size == 0) break - messages = messages.concat(fetched) - lastId = fetched.last()?.id - } - return messages -} - - -export const fetchAfterMessages = async (channel: TextBasedChannel, after?: Message) => { - let messages = new Collection(after ? [[after.id, after]] : undefined) - let lastId: string | undefined = after?.id - while (true) { - channel.messages.cache.clear() - const fetched: Collection = await channel.messages.fetch({ limit: 100, after: lastId }) - if (fetched.size == 0) break - messages = fetched.concat(messages) - lastId = fetched.first()?.id - } - return messages -} - - */ \ No newline at end of file + return messages; +}; diff --git a/src/utils/Reply.ts b/src/utils/Reply.ts index 867e732..66d2341 100644 --- a/src/utils/Reply.ts +++ b/src/utils/Reply.ts @@ -1,20 +1,28 @@ -import { CommandInteraction, InteractionReplyOptions, InteractionResponse, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; - -export async function reply(interaction: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, options: InteractionReplyOptions | string): Promise { +import { + CommandInteraction, + InteractionReplyOptions, + InteractionResponse, + Message, + MessageComponentInteraction, + ModalSubmitInteraction, +} from "discord.js"; +export async function reply( + interaction: CommandInteraction | MessageComponentInteraction | ModalSubmitInteraction, + options: InteractionReplyOptions | string +): Promise { try { - if (typeof options === 'string') { - options = { content: options } + if (typeof options === "string") { + options = { content: options }; } - options.ephemeral = options.ephemeral ?? true + options.ephemeral = options.ephemeral ?? true; if (interaction.replied || interaction.deferred) { - return interaction.followUp(options) + return interaction.followUp(options); } else { - return interaction.reply(options) + return interaction.reply(options); } - } catch (e) { - return + return; } -} \ No newline at end of file +} diff --git a/src/utils/SplitMessage.ts b/src/utils/SplitMessage.ts index bdc132e..448a685 100644 --- a/src/utils/SplitMessage.ts +++ b/src/utils/SplitMessage.ts @@ -1,23 +1,23 @@ export const splitMessage = (text: string, { maxLength = 2000, delimeter = "\n" } = {}): string[] => { - if (text.length <= maxLength) return [text] + if (text.length <= maxLength) return [text]; - const splitText = text.split(delimeter).map(t => t == "" ? delimeter : t) + const splitText = text.split(delimeter).map(t => (t == "" ? delimeter : t)); //delimeterでsplitしてもなおmaxLengthを超えたら無理やり分ける - splitText.flatMap(t => t.match(new RegExp(`.{1,${maxLength}}`, "g"))) + splitText.flatMap(t => t.match(new RegExp(`.{1,${maxLength}}`, "g"))); - const messages: string[] = [] + const messages: string[] = []; - let msg = "" + let msg = ""; for (const chunk of splitText) { if ((msg + chunk).length > maxLength) { messages.push(msg); - msg = "" + msg = ""; } msg += chunk; } - messages.push(msg) + messages.push(msg); //""を除外 - return messages.filter(m => m) -} \ No newline at end of file + return messages.filter(m => m); +}; diff --git a/src/utils/isCategory.ts b/src/utils/isCategory.ts index 2ad0e4d..3816a03 100644 --- a/src/utils/isCategory.ts +++ b/src/utils/isCategory.ts @@ -1,5 +1,5 @@ -import { BaseChannel, CategoryChannel, ChannelType } from "discord.js" +import { BaseChannel, CategoryChannel, ChannelType } from "discord.js"; export const isCategory = (channel: BaseChannel): channel is CategoryChannel => { - return channel.type == ChannelType.GuildCategory -} \ No newline at end of file + return channel.type == ChannelType.GuildCategory; +}; diff --git a/src/utils/isEditable.ts b/src/utils/isEditable.ts index cf3f4a1..f1deb1f 100644 --- a/src/utils/isEditable.ts +++ b/src/utils/isEditable.ts @@ -2,5 +2,5 @@ import { APIRole, Role } from "discord.js"; export const isEditable = (role: Role | APIRole | null): boolean => { if (!role) return true; - return ('editable' in role) && role.editable -} \ No newline at end of file + return "editable" in role && role.editable; +}; diff --git a/src/utils/transferMessage.ts b/src/utils/transferMessage.ts index 6e6210c..7f6b693 100644 --- a/src/utils/transferMessage.ts +++ b/src/utils/transferMessage.ts @@ -1,4 +1,17 @@ -import { ActionRow, ActionRowBuilder, Attachment, ButtonBuilder, GuildChannel, GuildTextBasedChannel, Message, MessageActionRowComponent, MessageMentionOptions, MessageType, PrivateThreadChannel, PublicThreadChannel } from "discord.js"; +import { + ActionRow, + ActionRowBuilder, + Attachment, + ButtonBuilder, + GuildChannel, + GuildTextBasedChannel, + Message, + MessageActionRowComponent, + MessageMentionOptions, + MessageType, + PrivateThreadChannel, + PublicThreadChannel, +} from "discord.js"; import { fetchAllMessages } from "./FetchAllMessages"; import { splitMessage } from "./SplitMessage"; import { buttonToRow } from "../utils/ButtonToRow"; @@ -6,59 +19,60 @@ import transferButton from "../components/buttons/transfer"; import { openMessage } from "../commands/slashcommands/open"; import { client } from "../bot"; - type transferOptions = { - noReaction?: boolean, - allowedMentions?: MessageMentionOptions, - updates?: { [key: string]: GuildChannel } //チャンネルリンク差し替え用 -} - -export const transferMessage = async (message: Message, destination: GuildTextBasedChannel, options?: transferOptions) => { - + noReaction?: boolean; + allowedMentions?: MessageMentionOptions; + updates?: { [key: string]: GuildChannel }; //チャンネルリンク差し替え用 +}; + +export const transferMessage = async ( + message: Message, + destination: GuildTextBasedChannel, + options?: transferOptions +) => { await destination.sendTyping(); - let contentAll = message.content - const { allowedMentions, updates } = options ?? {} + let contentAll = message.content; + const { allowedMentions, updates } = options ?? {}; if (updates) { - contentAll = replaceChannelLinks(contentAll, updates) + contentAll = replaceChannelLinks(contentAll, updates); } //2000文字を超えないように分割 - const contentSplit: string[] = splitMessage(contentAll) + const contentSplit: string[] = splitMessage(contentAll); //最後の1チャンクだけ取り出して残りは先に送る - let content = contentSplit.pop() + let content = contentSplit.pop(); for await (const msg of contentSplit) { - await destination.send({ content: msg, allowedMentions }) + await destination.send({ content: msg, allowedMentions }); } - const { attachments, embeds } = message - + const { attachments, embeds } = message; //巨大なファイルを除外 - const [files, largeFiles] = attachments.partition((f: Attachment) => f.size < 0x8000000) + const [files, largeFiles] = attachments.partition((f: Attachment) => f.size < 0x8000000); - let components: ActionRow[] | ActionRowBuilder[] = message.components + let components: ActionRow[] | ActionRowBuilder[] = message.components; //transfer,openのボタンを更新 - const customId = components[0]?.components[0]?.customId + const customId = components[0]?.components[0]?.customId; if (message.author.id !== client.user?.id) { - components = [] //自分以外のメッセージはcomponentsを削除 - } - else if (customId?.startsWith('transfer')) { - const [prefix, destinationId] = customId?.split(/[;:,]/) - const destination = updates?.[destinationId] + components = []; //自分以外のメッセージはcomponentsを削除 + } else if (customId?.startsWith("transfer")) { + const [, destinationId] = customId?.split(/[;:,]/); + const destination = updates?.[destinationId]; if (destination) { - components = buttonToRow(transferButton.build({ destination })) + components = buttonToRow(transferButton.build({ destination })); } - } else if (customId?.startsWith('open')) { - const [prefix, channelId, mentionableId] = customId?.split(/[;:,]/) - const channel = updates?.[channelId] - const mentionable = message.guild?.roles.cache.get(mentionableId) ?? message.guild?.members.cache.get(mentionableId) + } else if (customId?.startsWith("open")) { + const [, channelId, mentionableId] = customId?.split(/[;:,]/); + const channel = updates?.[channelId]; + const mentionable = + message.guild?.roles.cache.get(mentionableId) ?? message.guild?.members.cache.get(mentionableId); if (channel && mentionable) { - ({ content, components } = openMessage(channel, mentionable)) + ({ content, components } = openMessage(channel, mentionable)); } } try { @@ -67,46 +81,51 @@ export const transferMessage = async (message: Message, destination: GuildTextBa files: files.map((file: Attachment) => file.url), components, embeds, - allowedMentions - }) + allowedMentions, + }); for await (const file of largeFiles.values()) { await destination.send(`\\\\\\diff - ${file.name}のコピーに失敗しました -- ファイル容量の上限は8MBです\\\\\\`) +- ファイル容量の上限は8MBです\\\\\\`); } - if (message.pinned) await newMessage.pin() + if (message.pinned) await newMessage.pin(); if (!options?.noReaction) { - for await (const reaction of message.reactions.cache.keys()) - newMessage.react(reaction) + for await (const reaction of message.reactions.cache.keys()) newMessage.react(reaction); } if (message.thread && "threads" in destination) { - const name = message.thread.name - const newThread = (message.type == MessageType.ThreadCreated ? - await destination.threads.create({ name }) : - await newMessage.startThread({ name, })) as PublicThreadChannel | PrivateThreadChannel - await transferAllMessages(message.thread, newThread) + const name = message.thread.name; + const newThread = ( + message.type == MessageType.ThreadCreated + ? await destination.threads.create({ name }) + : await newMessage.startThread({ name }) + ) as PublicThreadChannel | PrivateThreadChannel; + await transferAllMessages(message.thread, newThread); } } catch (e: any) { - // emptyメッセージだったら無視 + // emptyメッセージだったら無視 if (e.code != 50006) { - throw e + throw e; } } -} - -export const transferAllMessages = async (from: GuildTextBasedChannel, to: GuildTextBasedChannel, options?: transferOptions) => { - const messages = (await fetchAllMessages(from)).reverse() +}; + +export const transferAllMessages = async ( + from: GuildTextBasedChannel, + to: GuildTextBasedChannel, + options?: transferOptions +) => { + const messages = (await fetchAllMessages(from)).reverse(); for await (const message of messages.values()) { - await transferMessage(message, to, options) + await transferMessage(message, to, options); } -} +}; const replaceChannelLinks = (content: string, updates: { [key: string]: GuildChannel }) => { Object.keys(updates).map((key: string) => { - content = content.replace(new RegExp(`<#${key}>`, 'g'), `${updates[key]}`) - }) - return content -} + content = content.replace(new RegExp(`<#${key}>`, "g"), `${updates[key]}`); + }); + return content; +};