原神のAPIを使って通知を送信するようにする

※ 先に断っておきますが、これを真似をして後で公式から怒られても責任は取りません。

題名の通りです。
原神のプレイヤー情報が取得できるエンドポイントを叩いて、ゲーム情報を取得します。
取得された情報によって、自動でLINE通知を送ってもらいます。

### やりたいこと

下記の条件の1つでも満たしたら、LINE通知を送信するようにしたいです。

- 天然樹脂が溢れそうである
- 洞天集宝盆が溢れそうである
- 探索派遣が完了した
- 参量物質変化器が再使用可能になった
- 夜になったのにまだデイリー任務が終わっていない

公式の原神のゲーム内にも通知の設定がありますが、その通知は天然樹脂が溢れた通知です。
溢れてからでは意味がありません。溢れる前に通知してもらいます。（廃人脳）

また、洞天集宝盆や参量物質変化器の通知は公式が対応していません。
一部はHoYoLABを見れば確認ができますが、こちらから能動的に開いてチェックするのは面倒なものです。

push型のモニタリングツールのように、向こうから知らせに来てもらいましょう。

### 事前準備

ゲーム情報を取得する外部APIを叩くにあたり、下記が必要になります。

- ゲーム内UID
- HoYoLABのログインセッション用cookie

ゲーム内UIDについては8から始まる9桁の番号のあれです。
ゲーム内でも確認することができます。

次にHoYoLABのログインセッション用cookieを取得しにいきます。
まずはEditThisCookie等のcookie情報を取得できるツールを入れておいてください。

https://www.editthiscookie.com/

PC上でHoYoLABにログインします。
（別に原神自体はPC版でプレイしていなくても大丈夫です。）

ログインしたら先ほどのEditThisCookie等を使い、cookie情報を確認します。
cookieのうち `ltoken` と `ltuid` というのがあると思うので、それをメモしておいてください。
後述しますがログアウトはしないでおいて下さい。

これで準備はOKです。

### APIを叩く

原神のAPIといえばEnkaAPIが有名だと思います。
ただこれは今回使いません。

https://github.com/EnkaNetwork/API-docs/blob/master/api_ja.md

というのもEnkaAPIはプレイヤー情報こそ手に入るものの、肝心の天然樹脂や探索派遣のリアルタイムステータスの取得ができません。

なので今回は下記のエンドポイントを使います。

https://bbs-api-os.hoyoverse.com/game_record/genshin/api/dailyNote

どこで見つけたかは覚えていません。適当に掘ってたら出てきました。
下記のようにリクエストします。

```
  const requestOptions = {
    url: 'https://bbs-api-os.hoyoverse.com/game_record/genshin/api/dailyNote',
    method: 'GET',
    params: {
        role_id: [ゲーム内UID],
        server: 'os_asia',
        schedule_type: 1,
    },
    headers: {
        Cookie: `ltoken_v2=[さっき取得したltoken]; ltuid_v2=[さっき取得したltuid]`,
    }
  };

  return axios(options)
        .then((res) => {
            return res.data;
        });
```

そうすると下記のようなレスポンスが得られます。

```
{
  retcode: 0,
  message: 'OK',
  data: {
    current_resin: 57,
    max_resin: 200,
    resin_recovery_time: '68245',
    finished_task_num: 0,
    total_task_num: 4,
    is_extra_task_reward_received: false,
    remain_resin_discount_num: 0,
    resin_discount_num_limit: 3,
    current_expedition_num: 5,
    max_expedition_num: 5,
    expeditions: [
      {
    avatar_side_icon: 'https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_avatar_side_icon_u38d4g/0b19abb2d0d920a62e49c11343fc88d9.png',
        status: 'Ongoing',
        remained_time: '21704'
      },
      ...
    ],
    current_home_coin: 2040,
    max_home_coin: 2400,
    home_coin_recovery_time: '41490',
    calendar_url: '',
    transformer: {
      obtained: true,
      recovery_time: { Day: 6, Hour: 0, Minute: 0, Second: 0, reached: false },
      wiki: '',
      noticed: false,
      latest_job_id: '0'
    },
    daily_task: {
      total_num: 4,
      finished_num: 0,
      is_extra_task_reward_received: false,
      task_rewards: [Array],
      attendance_rewards: [Array],
      attendance_visible: true,
      stored_attendance: '108.3',
      stored_attendance_refresh_countdown: 25021295
    },
    archon_quest_progress: {
      list: [],
      is_open_archon_quest: true,
      is_finish_all_mainline: true,
      is_finish_all_interchapter: true,
      wiki_url: ''
    }
  }
}
```

得られたプロパティのうち、下記のものを使えばやりたいことを実現できそうです。

- current_resin ... 現在の天然樹脂
- current_home_coin ... 現在の洞天集宝盆数
- is_extra_task_reward_received ... デイリー任務報告が終わっているか
- transformer.recovery_time.reached ... 参量物質変化器が使用可能になったか
- expeditions[].status ... 探索派遣の状況 Finishedなら完了している

```

if (current_resin > 180) {
  // 天然樹脂溢れそう
}

if (current_home_coin > 2000) {
  // 洞天集宝盆が溢れそう
}

if (!is_extra_task_reward_received && 現在時刻が21時以降) {
  // 21時になっているのにデイリー任務が完了していない
}

if (transformer.recovery_time.reached) {
  // 参量物質変化器が使用可能になった
}

if (expeditions.filter(ele => ele.status === 'Finished').length > 0) {
  // 完了済みの探索派遣がある
}

// この辺りで いずれか当てはまっていたら通知処理
```

イメージとしては上記です。
ここまでのリクエスト〜レスポンス内容の制御ができればプログラムのフェーズは概ねできたと言えます。

残るはLINE通知と定期実行になります。

### LINE通知

正式名称はLINE Notifyといいます。

https://notify-bot.line.me/ja/

ログインして通知用のトークンを取得します。
あとは公式ドキュメントに沿って処理を書いたら完了です。

```
    const options = {
        url: 'https://notify-api.line.me/api/notify',
        method: 'POST',
        params: {
            message,
        },
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            Authorization: `Bearer [LINE  Notifyのトークン]`,
        },
    };

    return axios(options);
```

特にLINE通知である必要はありません。
discordでも何でもいいです。

むしろLINE通知がサービス終了するとのことなので、discordの方が望ましいです。

### 定期実行

手動で実行して動くだけでは意味がないので、定期実行されるようにします。
具体的には1時間あるいは2時間に1回実行されるように設定できるといいですね。

GCPの無料インスタンス枠でcronを組むか、AWSのLambdaをEvent Bridge（旧CloudWatch Event）で定期実行するのがいいと思います。

前者の利点はなんといってもお金がかからないことです。
間違って無料枠ではないインスタンスを立てない限り、コスト面で悩むことはないでしょう。

https://qiita.com/Brutus/items/22dfd31a681b67837a74

ただ注意点として、無料枠のインスタンスは期限がある（たぶん）と思うので、それが切れるとインスタンスごと削除されてしまいます。
削除されたら作り直せばいい話ですが、期限の3ヶ月ごとくらいにその作業をしなくてはならないので、やや面倒くさいです。

後者だと削除リスクに悩まされることはないですが、お金がどうしても発生してしまいます。
Lambdaは実行時間でコストが発生するので、今回の場合そこまで時間がかかる処理ではないとはいえ、何度も実行されると塵も積もればになります。

2時間あるいは3時間に1回の実行に抑えておくのがいいでしょう。

加えて、Lambda上で動くようなコーディングにする必要があるので、知見がない場合は追加で学習コストが発生します。
デバッグもしにくいです。挑戦的で面白くはありますが、ひとまずまずはGCP無料インスタンス上のcron実行を目指し、必要であればLambdaに移行していく方がいいように感じます。

~~また、Lambda実行の場合は課金をして通知をリッチにしているとも言い換えられるので、無課金でプレイしている方々のポリシーにはそぐわないかもしれません。~~

無料枠ではないインスタンスを立ててcronを実行するのが最も楽な方法ですが、コストが大きくなるためお勧めしません。
既に別の目的でインスタンスを常時起動させており、そこに相乗りする形で実行するなら なんら問題はありません。

### 注意点

HoYoLABからログアウトしないでください。
今回紹介したAPIは、ログインセッションを持っている状態でないときちんとレスポンスが返却されません。
cookieを取得して送信している時点で気づいた方もいると思います。

ログアウトしてしまった場合は、再度ログインしてcookieを取り直してください。

また、cookieの扱いには大変気をつけて下さい。外部に漏れると危険です。
必ずhttpsで暗号化してAPIリクエストをするように実装して下さい。

LINE Notifyが近いうちにサービス終了するとのことなので、別の通知サービスの利用を推奨します。
そもそもサ終するサービスを紹介するなという話ですが、私が作成した時点でそういうのはなかったので、多めに見て下さい。

### 終わりに

私が実際に作成したアプリケーションは下記です。
Lambda + Event Bridge での実行を前提に書いていますが、Linux上のcronでも問題なく動くと思います。

https://github.com/hir-247-30/genshin-report-lambda/

![Screenshot_20241124_205739_LINE.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832619/42c3d379-5818-879d-a976-322ddba7fe65.jpeg)

いい原神ライフを。