Google Apps Script (GAS) から Discord の API にアクセスしてもエラーコード 1020 で失敗してアクセスできない

# 結論

Discord に GAS からアクセスしようとしても User Agent の関係でアクセスはできない。
GAS から何かしたいとしても何らかの迂回路を通らないと上手くいかないだろう。

# 何を試してどうなった

GoogleAppsScript で以下のように記載し実行した所、`403 'error code: 1020'` が出力された。

```javascript
function getChannelInfo(channelId) {
    const url = `https://discordapp.com/api/channels/${channelId}`;
    const response = UrlFetchApp.fetch(url, {
      'method': 'get',
      'headers': {
        'Authorization': getToken()
      },
      'muteHttpExceptions': true
    });

    console.log(response.getResponseCode(), response.getContentText());
}
```

コマンドプロンプトから curl を叩くだけならアクセスはできたし、
他のサーバからプログラムないし curl で Discord にアクセスしても情報の取得ができた。
そのため、権限や Token が違うといった問題ではなさそうである。

# エラーコード1020ってなんだ？

## Discord のエラーコードには 1020 はない

以下は Discord のエラーコード一覧である。

https://discord.com/developers/docs/dispatch/error-codes#error-codes

これを確認すると1020なんてエラーコードは記載されていない。Discord が返却しているエラーではないということだろうか。

## Cloudflare のエラーコードではないか？

Discord は Cloudflare を使っている旨が Cloudflare のケーススタディのページに掲載されている。

https://www.cloudflare.com/case-studies/discord/

ふーん、と思いながら Cloudflare のエラーコード一覧を見ているとエラーコード1020なるものが見つかった。

https://developers.cloudflare.com/support/troubleshooting/cloudflare-errors/troubleshooting-cloudflare-1xxx-errors/#error-1020-access-denied

以上のことから GAS からの API へのアクセスを Discord が遮断しているようだ。

## どうやって GAS からの API へのアクセスを遮断している？

※コメントを頂いたので調べたりして加筆しました。ありがとうございます！

Discord にアクセスする際は以下で説明されているように適切な User Agent を設定する必要がある。

https://discord.com/developers/docs/reference#user-agent

なるほど、これにひっかかっているようだ。

上述のリンク先通り、User Agentの値を設定してあげないといけないので、リクエストを送るときは次のようになる。

```bash
curl -H "User-Agent: DiscordBot (xxxxxxxx, 9)" -H "Authorization:Bot XXXXXXXXXXX" https://discordapp.com/api/guilds/XXXXXXXXXXXXXXXXXXXX/channels
```

はずなのだが…… User Agent が無くても情報を取得できてしまう。

```bash
# こっちでも動いてしまう
curl -H "Authorization:Bot XXXXXXXXXXX" https://discordapp.com/api/guilds/XXXXXXXXXXXXXXXXXXXX/channels
```

なので「実は Cloudflare で User Agent を見ているわけではないのかな」と思っていたがそうでもないようだ。

```bash
curl -H "User-Agent: Mozilla" -H "Authorization:Bot XXXXXXXXXXX" https://discordapp.com/api/guilds/XXXXXXXXXXXXXXXXXXXX/channels
```

などと変えると curl であってもエラーコード1020が得られた。

上述のページに "Client requests that do not have a valid User Agent specified may be blocked and return a Cloudflare error." とあるように、適切な User Agent じゃないなと判断されたら Cloudflare でエラーコード1020の返答を返すようにしているようだ。現時点では全てが全てチェックされているわけではないにせよ、実際にリクエストする場合はちゃんと書いたほうが良い。

### GAS から送る際も User Agent を書けば行けるのでは？

実際、以下のようにして試してみたのだが、結果は変わらなかった。

```javascript
function getChannelInfo(channelId) {
    const url = `https://discordapp.com/api/channels/${channelId}`;
    const response = UrlFetchApp.fetch(url, {
      'method': 'get',
      'headers': {
        'Authorization': getToken(),
        'User-Agent': 'DiscordBot (xxxxxxxx, 9)' // 上に比較して追記した行
      },
      'muteHttpExceptions': true
    });

    console.log(response.getResponseCode(), response.getContentText());
}
```

GAS からのリクエストで User-Agent の記載を変更することはできないらしい。

GAS から手元のサーバにアクセスさせてみて User Agent の値を確認してみたが、上述のようにしたとしても `Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: xxxxxxxxxxxxxxxxxx)`といった値が得られた。

# 対処

GAS からのアクセスに際しては User Agent を変えようがないので GAS 以外のものからアクセスさせる必要がある。なので GAS に載せるのを諦めて他の方法でアクセスさせることになる。

どうしても GAS である必要がある場合でも GAS から直に Discord にアクセスするのは無理である。そこで Discord へのリクエストを中継するサーバの類を用意するといった対処が考えられる。しかし、色々なコストが高くつくためあまり良い案ではないだろう。