🤖

SlackからDiscordにメッセージを移行するCLIを作ってみた

2022/08/23に公開
10

MSD

Slackのビジネスプランを契約しているのですが、通知が来たり来なかったりで調子が悪かったり、障害の数が多くなってきたり、ゲスト数が有料アクティブメンバーの数に依存するので外部の人をjoinさせづらいなど、使用しているうちにいくつか不満点が出てきました

そこで、普段使い慣れているDiscordへの移行を図るべく、今回SlackからDiscordに移行する「MSD」というCLIを簡単に作ってみました
今回はMSDの仕組みと、作る上でつまづいた箇所についてここに残しておきたいと思います
ちなみにスターをつけてくれたり、何か問題や提案などがあればissueを上げてもらえると大変喜びます

https://github.com/revoltage-inc/msd-cli

仕組み

仕組みとしては単純で、コマンドを実行すると、SlackのエクスポートデータをDiscordに出力できるデータに変換し、DiscordBot経由でチャンネルの作成とメッセージの出力を行うことでメッセージの移行を実現しています

アーキテクチャ

同じことを既にやっているパイオニアの方がいらっしゃるのですが、メッセージのデータの取得漏れが怖く、できるだけAPIを叩く回数を減らしたかったので、基本的にエクスポートデータから参照するようにしています
ただ、プライベートチャンネルの移行やWebアプリ化、後述する送信者情報の照合問題のことを考えると、将来的にはエクスポートデータではなく、全てSlackBot経由のAPIで取得するように改良を検討しています
https://qiita.com/yuki-n/items/25e73490d82a0ad3c3fd

コマンドの実行をすると、下記のようにチャンネルやメッセージがデプロイされていきます
添付ファイル、ピン留めアイテム、リプライ、チャンネルのトピックなども移行できます

メッセージの例

SlackBotによるメッセージの送信者情報の照合

Slackのエクスポートデータからメッセージのデータを参照するなら、
Slackのエクスポートデータにはusers.jsonというユーザー情報(Botを含む)の一覧ファイルがあるので、その内容と突き合わせればメッセージの送信者情報を照合できるのですが、一部取得できないものがありました

これは検証したところ、Botを再デプロイしたために別の新しいBotIDが振られたり、そもそも複数のBotがデプロイされてたりと様々な原因があることがわかっています

そのため、Slackのエクスポートデータだけでは取得できないメッセージの送信者情報がある場合は、SlackBotを使って照合しています

メッセージの変換

SlackとDiscordでは太文字や引用タグなど、記法が違う箇所があり、記法を合わせるように置換しています
メンションはDiscordにメッセージをデプロイした際に発火しないようにしたり、
リンク付き文字列のようなDiscordには無い記法はただの文字列にして、別途URLを貼るようにしたり色々と工夫をしています

https://slack.com/intl/ja-jp/help/articles/202288908-メッセージの書式設定

https://support.discord.com/hc/ja/articles/210298617-マークダウン-テキスト-101-チャットフォーマット-太字-斜体-下線-

Embed

DiscordはBotのみが可能な少しリッチな記述方法として、Embedがあります

Discord Embed Builderのページで疑似的に作ってみることができるのですが、timestampを埋め込んで日付検索に対応でき、Slackのユーザープロフィール画像やSlackのユーザーカラーを表現できるので見やすくなるため採用しました

Embed

つまづいた箇所

Privateチャンネルが移行できない

Slackはビジネスプラン以上でオーナー申請することで、Privateチャンネルを含めた全てのエクスポートファイルをリクエストできるのですが、法律的な特殊事情がない限り、弾かれてしまうので事実上移行できません
今回、移行したいから全てのエクスポートファイルが欲しいとリクエストしてみたのですが断られてしまいました
さらにSlackは仕様上、一度PrivateチャンネルにするとPublicチャンネルにはできません

Slack Web APIを叩けば取得できるかもしれないので、移行できるのであれば改修したいなと思っています

https://slack.com/intl/ja-jp/help/articles/1500001548241-すべての会話のエクスポートをリクエストする

浮動小数点付きのUNIXタイムスタンプのデータ型の保存

Slackはメッセージのプライマリーキーとして、1660786460.770929のように浮動小数点付きのUNIXタイムスタンプを利用しています

これを何も考えずにDatetime型に入れてしまい、浮動小数点が丸められてしまって、メッセージを削除する際にタイムスタンプが一致しなくて消せない事案が発生し、最終的に文字型にしました

ちなみにDiscordはプライマリーキーのID生成方式に、Twitterが開発したタイムスタンプを織り込んだSnowflakeというものを採用しているらしく、ID生成にUUIDくらいしか使ったことがなかったのでとても勉強になりました

https://snowsta.mp/

SQLiteの同時書き込みできない問題

最初は移行データをSlackのエクスポートデータと同じjsonファイルに保存してましたが、
メッセージのリプライに対応するために検索の必要性が求められてきたので、とりあえず簡単に実装しようと思いDBはSQLiteで、ORMとClientはPrismaで実装したのですが、
よくよく考えてみるとSQLiteはファイルなので同時書き込みできないじゃないかということに気がつき、
そのせいでメッセージの作成処理が直列処理になってしまって移行速度が遅くなってしまいました

とはいえORMとClientはPrismaを使ってるので、DockerでローカルDBを立てて接続先を変えて、処理を並列化すれば簡単に解決できる問題だと思っています
メッセージは時系列順に並べなくてはならないので、そこは並列化できないのですが、複数のチャンネルに同時にメッセージをデプロイできるように並列化すれば、2000件のメッセージのデータであれば移行に20分ほどかかっていたところを、数分に抑えられる想定です

Discordの最大アップロードファイルサイズの制限

Discordの最大アップロードファイルサイズはサーバーブーストレベルに応じて変わりますが、
最大でも100MBまでしかアップロードできません
サーバーブーストを一切していないと、8MBしかないのでMacのスクショ画像でもアップロードしようとすれば割と引っ掛かります

自分は基本的に重ためのファイルはクラウド共有しているため、Macのスクショ画像1枚くらいしか引っ掛かかりませんでしたので、とりあえず最大アップロードファイルサイズを超えたファイルはスキップするようになっています

自分のユースケースでは、S3やGoogleDriveなどの他のストレージサービスと連携するほど重要なファイルがなかったので、現状は実装していません

メッセージの文字数制限

メッセージを入れるEmbedのdescriptionには文字数制限があり、4096文字以上でエラーが発生します

4096文字以上の文字は文字が入るケースは稀だと思うので、暫定対応として4096文字以上は省略するようにしていますが、もしこの問題に引っかかるケースが多ければメッセージを分割するなどの実装も検討しています

所感

最近、Slackのプラン改定で、フリープランは今後過去90日間のメッセージ履歴しか見れなくなるようなので、今後中小規模のコミュニティがDiscordに移り変わっていく気配を感じています
AWS Startup CommunityやStorybookなどのDiscordを利用している技術コミュニティもよく見かけるようになってきました

2022 年 9 月 1 日から、フリープランの使用上限がシンプルになります。
メッセージ数を 1 万件、ストレージ容量を 5GB に制限する代わりに、過去 90 日間のメッセージ履歴とファイルストレージの利用を無制限にすることで、制限を気にせず皆さまのチームに Slack をご利用いただけます。
クリップおよびメッセージとファイルの保存設定などの機能もご利用いただけるようになります。
今後の変更予定についてのよくある質問をご覧ください。

https://slack.com/intl/ja-jp/help/articles/7050776459923-プロプランの料金改定とフリープランの最新情報#:~:text=2022 年 9 月 1 日より、過去 90 日間,できるようになります。

今後、Discordの企業利用が増えていくのではないかなと予想しています
非エンジニアの方には移行が難しいかと思うので、もし需要があればWebアプリ化を考えています

Discussion

ピン留めされたアイテム
yogarasuyogarasu
hihi

記事参考にさせていただいています。

チャンネルをデプロイする
ユーザーの画像をホストするためのチャンネルをデプロイする

までは成功するのですが、

メッセージをデプロイする
npm run migrate:message

を実行すると、以下のエラーとなります。

❯ npm run migrate:message

> msd-cli@1.0.8 migrate:message
> node --loader ts-node/esm ./command/migrate/message.mts

(node:64400) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✔️ Check parameter
✔️ Create client
× Migrate message
Error: An API error occurred: bot_not_found
    at platformErrorFromResult (/Users/アカウント名/Desktop/slack2discord/msd-cli/node_modules/@slack/web-api/src/errors.ts:95:5)
    at WebClient.apiCall (/Users/アカウント名/Desktop/slack2discord/msd-cli/node_modules/@slack/web-api/src/WebClient.ts:263:36)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async UserClient.getBot (file:///Users/アカウント名/Desktop/slack2discord/msd-cli/libs/user.mts:139:20)
    at async MessageClient.migrateAllMessage (file:///Users/アカウント名/Desktop/slack2discord/msd-cli/libs/message.mts:110:22)
    at async file:///Users/アカウント名/Desktop/slack2discord/msd-cli/command/migrate/message.mts:53:5 {
  code: 'slack_webapi_platform_error',
  data: {
    ok: false,
    error: 'bot_not_found',
    response_metadata: { scopes: [Array], acceptedScopes: [Array] }
  }
}

初回設定にてScopeに「users:read」を追加したslackBotは作っているはずで、どう対処していいかわからない状況です。お手数ですが、どこでエラーになっているかわかりますでしょうか。。

yogarasuyogarasu

そのままエラーが示している通り、Bot情報をSlackBot経由で取得する過程で失敗しています

Bot情報の取得は、メッセージの送信者がBotの場合にそのBot名などの必要な情報を取得するために行われます
今回そのBot情報が取得できなかったということは下記のケースが考えられます

  • Botじゃない送信者をBotとして情報を取得しようとした
  • Botではあるものの、Bot情報が取得できない想定外のタイプのBotだった

Slackは仕様変更も多々あり、メッセージのレスポンスに関して全てのパターンを網羅できないので、後者の可能性が十分に考えられます

現状このエラーだけではそれは判別不可能なので、
どこのメッセージで処理が止まっているかをデバッグして、その該当のメッセージのJSONを把握し、
メッセージに含まれるbot_idから情報が取得できるかbots.infoのAPIを叩いて確認してみてください

そこまで試して不明な場合は、GitHubのissueにバグとして起票して、メッセージのJSONを添えてください

hihi

お返事まで期間空いてしまってすみません。ご返信ありがとうございました。
はっきりとはわからなかったのですが、おそらく1投稿の文字数が多いことや1チャンネルの投稿数が多いことが原因でエラーになっていたようです。結果無事に移行することができました。ありがとうございました!

tai8848tai8848

これのためにGithubを初めて触っている次第で、初歩的な質問で申し訳ありません。
初期設定を終えてコマンドを実行すると、ドライブレターが二重になってフォルダやファイルが存在しないエラーに読めるのですが、何を間違えてしまっているでしょうか。

$ git rev-parse --show-toplevel
D:/hoge/Git/msd-cli
$ npm run deploy

> msd-cli@1.5.1 deploy
> run-s init migrate:channel deploy:channel migrate:user deploy:user migrate:message deploy:message


> msd-cli@1.5.1 init
> node --loader ts-node/esm ./command/init.mts && npm run prisma

(node:33108) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
× Init working directory
[Error: ENOENT: no such file or directory, mkdir 'D:\D:\hoge\Git\msd-cli\.dist'] {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'mkdir',
  path: 'D:\\D:\\hoge\\Git\\msd-cli\\.dist'
}

yogarasuyogarasu

私の環境ではそのようなエラーは発生しないので、原因は明確にわかりませんが
作業ディレクトリ.distの作成に失敗しているので下記をご確認ください

  • msd-cliのディレクトリでコマンドを実行しているか
  • msd-cliのディレクトリのパーミッションは適切か

それでも解決しない場合は、initコマンドはあくまで.distディレクトリを初期化&作成しているだけなので、msd-cli内のディレクトリに手動で.distディレクトリを作成してみてください

tai8848tai8848

ご返信ありがとうございます。msd-cliのディレクトリでコマンドを実行しており、上記エラーが出ながらも.distディレクトリが作成されて、その中にmsd.sqliteとmsd.sqlite-journalが作成されますが、次のmigrate:channelコマンド以降もエラーになります。
最初からやり直したら、npm installの時点でエラーがでていたようで(volta install node@18.12.0 npm@9.0.0は成功している)仰るとおりパーミッションの問題かも知れません。もう少し試してみます。

おっびおっび
$ npm run deploy:message

> msd-cli@1.5.1 deploy:message
> node --loader ts-node/esm ./command/deploy/message.mts

(node:18541) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✔️ Create client
× Deploy message
Error: Failed to deploy message
Not found message manager
Message Timestamp: 1666851054.249249
Message Content: {毎回同じメッセージで処理が停止します。}
    at MessageClient.deployManyMessage (file:///Users/{ユーザー名}/msd-cli/libs/message.mts:329:15)
    at async MessageClient.deployAllMessage (file:///Users/{ユーザー名}/msd-cli/libs/message.mts:204:9)
    at async file:///Users/{ユーザー名}/msd-cli/command/deploy/message.mts:48:5

この"Not found message manager"はどういった条件で出てくるのでしょうか?
毎回同じメッセージでこのエラーが表示されてデプロイが完了しませんのでエラーの解消を試みているのですがどうしてもエラーの解消ができずにいます。。。

yogarasuyogarasu

そのメッセージは、メッセージクライアントの作成に失敗している状態です

考えられるケースとしては、下記が挙げられます

  • Discordにメッセージの送信先のチャンネルが作成されていない(もしくは失敗している)
  • DiscordのBotに権限がない
  • メッセージに想定されていない問題がある、もしくはマイグレーション漏れ

まず、最初にDiscordのBotに「Send Messages」、「Manage Channels」、「Manage Messages」の権限が設定されていることを確認してください

それでもダメな場合は、メッセージかチャンネルの問題だと思いますが
その場合はDBを見ないとわからないレベルの話になるので、
.distディレクトリ下にmsd.sqliteファイルがあるので、それを何かしらのDBクライアントで開いて、Messageテーブルでデプロイが失敗している箇所のメッセージのレコードを確認してみてください

まず、Messageテーブルで666851054.249249のtimestampのレコードのchannelDeployIdを見て、
そのchannelDeployIdと、ChannelテーブルでdeployIdが一致するレコードが無いかを確認するのがいいと思います

ログインするとコメントできます