VRChatAPIでget_user()を実施した場合に発生する形式チェックエラーの原因と対応ついて

:::note alert
# 注意 Caution
記載内容についてVRChat側に問い合わせすることは**厳禁**です。
記載内容についての責任は全てかずやまにあります。

It is **strictly forbidden** to contact VRChat regarding the contents of the description.
かずやま is responsible for the contents described here.
:::
# 概要
VRCharAPIでユーザー情報を取得する場合に発生するエラーの原因と対応について記載します。
Python用のAPIライブラリを使用した場合であり、他の方法については言及していません。
対象APIは vrchatapi.UsersApi の get_user() メソッドです。
この資料は2023-08-29時点、対象ソースは2023-05-31時点の物です。

# 原因について
get_user() でユーザー情報を取得する場合、取得内容のうち instance_id、world_id、location の形式が正しいかをチェックしています。
しかし、この資料を作成時点では実際に取得する内容とチェック内容が一致しておらず、そのため ValueError という例外が発生します。

# 対応について
チェックは取得したユーザーに関する情報の文字列中に想定された値がが存在するかの確認で行っているため、調査時点で実際に取得する内容に合わせてチェック内容を変更します。
チェックする変数は location, world_id, instance_id の３個です。

## locationのチェック対応
変更前のチェックは下記の文字列が存在することを正常としています。
+ 空文字
+ offline
+ ワールドID("wrld"または"wld")+"_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")

実際は上記に加え下記の文字列も存在します。
+ private

チェックは正規表現で検索しているのでその部分を下記のように変更します。
`'(^$|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})'`
↓ privateを追加
`'(^$|offline|private|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})'`

補足：取得文字列がワールドIDで始まる場合はその後に後期のinstance_idの情報を示す文字列が続きます。詳細については補足１を参照ください。

## world_idのチェック対応
変更前のチェックは下記の文字列が存在することを正常としています。
・空文字
・offline
・ワールドID("wrld"または"wld")+"_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")

実際は下記の文字列があることも正常としています。
・private

チェックは正規表現で検索しているのでその部分を下記のように変更します。
`'(^$|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})'`
↓ privateを追加
`'(^$|offline|private|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})'`
## instance_idのチェック対応
変更前のチェックは下記の文字列が存在することを正常としています。
・空文字
・offline
・private
・ワールドID("wrld"または"wld")+"_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:")+インスタンスID(数値)+各種属性パラメータ
※詳しくは下記正規表現参照

実際はワールドID部分がなくインスタンスID以降の文字列、つまりlocationの文字列からワールドIDを除いた文字列をチェックするようです。
しかし、WEB画面からインスタンスを開いた場合などは全く違う内容になるため形式立ったチェックが不可能です。ゆえに、ユーザー情報取得時のチェックは行わないように修正します。
何らかのチェックが必要な場合は、使用する場面ごとに行うことになる想定です。

チェックは下記正規表現で検索していますが、チェック自体をなくします。
`'(private|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:(\d+)(~region\(([\w]+)\))?(~([\w]+)\(usr_([\w-]+)\)((\~canRequestInvite)?)(~region\(([\w].+)\))?~nonce\((.+)\))?)'`

# 補足１:locationの例
```
wrld_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:11678~region(jp)
wrld_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:11009~friends(usr_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)~region(use)
wrld_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:47866~hidden(usr_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)~region(jp)
wrld_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:63162~private(usr_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)~region(jp)
wrld_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:51689~private(usr_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)~canRequestInvite~region(jp)
wrld_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:80359~group(grp_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)~groupAccessType(members)~region(jp)
```
形式としては最初にワールドID+インスタンスID(数字以外もありうる)が続いてワールドインスタンスを表します。
その後にインスタンスの属性を表す下記のようなパラメータが続きます。
・インスタンスタイプ(無し：Public、friends:Frends+,hidden:Frends,private:Invite or Invite+ ,Group:group等)+オーナーID ※privateは自身を検索した場合のみです。
・インスタンスタイプごとの属性(canRequestInvite, groupAccessType等)
・リージョン(region(地域))
※現時点で存在が確認されているパターンで、全ての例を示してはいません。
※特にインスタンスID以降はユーザーによる任意の指定になる可能性があります。

# 補足2：別案
self.local_vars_configuration.client_side_validation に False を設定することで内容チェックをスキップすることもできそうである。
但し、その方法についての調査は行っていません。
知っている方が居たら教えてください。

# 補足３：関連資料
VRChatAPIのユーザー情報取得APIのドキュメント:
https://github.com/vrchatapi/vrchatapi-python/blob/main/docs/UsersApi.md

修正対象のソース:
https://github.com/vrchatapi/vrchatapi-python/blob/main/vrchatapi/models/user.py
差分
```diff_python
--- user_ORG.py 2023-08-29 07:54:41.858959243 +0900
+++ user.py     2023-08-29 17:23:15.286387576 +0900
@@ -460,9 +460,6 @@
         :param instance_id: The instance_id of this User.  # noqa: E501
         :type instance_id: str
         """
-        if (self.local_vars_configuration.client_side_validation and
-                instance_id is not None and not re.search(r'(private|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:(\d+)(~region\(([\w]+)\))?(~([\w]+)\(usr_([\w-]+)\)((\~canRequestInvite)?)(~region\(([\w].+)\))?~nonce\((.+)\))?)', instance_id)):  # noqa: E501
-            raise ValueError(r"Invalid value for `instance_id`, must be a follow pattern or equal to `/(private|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:(\d+)(~region\(([\w]+)\))?(~([\w]+)\(usr_([\w-]+)\)((\~canRequestInvite)?)(~region\(([\w].+)\))?~nonce\((.+)\))?)/`")  # noqa: E501

         self._instance_id = instance_id

@@ -587,7 +584,7 @@
         :type location: str
         """
         if (self.local_vars_configuration.client_side_validation and
-                location is not None and not re.search(r'(^$|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', location)):  # noqa: E501
+                location is not None and not re.search(r'(^$|offline|private|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', location)):  # noqa: E501
             raise ValueError(r"Invalid value for `location`, must be a follow pattern or equal to `/(^$|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/`")  # noqa: E501

         self._location = location
@@ -860,7 +857,7 @@
         :type world_id: str
         """
         if (self.local_vars_configuration.client_side_validation and
-                world_id is not None and not re.search(r'(^$|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', world_id)):  # noqa: E501
+                world_id is not None and not re.search(r'(^$|offline|private|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', world_id)):  # noqa: E501
             raise ValueError(r"Invalid value for `world_id`, must be a follow pattern or equal to `/(^$|offline|(wrld|wld)_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/`")  # noqa: E501

         self._world_id = world_id
```