diff --git a/.envrc.sample b/.envrc.sample index 0b4dae2..6bf1d37 100644 --- a/.envrc.sample +++ b/.envrc.sample @@ -1,6 +1,6 @@ +export NODE_OPTIONS=--openssl-legacy-provider # Option for OpenSSL error prevention for libraries in use export SLACK_BOT_TOKEN="" # SlackBot OAuth Token export DISCORD_BOT_TOKEN="" # DiscordBot OAuth Token export DISCORD_SERVER_ID="" # DiscordBot OAuth Token -export NODE_OPTIONS=--openssl-legacy-provider # Prevent OpenSSL compatibility errors export MIGRATE_ARCHIVE="true" # Whether to migrate archive channel export SHOW_CUT_LINE="true" # Whether to show cut line between message diff --git a/command/build/message.mts b/command/build/message.mts index 2cdc276..04a9718 100644 --- a/command/build/message.mts +++ b/command/build/message.mts @@ -71,6 +71,12 @@ interface Options { process.exit(0) } spinner.success() + // メッセージに最大ファイルサイズを超えているファイルがある場合は警告を出力する + if (buildAllMessageFileResult.isMaxFileSizeOver) { + spinner.warning( + "Message has attachments that exceed Discord's maximum file size.\nAttachments that exceed Discord's maximum file size will be appended to the message as a file URL.\nConsider releasing the maximum file upload size limit with Discord's server boost." + ) + } process.exit(0) })() diff --git a/command/deploy/message.mts b/command/deploy/message.mts index f469351..1f0fad4 100644 --- a/command/deploy/message.mts +++ b/command/deploy/message.mts @@ -73,6 +73,12 @@ interface Options { process.exit(0) } spinner.success() + // メッセージに最大ファイルサイズを超えているファイルがある場合は警告を出力する + if (createAllMessageResult.isMaxFileSizeOver) { + spinner.warning( + "Message has attachments that exceed Discord's maximum file size.\nAttachments that exceed Discord's maximum file size will be appended to the message as a file URL.\nConsider releasing the maximum file upload size limit with Discord's server boost." + ) + } process.exit(0) })() diff --git a/libs/channel.mts b/libs/channel.mts index 637d14c..a88f28f 100644 --- a/libs/channel.mts +++ b/libs/channel.mts @@ -19,6 +19,11 @@ export interface Channel { channel_name: string is_archived: boolean is_deleted: boolean + guild: { + boost_level: 0 | 1 | 2 | 3 + boost_count: number + max_file_size: 8000000 | 50000000 | 100000000 + } topic: string message_file_paths: string[] } @@ -119,6 +124,11 @@ export const buildChannelFile = async ( channel_name: channel.name || "", is_archived: channel.is_archived || false, is_deleted: false, + guild: { + boost_level: 0, + boost_count: 0, + max_file_size: 8000000, + }, topic: channel.purpose?.value ? channel.purpose.value : "", message_file_paths: distMessageFilePaths, }, @@ -177,13 +187,34 @@ export const createChannel = async ( ? archiveCategory.id : defaultCategory.id, }) - // チャンネルのIDを更新する - channel.discord.channel_id = result.id - newChannels.push(channel) + + // サーバーブーストレベルとファイルサイズを算出する + const boostCount = result.guild.premiumSubscriptionCount || 0 + let boostLevel: Channel["discord"]["guild"]["boost_level"] = 0 + if (boostCount >= 2 && boostCount < 7) { + boostLevel = 1 + } else if (boostCount >= 7 && boostCount < 14) { + boostLevel = 2 + } else if (boostCount >= 14) { + boostLevel = 3 + } + let maxFileSize: Channel["discord"]["guild"]["max_file_size"] = 8000000 + if (boostLevel === 2) { + maxFileSize = 50000000 + } else if (boostLevel === 3) { + maxFileSize = 100000000 + } + + const newChannel = { ...channel } + newChannel.discord.channel_id = result.id + newChannel.discord.guild.boost_level = boostLevel + newChannel.discord.guild.boost_count = boostCount + newChannel.discord.guild.max_file_size = maxFileSize + newChannels.push(newChannel) } } - // チャンネルファイルを作成する + // チャンネルファイルを更新する const createChannelFileResult = await createChannelFile( distChannelFilePath, newChannels diff --git a/libs/message.mts b/libs/message.mts index 67a81ba..38c23b9 100644 --- a/libs/message.mts +++ b/libs/message.mts @@ -90,9 +90,11 @@ export const buildMessageFile = async ( srcMessageFilePath: string, distMessageFilePath: string, users: User[], + maxFileSize: number, showCutLine: boolean ): Promise<{ messages: Message[] + isMaxFileSizeOver?: boolean status: "success" | "failed" message?: any }> => { @@ -101,6 +103,7 @@ export const buildMessageFile = async ( await access(srcMessageFilePath, constants.R_OK) const messageFile = await readFile(srcMessageFilePath, "utf8") const messages = JSON.parse(messageFile) as SlackMessage[] + let isMaxFileSizeOver = false for (const message of messages) { let text = "" @@ -140,6 +143,9 @@ export const buildMessageFile = async ( // 添付ファイルがあれば追加 const files: Message["files"] = message.files?.map((file) => { + if (file.size && file.size > maxFileSize && !isMaxFileSizeOver) { + isMaxFileSizeOver = true + } return { id: file.id || "", file_type: file.filetype || "", @@ -168,7 +174,11 @@ export const buildMessageFile = async ( } } - return { messages: newMessages, status: "success" } + return { + messages: newMessages, + isMaxFileSizeOver: isMaxFileSizeOver, + status: "success", + } } catch (error) { return { messages: [], status: "failed", message: error } } @@ -185,10 +195,12 @@ export const buildAllMessageFile = async ( users: User[], showCutLine: boolean ): Promise<{ + isMaxFileSizeOver?: boolean status: "success" | "failed" message?: any }> => { try { + let isMaxFileSizeOver = false await Promise.all( channels.map( async (channel) => @@ -197,21 +209,28 @@ export const buildAllMessageFile = async ( async (srcMessageFilePath, index) => { const distMessageFilePath = channel.discord.message_file_paths[index] - const { status, message } = await buildMessageFile( + const buildMessageFileResult = await buildMessageFile( srcMessageFilePath, distMessageFilePath, users, + channel.discord.guild.max_file_size, showCutLine ) - if (status === "failed") { - throw new Error(message) + if ( + buildMessageFileResult.isMaxFileSizeOver && + !isMaxFileSizeOver + ) { + isMaxFileSizeOver = true + } + if (buildMessageFileResult.status === "failed") { + throw new Error(buildMessageFileResult.message) } } ) ) ) ) - return { status: "success" } + return { isMaxFileSizeOver: isMaxFileSizeOver, status: "success" } } catch (error) { return { status: "failed", message: error } } @@ -240,6 +259,7 @@ export const getMessageFilePaths = async (messageDirPath: string) => { * Create message * @param discordGuild * @param channelId + * @param maxFileSize * @param distMessageFilePath * @param messages */ @@ -247,9 +267,11 @@ export const createMessage = async ( discordGuild: Guild, messages: Message[], channelId: string, + maxFileSize: number, distMessageFilePath: string ): Promise<{ messages: Message[] + isMaxFileSizeOver?: boolean status: "success" | "failed" message?: any }> => { @@ -257,9 +279,31 @@ export const createMessage = async ( // メッセージを作成 const channelGuild = discordGuild.channels.cache.get(channelId) const newMessages: Message[] = [] + let isMaxFileSizeOver = false if (channelGuild && channelGuild.type === ChannelType.GuildText) { for (const message of messages) { - const result = await channelGuild.send(message.text) + /** + * サーバーブーストレベルに応じて、最大ファイルサイズを超過したファイルは、 + * ファイルをアップロードせず、ファイルURLを添付するようにする + */ + const maxSizeOverFileUrls = message.files?.filter( + (file) => file.size > maxFileSize + ) + const uploadFileUrls = message.files + ?.filter((file) => file.size < maxFileSize) + .map((file) => file.url) + let content = message.text + if (maxSizeOverFileUrls) { + isMaxFileSizeOver = true + for (const file of maxSizeOverFileUrls) { + content += `\n${file.url}` + } + } + + const result = await channelGuild.send({ + content: content, + files: uploadFileUrls, + }) newMessages.push({ ...message, ...{ @@ -290,7 +334,11 @@ export const createMessage = async ( } } - return { messages: newMessages, status: "success" } + return { + messages: newMessages, + isMaxFileSizeOver: isMaxFileSizeOver, + status: "success", + } } catch (error) { return { messages: [], status: "failed", message: error } } @@ -303,10 +351,12 @@ export const createAllMessage = async ( discordGuild: Guild, channels: Channel[] ): Promise<{ + isMaxFileSizeOver?: boolean status: "success" | "failed" message?: any }> => { try { + let isMaxFileSizeOver = false await Promise.all( channels.map(async (channel) => { await Promise.all( @@ -319,16 +369,20 @@ export const createAllMessage = async ( discordGuild, getMessageFileResult.messages, channel.discord.channel_id, + channel.discord.guild.max_file_size, messageFilePath ) if (createMessageResult.status === "failed") { throw new Error(createMessageResult.message) } + if (createMessageResult.isMaxFileSizeOver && !isMaxFileSizeOver) { + isMaxFileSizeOver = true + } }) ) }) ) - return { status: "success" } + return { isMaxFileSizeOver: isMaxFileSizeOver, status: "success" } } catch (error) { return { status: "failed", message: error } } diff --git a/libs/util/spinner.mts b/libs/util/spinner.mts index e44476b..fed5cc0 100644 --- a/libs/util/spinner.mts +++ b/libs/util/spinner.mts @@ -8,8 +8,9 @@ export class Spinner { loading: "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", success: "✔️", failed: "×", + warning: "⚠️", } - private charsType: "loading" | "success" | "failed" = "loading" + private charsType: "loading" | "success" | "failed" | "warning" = "loading" private charIndex: number = 0 private delay: number = 60 private id?: NodeJS.Timer @@ -29,6 +30,8 @@ export class Spinner { char = pc.green(char) } else if (this.charsType === "failed") { char = pc.red(char) + } else if (this.charsType === "warning") { + char = pc.yellow(char) } this.stream.write(char + " " + pc.blue(this.text)) this.charIndex = (this.charIndex + 1) % chars.length @@ -54,7 +57,6 @@ export class Spinner { success(text?: string | null, message?: any) { this.stop() if (text) this.text = text - if (this.text === "") this.text = "Success" this.charsType = "success" console.log(pc.green(this.chars.success) + " " + pc.blue(this.text)) if (message) console.log(message) @@ -63,9 +65,12 @@ export class Spinner { failed(text?: string | null, message?: any) { this.stop() if (text) this.text = text - if (this.text === "") this.text = "Failed" this.charsType = "failed" console.log(pc.red(this.chars.failed) + " " + pc.blue(this.text)) if (message) console.error(message) } + + warning(text: string) { + console.log(pc.yellow(this.chars.warning) + " " + pc.yellow(text)) + } }