From cd288bf171dff93b794dbc336a0d3aacd4f953c1 Mon Sep 17 00:00:00 2001 From: yogarasu Date: Fri, 19 Aug 2022 10:05:54 +0900 Subject: [PATCH] =?UTF-8?q?fix=20#14=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E5=90=8D=E3=82=82=E3=81=97=E3=81=8F=E3=81=AF=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=8C=E6=AD=A3=E3=81=97?= =?UTF-8?q?=E3=81=8F=E7=BD=AE=E6=8F=9B=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- command/build/message.mts | 40 ++++++++++++- command/build/user.mts | 21 +------ libs/bot.mts | 75 ------------------------ libs/message.mts | 50 +++++++++++++--- libs/user.mts | 117 ++++++++++++++++++++++++++++++-------- 5 files changed, 174 insertions(+), 129 deletions(-) delete mode 100644 libs/bot.mts diff --git a/command/build/message.mts b/command/build/message.mts index dfaea8a..881c28e 100644 --- a/command/build/message.mts +++ b/command/build/message.mts @@ -5,6 +5,7 @@ import { Spinner } from "../../libs/util/spinner.mjs" import { getChannelFile } from "../../libs/channel.mjs" import { buildAllMessageFile } from "../../libs/message.mjs" import { getUserFile } from "../../libs/user.mjs" +import { createSlackClient } from "../../libs/client.mjs" const __dirname = new URL(import.meta.url).pathname const distDirPath = resolve(__dirname, "../../../.dist/") @@ -14,9 +15,40 @@ const distUserFilePath = join(distDirPath, "user.json") dotenv.config({ path: "./.envrc" }) const spinner = new Spinner() +interface Options { + slackBotToken?: string +} + ;(async () => { const program = new Command() - program.description("Build message file command").parse(process.argv) + program + .description("Build message file command") + .requiredOption( + "-st, --slack-bot-token [string]", + "SlackBot OAuth Token", + process.env.SLACK_BOT_TOKEN + ) + .parse(process.argv) + + // パラメーターの取得 + spinner.loading("Check parameter") + const options: Options = program.opts() + const { slackBotToken } = options + if (slackBotToken === undefined) { + spinner.failed(null, "Required parameter is not found") + process.exit(0) + } + spinner.success() + + // Slackのクライアントを作成する + spinner.loading("Create slack client") + const { slackClient, ...createSlackClientResult } = + createSlackClient(slackBotToken) + if (!slackClient || createSlackClientResult.status === "failed") { + spinner.failed(null, createSlackClientResult.message) + process.exit(0) + } + spinner.success() // チャンネルを取得する spinner.loading("Get channel") @@ -40,7 +72,11 @@ const spinner = new Spinner() // メッセージファイルを作成する spinner.loading("Build message file") - const buildAllMessageFileResult = await buildAllMessageFile(channels, users) + const buildAllMessageFileResult = await buildAllMessageFile( + slackClient, + channels, + users + ) if (buildAllMessageFileResult.status === "failed") { spinner.failed(null, buildAllMessageFileResult.message) process.exit(0) diff --git a/command/build/user.mts b/command/build/user.mts index 2b32094..145b0f3 100644 --- a/command/build/user.mts +++ b/command/build/user.mts @@ -4,7 +4,6 @@ import { resolve, join } from "node:path" import { Spinner } from "../../libs/util/spinner.mjs" import { buildUser } from "../../libs/user.mjs" import { getChannelFile } from "../../libs/channel.mjs" -import { getMessageBotId, getBotData } from "../../libs/bot.mjs" import { createSlackClient } from "../../libs/client.mjs" const __dirname = new URL(import.meta.url).pathname @@ -63,28 +62,10 @@ interface Options { } spinner.success() - // メッセージファイル内のBotIdを取得する - spinner.loading("Get BotId in message file") - const { botIds, ...getMessageBotIdResult } = await getMessageBotId(channels) - if (getMessageBotIdResult.status === "failed") { - spinner.failed(null, getMessageBotIdResult.message) - process.exit(0) - } - spinner.success - - // Botのデータを取得する - spinner.loading("Get bot data") - const { bots, ...getBotDataResult } = await getBotData(slackClient, botIds) - if (getBotDataResult.status === "failed") { - spinner.failed(null, getBotDataResult.message) - process.exit(0) - } - spinner.success - // ユーザーファイルを作成する spinner.loading("Build user file") try { - await buildUser(srcUserFilePath, distUserFilePath, bots) + await buildUser(srcUserFilePath, distUserFilePath) } catch (error) { spinner.failed(null, error) process.exit(0) diff --git a/libs/bot.mts b/libs/bot.mts deleted file mode 100644 index 7c6dd1f..0000000 --- a/libs/bot.mts +++ /dev/null @@ -1,75 +0,0 @@ -import { readFile } from "node:fs/promises" -import { Message as SlackBaseMessage } from "@slack/web-api/dist/response/ChatPostMessageResponse" -import { WebClient as SlackClient } from "@slack/web-api" -import type { Channel } from "./channel.mjs" - -export interface Bot { - id: string - app_id: string - name: string -} - -/** - * Get bot data - * @param slackClient - * @param botIds - */ -export const getBotData = async ( - slackClient: SlackClient, - botIds: string[] -): Promise<{ - bots: Bot[] - status: "success" | "failed" - message?: any -}> => { - try { - const bots = await Promise.all( - botIds.map(async (botId) => { - const result = await slackClient.bots.info({ bot: botId }) - return { - id: result.bot?.id || "", - app_id: result.bot?.app_id || "", - name: result.bot?.name || "", - } as Bot - }) - ) - return { bots: bots, status: "success" } - } catch (error) { - return { bots: [], status: "success", message: error } - } -} - -/** - * Get BotId in message - * @param channels - */ -export const getMessageBotId = async ( - channels: Channel[] -): Promise<{ - botIds: string[] - status: "success" | "failed" - message?: any -}> => { - let botIds: string[] = [] - - try { - for (const channel of channels) { - for (const messageFilePath of channel.slack.message_file_paths) { - const srcMessageFile = await readFile(messageFilePath, "utf8") - const srcMessage: SlackBaseMessage[] = JSON.parse(srcMessageFile) - botIds = [ - ...botIds, - ...srcMessage - .filter((message) => message.bot_id) - .map((message) => message.bot_id as string), - ] - } - } - // 重複は排除する - botIds = [...new Set(botIds)] - - return { botIds: botIds, status: "success" } - } catch (error) { - return { botIds: [], status: "failed", message: error } - } -} diff --git a/libs/message.mts b/libs/message.mts index f904049..c2d6d98 100644 --- a/libs/message.mts +++ b/libs/message.mts @@ -8,6 +8,8 @@ import { } from "@slack/web-api/dist/response/ChatPostMessageResponse" import { ChannelType, EmbedType } from "discord.js" import type { Guild as DiscordClientType, APIEmbed as Embed } from "discord.js" +import type { WebClient as SlackClientType } from "@slack/web-api" +import { getUser, getUsername } from "./user.mjs" import type { User } from "./user.mjs" import type { Channel } from "./channel.mjs" @@ -93,6 +95,7 @@ export const createMessageFile = async ( * @param users */ export const buildMessageFile = async ( + slackClient: SlackClientType, srcMessageFilePath: string, distMessageFilePath: string, users: User[], @@ -113,17 +116,35 @@ export const buildMessageFile = async ( let content = message.text || "" // メッセージ内のユーザー名もしくはBot名のメンションを、Discordでメンションされない形式に置換 - if (/<@U[A-Z0-9]{10}>/.test(content)) { - for (const user of users) { - if (new RegExp(`<@${user.slack.id}>`, "g").test(content)) { - content = content.replaceAll( - new RegExp(`<@${user.slack.id}>`, "g"), - `@${user.discord.name}` - ) + const matchMention = content.match(/<@U[A-Z0-9]{10}>/g) + if (matchMention?.length) { + const userIds = matchMention.map((mention) => + mention.replace(/<@|>/g, "") + ) + for (const userId of userIds) { + const user = users.find((user) => user.slack.id === userId) + if (user && user.slack.name) { + content = content.replaceAll(`<@${userId}>`, `@${user.slack.name}`) + } else { + // usersにないメンションは、APIから取得する + const getUsernameResult = await getUsername(slackClient, userId) + const username = getUsernameResult.username + if (username && getUsernameResult.status === "success") { + content = content.replaceAll(`<@${userId}>`, `@${username}`) + } else { + throw new Error( + `Failed to convert mention of @${userId} to username\n` + + getUsernameResult.message + ) + } } } } + // メッセージ内のチャンネルメンションタグを、Discordで表示される形式に置換 + if (//.test(content)) + content = content.replaceAll(//g, "@channel") + // メッセージ内の太文字を、Discordで表示される形式に置換 if (/\*.*\*/.test(content)) content = content.replaceAll(/\**\*/g, "**") @@ -149,13 +170,22 @@ export const buildMessageFile = async ( ] // メッセージの送信者情報を取得 - const user = users.find( + let user = users.find( (user) => user.slack.id === message.user || user.slack.bot?.app_id === message.app_id ) + // メッセージの送信者情報が取得できない場合は、APIから取得 if (!user) { - throw new Error("Failed to get user for message") + if (message.user) { + const getUserResult = await getUser(slackClient, message.user) + if (getUserResult.user) { + user = getUserResult.user + } + } + if (!user) { + throw new Error("Failed to get user for message") + } } const anthor: Message["slack"]["anthor"] = { id: user.slack.id, @@ -246,6 +276,7 @@ export const buildMessageFile = async ( * @param users */ export const buildAllMessageFile = async ( + slackClient: SlackClientType, channels: Channel[], users: User[] ): Promise<{ @@ -264,6 +295,7 @@ export const buildAllMessageFile = async ( const distMessageFilePath = channel.discord.message_file_paths[index] const buildMessageFileResult = await buildMessageFile( + slackClient, srcMessageFilePath, distMessageFilePath, users, diff --git a/libs/user.mts b/libs/user.mts index 71f29c6..e3faa5a 100644 --- a/libs/user.mts +++ b/libs/user.mts @@ -1,14 +1,14 @@ import { access, readFile, writeFile, mkdir, constants } from "node:fs/promises" import { dirname } from "node:path" +import { WebClient as SlackClient } from "@slack/web-api" import { Member as SlackUser } from "@slack/web-api/dist/response/UsersListResponse" -import type { Bot } from "./bot.mjs" export interface User { slack: { id: string name: string deleted: boolean - color: string | "808080" + color: string image_url: string is_bot: boolean bot?: { @@ -22,6 +22,68 @@ export interface User { } } +/** + * Get user data + * @param slackClient + * @param userId + */ +export const getUser = async ( + slackClient: SlackClient, + userId: string +): Promise<{ + user?: User + status: "success" | "failed" + message?: any +}> => { + try { + const result = await slackClient.users.info({ user: userId }) + + const user: User = { + slack: { + id: result.user?.id || "", + name: result.user?.name || "", + deleted: result.user?.deleted || false, + color: result.user?.color || "808080", + image_url: result.user?.profile?.image_512 || "", + is_bot: result.user?.is_bot || false, + bot: { + app_id: result.user?.profile?.api_app_id || "", + bot_id: result.user?.profile?.bot_id || "", + }, + }, + discord: { + id: "", + name: "", + }, + } + + return { user: user, status: "success" } + } catch (error) { + return { status: "success", message: error } + } +} + +/** + * Get username + * @param slackClient + * @param userId + */ +export const getUsername = async ( + slackClient: SlackClient, + userId: string +): Promise<{ + username?: string + status: "success" | "failed" + message?: any +}> => { + try { + const result = await slackClient.users.info({ user: userId }) + return { username: result.user?.name, status: "success" } + } catch (error) { + return { status: "success", message: error } + } +} + /** * Get user * @param distUserFilePath @@ -46,49 +108,58 @@ export const getUserFile = async ( * Build user * @param srcUserFilePath * @param distUserFilePath - * @param bots */ export const buildUser = async ( srcUserFilePath: string, - distUserFilePath: string, - bots: Bot[] + distUserFilePath: string ): Promise<{ users: User[]; status: "success" | "failed"; message?: any }> => { try { await access(srcUserFilePath, constants.R_OK) const usersFile = await readFile(srcUserFilePath, "utf8") const users = JSON.parse(usersFile) .map((user: SlackUser) => { - // Botの場合はBot情報を取得 - let botInfo: User["slack"]["bot"] = undefined + // ユーザーがBotの場合はBot情報を取得 + let bot: User["slack"]["bot"] = undefined if (user.is_bot) { const appId = user.profile?.api_app_id || "" - const botId = - bots.find((bot) => bot.app_id === appId)?.id || - user.profile?.bot_id || - "" - botInfo = { bot_id: botId, app_id: appId } + const botId = user.profile?.bot_id || "" + bot = { app_id: appId, bot_id: botId } } // ユーザー名もしくはBot名を取得 const name = user.is_bot - ? user.real_name || "" - : user.profile?.display_name || "" + ? user.profile?.real_name + : user.profile?.display_name + + // ユーザーの必須項目がない場合は例外を投げる + if ( + !user.id || + !name || + user.deleted === undefined || + user.is_bot === undefined || + !user.color || + !user.profile?.image_512 + ) { + throw new Error("User is missing a required parameter") + } - return { + const newUser: User = { slack: { - id: user.id || "", - name: name, - deleted: user.deleted || false, - is_bot: user.is_bot || false, - color: user.color || "808080", - image_url: user.profile?.image_512 || "", - bot: botInfo, + id: user.id, + name: name || "", + deleted: user.deleted, + is_bot: user.is_bot, + color: user.color, + image_url: user.profile.image_512, + bot: bot, }, discord: { id: "", name: name, }, - } as User + } + + return newUser }) .sort((user: User) => !user.slack.is_bot || !user.slack.deleted)