ブラウザでdiscord.jsを使ってボットを動かそうとしたらエラーになってしまったので、その原因を調べて、エラーを回避するスクリプトを実装してみました。
※一部の機能しか使えません。
ライブラリ:discord.js(webpackブランチ)
ブラウザ:Microsoft Edge
Webサーバー:Visual Studio Code + Live Server
Node.jsは使用しません。
何が問題?
discord.jsのドキュメントのExampleソースコードを実行します。
https://discord.js.org/#/docs/main/stable/topics/web
すると早速ブラウザのコンソールに次のエラーが出てしまいます。
Access to fetch at 'https://discord.com/api/v7/gateway/bot' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
原因
Discordのドキュメントによると、HTTP APIには有効なUser-Agentを送らなければならないようです。
https://discord.com/developers/docs/reference#user-agent
Clients using the HTTP API must provide a valid User Agent which specifies information about the client library and version in the following format:
User Agent Example
User-Agent: DiscordBot ($url, $versionNumber)
よって、それ以外のUser-Agentの場合は未定義です。(User-AgentによってAPIが使えなかったり使えたりしました。)
ブラウザのUser-Agentでエラーになるのはバグではなく仕様ですね。
ぶっちゃけ、ブラウザの開発者ツールでUser-Agentを設定してしまえば直ります。
エラー回避方法
discord.jsを読み込む前に次のコードを実行します。
const FETCH = fetch;
window.fetch = async (input, init) => {
if (input === 'https://discord.com/api/v7/gateway/bot') {
const data = {
url: 'wss://gateway.discord.gg',
session_start_limit: {},
};
const headers = {
'Content-Type': 'application/json',
};
return new Response(JSON.stringify(data), { headers });
}
return FETCH(input, init);
};
とりあえずこれでExampleソースコードが動いてメッセージを受信できるようになりました。
使えるのは一部の機能のみで、残念ながらエラー回避できない機能があります。
メッセージの送信はWebhookを代用します。
https://discord.js.org/#/docs/main/stable/examples/webhook
解説
DiscordのAPIは2つあります。
- HTTP API
- Gateway (WebSocket) API
discord.jsはまずHTTP APIにアクセスして、Gateway APIのURLを受け取ってからGatewayに接続しています。
Gateway APIにはUser-Agentの仕様がないのでブラウザからもアクセス可能です。
そこで上記のソースコードで細工して、最初のHTTP APIのアクセスでアクセスせずにGatewayのURLを受け取ったことにしています。
するとGatewayには接続できるので、Gatewayを使う一部機能のみが使えます。
ちなみに、discord.jsではGateway APIのURLを/gateway/bot
で取得していますが、/gateway
でも取得できます。
/gateway
はブラウザからもアクセスできました。認証不要なので例外なのかもしれません。
https://discord.com/developers/docs/topics/gateway#get-gateway
なので/gateway
から取得するようにしても良いと思います。
ただしエラーも想定した方が良いでしょう。
WebhookはHTTP APIに含まれそうな気がしますが、GitHubなどの連携サービスはおそらく有効なUser-Agent(DiscordBot)を送ってこないので、連携サービスからのアクセスを受け付けるために例外なのかもしれません。
JavaScriptでUser-Agentを変更できないの?
Edgeでは変更できませんでした。
仕様としてはUser-Agentを変更でき、Firefoxでは変更できるようです。
https://developer.mozilla.org/ja/docs/Glossary/Forbidden_header_name
注: User-Agent ヘッダーは仕様としては禁止ではなくなりました (Firefox 43 で実装された forbidden header name list を参照)。 Fetch の Headers オブジェクトや、XHR の setRequestHeader() などでこのヘッダーを設定することが可能です。
ということで、消してしまったFirefoxをインストールして検証してみます。
const FETCH = fetch;
window.fetch = (input, init) => {
init.headers['User-Agent'] = 'DiscordBot';
return FETCH(input, init);
};
コンソールに次のエラーが出ました。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://discord.com/api/v7/gateway/bot にあるリモートリソースの読み込みは拒否されます (理由: CORS プリフライト応答からのヘッダー ‘Access-Control-Allow-Headers’ によりヘッダー ‘user-agent’ が許可されていない)。
Discord APIからのレスポンスヘッダーには下記のAccess-Control-Allow-Headersがありました。
access-control-allow-headers: Content-Type, Authorization, X-Track, X-Super-Properties, X-Context-Properties, X-Failed-Requests, X-Fingerprint, X-RPC-Proxy, X-Debug-Options, x-client-trace-id, If-None-Match, X-RateLimit-Precision
確かに、User-Agentは許可されているヘッダーに含まれていませんでした。
ダメみたいですね。
FAQ
discord.jsとDiscord Official API DocumentationのIssuesで見つけたブラウザ関連の質問と答えを超簡単にまとめました。
Q. [discord.js]Discord APIへのアクセスで403レスポンスが来る
https://discord.com/api/v7/gateway/bot
へのアクセスで常に403レスポンスが来る。
2020/11/7に廃止されるdiscordapp.com
に変更するしか接続方法がない。
A. discord.jsではどうにもできません。(超要約)
Q. [Discord]APIがCORSに対応していない
A. CORSに対応しています。何らかのエラーがあるとCORSエラーになります。
Q. [Discord]ブラウザのUser-Agentを送るとエラーになる
A. User-Agentのドキュメントを参照してください。 https://discord.com/developers/docs/reference#user-agent
まとめ
私(CORS)は許そう
だがこいつ(User-Agent)が許すかな!