From 65790f982f78db28d3c5515fc37405bd2c789b1b Mon Sep 17 00:00:00 2001 From: Vinyarion <38413862+VinyarionHyarmendacil@users.noreply.github.com> Date: Fri, 3 May 2024 21:17:04 -0500 Subject: [PATCH] Updated tutorials related to instances (#144) Changes to Pipeline events Added support for Group Public, Group+, and Group instances to instance generator widget --- content/tutorials/instances.markdown | 13 +++- content/tutorials/websocket.markdown | 83 ++++++++++++++-------- layouts/shortcodes/instance_generator.html | 62 +++++++++++++--- 3 files changed, 118 insertions(+), 40 deletions(-) diff --git a/content/tutorials/instances.markdown b/content/tutorials/instances.markdown index e575a6b..e2657fc 100644 --- a/content/tutorials/instances.markdown +++ b/content/tutorials/instances.markdown @@ -4,10 +4,12 @@ category: general difficulty: medium --- -Instances are parallell "rooms" or "lobbies" of a world. +Instances are parallel "rooms" or "lobbies" of a world. Each instance can only hold a limited number of users, but there is no limit on how many instances can exist of a world. Instances are uniquely identified by the combined World ID and Instance ID. +*Note: as of 2024-05-02, VRChat indicated in [a Developer Update](https://ask.vrchat.com/t/developer-update-2-may-2024/24284#changes-to-instance-apis-and-auto-creation-13) an eventual intent to replace the current system with a UUID-ish system similar to User IDs* + ## Instance Generator {{< instance_generator >}} @@ -37,3 +39,12 @@ USA, West | San José | us USA, East | Washington D.C. | use Europe | Amsterdam | eu Japan | Tokyo | jp + +### Special Values + +The VRChat API has several sentinel values for location strings: + +- `""` Pseudo-null value +- `"offline"` Implies a user currently is not either running the VRChat client or connected to the Pipeline (e.g., browser tab open) +- `"traveling"` Indicates a user's client is travelling between instances (e.g., downloading world, synchronizing world state) +- `"private"` Indicates a user's location is not visible to the currently logged-in user. (e.g., Ask Me/Do Not Disturb status, Invite/Invite+/Group instance) diff --git a/content/tutorials/websocket.markdown b/content/tutorials/websocket.markdown index 401a943..43b7154 100644 --- a/content/tutorials/websocket.markdown +++ b/content/tutorials/websocket.markdown @@ -28,6 +28,36 @@ It is possible to be connected from multiple locations at the same time. All cli > } > ``` +## Note on Enumerations + +Several JSON string values present in both Websocket API messages and HTTP API responses appear to be contained in predictably consistent sets. +Throughout both APIs, the empty string `""` is returned in places (including the immediately) where it would seem otherwise reasonable to have a `null` or undefined value. +In this part of the documentation, the following `":identifier"`s will be used to describe possible enumeration-ish values: + +- `":locationString"` + - `""` Pseudo-null value + - `"offline"` Implies a user currently is not either running the VRChat client or connected to the Pipeline (e.g., browser tab open) + - `"traveling"` Indicates a user's client is travelling between instances (e.g., downloading world, synchronizing world state) + - `"private"` Indicates a user's location is not visible to the currently logged-in user. (e.g., Ask Me/Do Not Disturb status, Invite/Invite+/Group instance) + - *other values* An actual location (see https://vrchatapi.github.io/tutorials/instances/) +- `":platformString"` + - `""` Pseudo-null value + - `"standalonewindows"` + - `"android"` + - `"web"` User is on a https://vrchat.com/home page + - *other values* Some other platform or third-party application (e.g., `"ios"` could be the value for a future build on some Apple devices) +- `":contentRefreshContentTypeEnum"` + - `"gallery"` + - `"icon"` + - `"emoji"` + - `"avatar"` + - `"world"` + - *other values* Some other user-uploaded content +- `":contentRefreshActionTypeEnum"` + - `"created"` + - `"deleted"` + - *other values* Not expected + ## Events ### Notification Events @@ -209,13 +239,13 @@ A "`friend-online`" event is sent when one of the user's friends has gone online "type": "friend-online", "content": { "userId": ":userId", + "platform": ":platformString", + "location": ":locationString", + "canRequestInvite": , "user": { // , See return data of User API: // https://vrchatapi.github.io/docs/api/#get-/users/-userId- - }, - "location": ":locationString", // Refer to https://vrchatapi.github.io/tutorials/instances/ - "instance": ":instanceId", // This is locationString without the World ID part. - "canRequestInvite": + } } } ``` @@ -228,6 +258,7 @@ A "`friend-active`" event is sent when one of the user's friends is active on th "type": "friend-active", "content": { "userid": ":userId", + "platform": ":platformString", // Appears only to be "web" from these events "user": { // , See return data of User API: // https://vrchatapi.github.io/docs/api/#get-/users/-userId- @@ -243,7 +274,8 @@ A "`friend-offline`" event is sent when one of the user's friends has gone offli { "type": "friend-offline", "content": { - "userId": ":userId" + "userId": ":userId", + "platform": ":platformString", // Appears only to be empty "" from these events } } ``` @@ -266,22 +298,21 @@ A "`friend-update`" event is sent when something about one of the user's friends #### friend-location -A "`friend-location`" event is sent when one of the user's friends has changed instances. Note that the "`world`" field will be an empty object when the friend is on orange/red, or is in a private world. +A "`friend-location`" event is sent when one of the user's friends has changed instances. Note that the `worldId` field will be `"private"` when the friend is on orange/red, or is in a private world. ```json { "type": "friend-location", "content": { "userId": ":userId", + "location": ":locationString", + "travelingToLocation": ":locationString", // normally empty "", but when the above "location" is "traveling", this contains the imminent destination + "worldId": ":worldId", + "canRequestInvite": , "user": { // , See return data of User API: // https://vrchatapi.github.io/docs/api/#get-/users/-userId- - }, - "location": ":locationString", // Refer to https://vrchatapi.github.io/tutorials/instances/ - "travelingToLocation": ":locationString", // Refer to https://vrchatapi.github.io/tutorials/instances/ - "instance": ":instanceId", // This is locationString without the World ID part. - "worldId": ":worldId", // wlrd_... - "canRequestInvite": + } } } ``` @@ -335,9 +366,9 @@ A "`user-location`" event is sent when the user has changed instances. // , See return data of User API: // https://vrchatapi.github.io/docs/api/#get-/users/-userId- }, - "location": ":locationString", // Refer to https://vrchatapi.github.io/tutorials/instances/ + "location": ":locationString", "instance": ":instanceId", // This is locationString without the World ID part. - "worldId": ":worldId", // wlrd_... + "worldId": ":worldId", "world": { // , See return data of World API: // https://vrchatapi.github.io/docs/api/#get-/worlds/-worldId- @@ -354,8 +385,8 @@ A "`content-refresh`" event is sent when the user adds or removes profile images { "type": "content-refresh", "content": { - "contentType": ":contentRefreshContentTypeEnum", // One of: "gallery", "icon", "emoji", "avatar", "world", ??? - "actionType": ":contentRefreshActionTypeEnum" // One of: "created", "deleted", ??? + "contentType": ":contentRefreshContentTypeEnum", + "actionType": ":contentRefreshActionTypeEnum" } } ``` @@ -368,7 +399,7 @@ A "`instance-queue-joined`" event is sent when the user queues to join an instan { "type": "instance-queue-joined", "content": { - "instanceLocation": ":locationString", // Refer to https://vrchatapi.github.io/tutorials/instances/ + "instanceLocation": ":locationString", "position": // Integer position in queue } } @@ -382,7 +413,7 @@ A "`instance-queue-ready`" event is sent when the user is at the front of the qu { "type": "instance-queue-ready", "content": { - "instanceLocation": ":locationString", // Refer to https://vrchatapi.github.io/tutorials/instances/ + "instanceLocation": ":locationString", "expiry": ":dateTimeString" // Time at which priority will be lost } } @@ -419,25 +450,15 @@ A "`group-left`" event is sent when the user has either left a group, or has bee #### group-member-updated -A "`group-member-updated`" event is sent when something regarding the user's group membership changes. Note that the `member` object is **not** a full GroupMember object, even though it has similarities. It's missing `user`, `createdAt`, `bannedAt`, and `managerNotes`. It also has the extra `lastPostReadAt` field. +A "`group-member-updated`" event is sent when something regarding the user's group membership changes. Note that the optional values in the `member` are as if "fetching a specific user", per the schema description. ```json { "type": "group-member-updated", "content": { "member": { - "id": ":groupMemberId", // gmem_00000000-0000-0000-000000000000 - "groupId": ":groupId", - "userId": ":userId", - "isRepresenting": , - "roleIds": [ - ":groupRoleId" // grol_00000000-0000-0000-000000000000 - ], - "joinedAt": ":dateTimeString", // yyyy-mm-ddThh:mm:ss.sssZ - "membershipStatus": ":groupMembershipEnum", // One of: "member", ??? - "visibility": ":groupVisibilityEnum", // One of: "visible", ??? - "isSubscribedToAnnouncements": , - "lastPostReadAt": ":dateTimeString" // NOTE: WILL BE NULL if the user hasn't read any group posts + // , See return data of Groups API: + // https://vrchatapi.github.io/docs/api/#get-/groups/-groupId-/members/-userId- } } } diff --git a/layouts/shortcodes/instance_generator.html b/layouts/shortcodes/instance_generator.html index 4c3f12c..fd338c2 100644 --- a/layouts/shortcodes/instance_generator.html +++ b/layouts/shortcodes/instance_generator.html @@ -45,6 +45,9 @@ + + + @@ -66,7 +69,7 @@
- Must be a valid User ID. + Must be a valid User ID or Group ID.
@@ -143,6 +146,7 @@ nonceElement.value = uuidv4(); } + let ownerIdMatchesGroupID = (ownerId.match(/^(grp_[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12})$/) || [])[1] == undefined; switch (instanceType) { case "public": ownerIdElement.disabled = true; @@ -154,6 +158,20 @@ case "invite": ownerIdElement.disabled = false; nonceElement.disabled = true; + if (!ownerIdMatchesGroupID) { + output.innerHTML = "Invalid User ID."; + return; + } + break; + case "grouppublic": + case "group+": + case "group": + ownerIdElement.disabled = false; + nonceElement.disabled = true; + if (ownerIdMatchesGroupID) { + output.innerHTML = "Invalid Group ID."; + return; + } break; } @@ -163,7 +181,8 @@ } let res_type = ""; - let res_hidden = ""; + let res_owner = ""; + let res_gaccess = ""; let res_nonce = ""; let res_canRequestInvite = false; @@ -177,14 +196,14 @@ ownerIdElement.disabled = false; nonceElement.disabled = true; res_type = "hidden"; - res_hidden = ownerId; + res_owner = ownerId; res_nonce = nonce; break; case "friends": ownerIdElement.disabled = false; nonceElement.disabled = true; res_type = "friends"; - res_hidden = ownerId; + res_owner = ownerId; res_nonce = nonce; break; case "invite+": @@ -192,14 +211,38 @@ nonceElement.disabled = true; res_type = "private"; res_canRequestInvite = true; - res_hidden = ownerId; + res_owner = ownerId; res_nonce = nonce; break; case "invite": ownerIdElement.disabled = false; nonceElement.disabled = true; res_type = "private"; - res_hidden = ownerId; + res_owner = ownerId; + res_nonce = nonce; + break; + case "grouppublic": + ownerIdElement.disabled = false; + nonceElement.disabled = true; + res_type = "group"; + res_owner = ownerId; + res_gaccess = "public"; + res_nonce = nonce; + break; + case "group+": + ownerIdElement.disabled = false; + nonceElement.disabled = true; + res_type = "group"; + res_owner = ownerId; + res_gaccess = "plus"; + res_nonce = nonce; + break; + case "group": + ownerIdElement.disabled = false; + nonceElement.disabled = true; + res_type = "group"; + res_owner = ownerId; + res_gaccess = "members"; res_nonce = nonce; break; } @@ -207,8 +250,11 @@ let result = ""; result += "worldId=" + worldId + ""; result += "&instanceId=" + instanceId + ""; - if (res_hidden != "") { - result += "~" + res_type + "(" + res_hidden + ")"; + if (res_owner != "") { + result += "~" + res_type + "(" + res_owner + ")"; + } + if (res_gaccess != "") { + result += "~groupAccessType(" + res_gaccess + ")"; } if (res_canRequestInvite) { result += "~canRequestInvite";