VRChat API使ってみた

# はじめに
皆さんは[VRChat](
https://hello.vrchat.com/)というゲームを知っていますか？VRの中で色んな人とおしゃべりをするゲーム（SNS?）です。

このゲーム、コミュニティ主導のプロジェクト（公式ではない）として、APIが存在します。
本記事ではこのAPI利用について、Pythonを用いた実装方法を共有します。

![header.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974522/f056b6ab-65b8-c735-66d1-4736d8c4f7ff.jpeg)

# なにができるの？
例えば以下に挙げるような内容を**ゲームの中からではなく、APIから**実行可能となります
- フレンドのステータス確認
- リクエストインバイトの承認
- ワールド情報の取得

皆さんがよく利用している、[OyasumiVR](https://github.com/Raphiiko/OyasumiVR/blob/develop/docs/readmes/generated/README_JA.md)や[VRCX](https://github.com/vrcx-team/VRCX)もVRChat APIが組み込まれ、利用されています。

# 実際に使ってみよう
APIのドキュメントは[こちら](https://vrchatapi.github.io/docs/api/)に詳しく記載されています。
もちろんAPIをそのままPostman等で利用することも可能ですが、言語によってはライブラリが提供されています。今回は[Pythonのライブラリ](https://github.com/vrchatapi/vrchatapi-python)を用いて実装していきます。


### ライブラリの導入
いつものやつ
```zsh
pip install vrchatapi
```
### ユーザーログイン
APIの情報を取得するには予めログインしておく必要があります。
以下のコードで認証済みのインスタンスを作成することができます。
（`username`がメールアドレスであることを想定しています。）
ここで使用する`username`と`password`は、画像で示すような普段HPにログインする情報を用いると上手くいくと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974522/f11cd374-b17c-882c-1107-56d6970dd77f.png)

```python
import vrchatapi
from vrchatapi.api import authentication_api
from vrchatapi.exceptions import UnauthorizedException
from vrchatapi.models.two_factor_auth_code import TwoFactorAuthCode
from vrchatapi.models.two_factor_email_code import TwoFactorEmailCode

configuration = vrchatapi.Configuration(
    username = username, # e-mail
    password = password,
)
api_client = vrchatapi.ApiClient(configuration)

api_client.user_agent = f"Mozilla/5.0 {username}"
auth_api = authentication_api.AuthenticationApi(api_client)
try:
    current_user = auth_api.get_current_user()
# 初回は2FAが有効になっているため、2FAのコードを入力する
except UnauthorizedException as e:
    if e.status == 200:
        auth_api.verify2_fa_email_code(two_factor_email_code=TwoFactorEmailCode(input("Email 2FA Code: ")))
    elif "2 Factor Authentication" in e.reason:
        auth_api.verify2_fa(two_factor_auth_code=TwoFactorAuthCode(input("2FA Code: ")))
    else:
        print("Exception when calling API: %s\n", e)
except vrchatapi.ApiException as e:
    print("Exception when calling API: %s\n", e)

```

### 利用してみよう
正常にログインできると、先ほどの`api_client`を用いて実際にAPIを利用することができます。

:::note warn
60秒毎に1回以上のクエリ実行を行わないようにしましょう
:::

#### ユーザー情報取得

```python
from vrchatapi.api import users_api
user_id = "usr_xxxxxxxx"
users_api = users_api.UsersApi(api_client)
user = users_api.get_user(user_id=user_id)
print(user)
```
以下にレスポンス例の一部を示しました。
`bio`や`note`、`status_description`はHPからでもよく見る内容ですね。
`current_avatar_image_url`からはアバターの写真を確認することができます。
このユーザーは緑ステータスでプラべに籠ってアバター改変していることが分かりますね！

```json
{
    "bio": "人が多い場所では基本無言です",
    "current_avatar_image_url": "https://api.vrchat.cloud/api/1/file/file_xxxxxxxx/2/file",
    "display_name": "hiyori",
    "id": "usr_xxxxxxxx",
    "is_friend": true,
    "location": "private",
    "note": "ポピ横で出会った",
    "state": "online",
    "status": "active",
    "status_description": "アバター改変中",
    "world_id": "private"
}
```

<details>
  <summary>レスポンス全体</summary>

```json
{
    "allow_avatar_copying": true,
    "badges": [],
    "bio": "人が多い場所では基本無言です",
    "bio_links": ["https://x.com/xxxxxxx"],
    "current_avatar_image_url": "https://api.vrchat.cloud/api/1/file/file_xxxxxxxx/2/file",
    "current_avatar_tags": [],
    "current_avatar_thumbnail_image_url": "https://api.vrchat.cloud/api/1/image/file_xxxxxxxx/2/256",
    "date_joined": "2024-01-01",
    "developer_type": "none",
    "display_name": "hiyori",
    "friend_key": "xxxxxxxx",
    "friend_request_status": "completed",
    "id": "usr_xxxxxxxx",
    "instance_id": "private",
    "is_friend": true,
    "last_activity": "2024-12-09T13:00:00.000Z",
    "last_login": "2024-12-09T09:00:00.000Z",
    "last_mobile": null,
    "last_platform": "standalonewindows",
    "location": "private",
    "note": "ポピ横で出会った",
    "platform": "standalonewindows",
    "profile_pic_override": "",
    "profile_pic_override_thumbnail": "",
    "pronouns": "",
    "state": "online",
    "status": "active",
    "status_description": "アバター改変中",
    "tags": [
        "language_jpn",
        "system_world_access",
        "system_avatar_access",
        "system_trust_basic",
        "system_trust_known",
        "system_feedback_access",
        "system_trust_trusted",
        "system_trust_veteran",
        "language_eng"
    ],
    "traveling_to_instance": "private",
    "traveling_to_location": "private",
    "traveling_to_world": "private",
    "user_icon": "",
    "username": null,
    "world_id": "private"
}
```
</details>

#### ワールド情報取得
```python
from vrchatapi.api import worlds_api
world_id = "wrld_beddab1e-fee1-cafe-f00d-ca7c0dd1eca7"
worlds_api = worlds_api.WorldsApi(api_client)
world = worlds_api.get_world(world_id=world_id)
print(world)
```
以下にレスポンス例の一部を示しました。
今回はpublicに公開されているSuRoomの情報を取得してみました。
`image_url`からはSuRoomのサムネイルが、`instances`からは現在のSuRoomのインスタンス情報を確認できますね！
```json
{
   "author_id": "usr_0d262187-c8f4-43f1-b78f-d1618fc9a1c3",
   "author_name": "citrusxr",
   "capacity": 32,
   "description": "Experience true VR home world. Multilingual supported. created by : CITRUS Studio members：[\"Shatoo\", \"ureishi\", \"Rastvas\", \"Izzy_da\"]",
   "favorites": 94748,
   "featured": false,
   "heat": 7,
   "id": "wrld_beddab1e-fee1-cafe-f00d-ca7c0dd1eca7",
   "image_url": "https://api.vrchat.cloud/api/1/file/file_0043cc43-931c-4c4e-87a0-2e0e4622d320/3/file",
   "instances": [
       ["36000~region(jp)", 20],
       ["61000~region(jp)", 1],
       ["92000~region(jp)", 2],
       ["71000~region(jp)", 5],
       ["13000~region(jp)", 6],
       ["11000~region(jp)", 7]
   ],
   "name": "SuRroom",
   "organization": "vrchat",
   "release_status": "public",
   "thumbnail_image_url": "https://api.vrchat.cloud/api/1/image/file_0043cc43-931c-4c4e-87a0-2e0e4622d320/3/256",
}
```
実際のSuRoomのHPから得られる情報とほぼ一致しているかと思います
（少しずれているのは取得時間の問題）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974522/a67e1518-0532-0cd7-db83-90e1037963ab.png)


<details>
  <summary>レスポンス全体</summary>
  
```json
{
   "author_id": "usr_0d262187-c8f4-43f1-b78f-d1618fc9a1c3",
   "author_name": "citrusxr",
   "capacity": 32,
   "created_at": "2022-12-19T13:01:34.364000+00:00",
   "description": "Experience true VR home world. Multilingual supported. created by : CITRUS Studio members：[\"Shatoo\", \"ureishi\", \"Rastvas\", \"Izzy_da\"]",
   "favorites": 94748,
   "featured": false,
   "heat": 7,
   "id": "wrld_beddab1e-fee1-cafe-f00d-ca7c0dd1eca7",
   "image_url": "https://api.vrchat.cloud/api/1/file/file_0043cc43-931c-4c4e-87a0-2e0e4622d320/3/file",
   "instances": [
       ["36004~region(jp)", 2],
       ["61520~region(jp)", 14],
       ["92575~region(jp)", 2],
       ["71747~region(jp)", 1],
       ["13460~region(jp)", 1],
       ["11783~region(jp)", 1]
   ],
   "labs_publication_date": "2023-03-17T13:40:55.999Z",
   "name": "SuRroom",
   "namespace": null,
   "occupants": 557,
   "organization": "vrchat",
   "popularity": 9,
   "preview_youtube_id": null,
   "private_occupants": 511,
   "public_occupants": 46,
   "publication_date": "2023-03-17T13:40:56.019Z",
   "recommended_capacity": 16,
   "release_status": "public",
   "tags": [
       "author_tag_home",
       "author_tag_relax",
       "author_tag_party",
       "author_tag_sleep",
       "admin_onboarding_japan",
       "system_approved"
   ],
   "thumbnail_image_url": "https://api.vrchat.cloud/api/1/image/file_0043cc43-931c-4c4e-87a0-2e0e4622d320/3/256",
   "udon_products": [],
   "unity_packages": [
       {
           "asset_url": "https://api.vrchat.cloud/api/1/file/file_0f4d90d3-0e01-4286-84ae-275f64567c83/55/file",
           "asset_version": 4,
           "created_at": "2023-09-10T05:01:25.520000+00:00",
           "id": "unp_490f9905-5e31-4d2a-8aac-534b8f49ddc2",
           "platform": "standalonewindows",
           "unity_version": "2019.4.31f1"
       },
       {
           "asset_url": "https://api.vrchat.cloud/api/1/file/file_ede24b0c-e349-4c94-a603-eea05b83d93a/232/file",
           "asset_version": 1,
           "created_at": "2023-12-28T04:11:11.209000+00:00",
           "id": "unp_e15a3397-a1da-4f1e-8d9c-8dc1fc025e04",
           "platform": "standalonewindows",
           "unity_version": "2019.4.31f1"
       },
       {
           "asset_url": "https://api.ierzchat.cloud/api/1/file/file_ede24b0c-e349-4c94-a603-eea05b83d93a/287/file",
           "asset_version": 4,
           "created_at": "2024-05-01T04:17:44.310000+00:00",
           "id": "unp_6b7c3881-ef91-4fcf-b4f1-40f99c7494f6",
           "platform": "standalonewindows",
           "unity_version": "2022.3.6f1"
       },
       {
           "asset_url": "https://api.vrchat.cloud/api/1/file/file_ede24b0c-e349-4c94-a603-eea05b83d93a/399/file",
           "asset_version": 4,
           "created_at": "2024-11-24T14:41:57.095000+00:00",
           "id": "unp_68276bd4-63b7-4867-aa1d-a685208c1950",
           "platform": "standalonewindows",
           "unity_version": "2022.3.22f1",
           "world_signature": "AORwBdPa0kezOjcnONA+bqJo3t8tYTYjpvwW6nOl8bAHGBq8zw=="
       }
   ],
   "updated_at": "2024-11-24T14:41:57.320000+00:00",
   "version": 171,
   "visits": 5870877
}
```
</details>

#### ちなみに
以上で使用している`user_id`や`world_id`の情報は、APIのレスポンス情報の他にブラウザからも取得できます（デベロッパーモードのNetworkタブ）。


![プレゼンテーション1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974522/23b82e58-57e0-30ca-a1bd-9a73df327f7c.png)


# まとめ
VRChatには非公式ですがAPIが存在し、ライブラリを通じて簡単に利用できます。
リクイン承認の自動化や、フレンドのステータス変更に伴う通知機能など多くのサービスに応用できるので、ぜひ試してみてはいかがでしょうか？