たろログ2

実験的運用により、記事品質が乱高下することがあります。予めご了承ください。

2022-05-19 discord にファイルをアップロードするアプリケーションの作成

discord にファイルをアップロードするアプリケーションの作成を実施している。

discord はファイルの添付について 8MB という制限があるため、友人が大きなファイルを共有しようとしては、「あ、送信できんかった、容量でかすぎるんか」と言うことが度々発生していた。

ということでアプリケーションを作成することにした。

まあ Google Drive にアップロードしてから共有したり、その他様々な方法で共有はできるはずなので、そこまで必要性のあるアプリケーションではない。単に自分が何か自分が作りたかったというのが一番の理由である。

HTML のフォームから、 S3 にファイルを保存する。その後、 Discord の Webhook にリクエストを送り、チャットに S3 のオブジェクトの URL を投稿する。

S3 への画像アップロード

Uploading Photos to Amazon S3 from a Browser - AWS SDK for JavaScript

上記のチュートリアルで紹介されている、説明用のアプリケーションを流用。

Amazon Cognito という AWS のサービスを使って、 HTML と JavaScript で構成したブラウザ上のアプリケーションから、直接 S3 にファイルを保存した。

このチュートリアルでハマったこと

S3 の設定において、「Block Public Access」を有効にした状態で Policy を変更しようとした。

すると、この「Block Public Access」の設定によってコンソールからの Policy の設定変更も拒否され、変更ができなかった。

You don't have permissions to edit bucket policy とか、 s3:PutBucketPolicy がどうこうとか表示されて IAM を疑ってしまった。

S3 バケットポリシーを変更するときの 403 エラーを解決する

を確認して解決

Amazon Cognito

上記のチュートリアルAmazon Cognito というサービスを使うことになっていたので、これを学ぶ必要があった。

簡単にこちらのサービスのドキュメントにも目を通した。

Getting started with Amazon Cognito identity pools (federated identities) - Amazon Cognito

  • 認証の機能を提供するサービスである。
  • user pool と、 identity pool が定義できる
  • user pool では、どのような方法でユーザがサインアップ、ログインするかを定義する
  • identity pool では、どのユーザに、どのようなアクセス権限 (AWS の各サービスにアクセスする権限) を与えるかを定義する
  • 今回の例では、認証なしの unauthorized user を発行し、これにアクセス権限を割り当てる。そのため、 user pool を使う必要はなく、 identity pool を作成するのみでよい。

Discord の Webhook を利用してメッセージを送信

次は discord に Webhook でメッセージを送信するステップ。しかしこれが曲者だった。現在も完了していない。

まず Axios を利用して POST リクエストを送ることにした。しかし、 CORS でアクセスを拒否されてしまった。

素直に CORS に関する正しい手続きを Axios で踏んでもよかったのだけれど、まだ CORS は曖昧な部分があり学習コストがかかる。

discord の Webhook 送信用のライブラリがあるのではないかと探しているとビンゴ、 discord.js というライブラリが存在した。

Webhooks | discord.js Guide

しかしこのライブラリは、CDN でのインストールが公式でサポートされていなかった。

Installing Node.js and discord.js | discord.js Guide

discord.js を利用するために、アプリケーションに変更を加える必要が生じた。具体的には、ライブラリを CDN 経由で利用する形から、 npm install で利用する形に変更する必要があった。

npm

まず、 npm について簡単に調べた。

Node.js が JavaScript を動作させるための実行環境で、 npm はパッケージマネージャーである。

次に、 ブラウザ上で動作する、 HTML ベースのアプリケーションでどのように npm のパッケージを利用するのかについて調べた。

Node.jsでモジュールを読み込んでどうやってHTMLで使用するのか?

ブラウザ上で動作させるアプリケーションにおいてこのようなことを行うためには、 Webpack や browserify のような「モジュールハンドラ」を利用し、 ブラウザで扱える形式の JavaScript ファイルを生成する必要があるらしい。

webpack というモジュールハンドラを利用することにした。

webpack

webpackを利用してみる(入門編) | フロントエンドBlog | ミツエーリンクス

webpack.config.js を生成した。各設定項目の詳細は以下の通り。

  • mode : development, production, none が選択できる。production では圧縮されるなど
  • entry : スクリプトのエントリーポイント
  • output : 出力先のパスと、出力先のファイル名

出力された bundle.js を HTML で読み込むことで、 npm のライブラリを利用することができた。

しかし、ここでまたひとつ問題が発生した。

<input id="photoupload" type="file" accept="image/*"><br>
<button id="addphoto" onclick="addPhoto()">Add Photo</button>
function addPhoto(albumName) {
    var files = document.getElementById("photoupload").files;
    ...
}

という形で onclick 属性を用いて JavaScript の関数を呼んでいたのだが、 webpack でビルドした形式でスクリプトを読み込んで呼ぶと、 addPhoto is not defined と表示されてしまう。

package - Uncaught ReferenceError: FUNCTION is not defined within webpack JS bundle - Stack Overflow

window.addPhoto = function addPhoto () {...} として公開する方法を試したが、結果は変わらなかった。

結局、stackoverflow のこの回答 および stackoverflow-webpack-event-listener/user-container.ts のコードを参考にして、 HTML 自体を JavaScript 側で生成するようにした。

そうすると、この問題は解決した。

CDN でのインストールから、 npm でのインストールに

npm で各種モジュールをインストールできるようになったので、まず aws-sdkCDN による利用から、 npm install による利用に切り替えた。

aws sdk - Angular 12 aws-sdk 2.910.0 Error: Module not found: Error: Can't resolve 'util' in '...\node_modules\aws-sdk\lib' - Stack Overflow

不足したパッケージがあったため、 npm install -S util で追加インストールを行った。

discord.js を npm でインストールするとエラー

次に discord.js を npm install した。インストール自体は問題なく済んだのだが、 webpack でビルドをしようとすると凄まじい数のエラーが出た。不足パッケージやその他。

不足パッケージについては手動で指定して追加インストールを行ったのだが、結局エラーは解消されず、また単純なパッケージの不足でないエラーがたくさん出力されたためこの手法は諦めた。

discord.js - Is it possible to make a discord js website and how? - Stack Overflow

で、 discord.js/browser というものがあると案内されていたが廃止されたようだった。なんせ諸々難しすぎ、めげるようなことが多かった。

改めてライブラリに構わず方法を探し回っていたところ、簡単なスクリプトを見つけた。

js post request example for discord webhooks using the fetch web api

ちょっと実際に小さなスクリプトを書いて動かしてみたところ、そのままで問題なく動作することが確認できた!

ということで、明日以降はこのスクリプトを実際にプロダクトに組み込んでみようと思っている。