From 809ded3f4a30552dbdf4e374d2d5164699c3baee Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Fri, 21 Nov 2025 02:06:35 +1300 Subject: [PATCH 1/7] Add place-level user restriction support Extends the Universes class to allow updating user restrictions at the place level by accepting an optional placeId parameter. Adjusts the resource path logic to handle both universe-level and place-level user restrictions. --- src/resources/universes.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/resources/universes.ts b/src/resources/universes.ts index c8e7a0c..ee3728f 100644 --- a/src/resources/universes.ts +++ b/src/resources/universes.ts @@ -159,6 +159,7 @@ export class Universes { * @param universeId - The universe ID (numeric string) * @param userRestrictionId - The user ID (numeric string) * @param body - The user restriction data to update + * @param placeId - The place ID (optional) (numeric string) * @returns Promise resolving to the user restriction response * @throws {AuthError} If API key is invalid * @throws {OpenCloudError} If an API error occurs @@ -180,6 +181,7 @@ export class Universes { universeId: string, userRestrictionId: string, body: GameJoinRestriction, + placeId?: string, ): Promise { const searchParams = new URLSearchParams(); @@ -191,14 +193,20 @@ export class Universes { searchParams.set("idempotencyKey.key", idempotencyKey); searchParams.set("idempotencyKey.firstSent", firstSent); - return this.http.request( - `/cloud/v2/universes/${universeId}/user-restrictions/${userRestrictionId}`, - { - method: "PATCH", - body: JSON.stringify({ gameJoinRestriction: body }), - searchParams, - }, - ); + let resourcePath = `/cloud/v2/universes/${universeId}`; + + // This resource has two different paths, one for universe-level restrictions, and another for place-level restrictions + if (placeId) { + resourcePath += `/places/${placeId}`; + } + + resourcePath += `/user-restrictions/${userRestrictionId}`; + + return this.http.request(resourcePath.toString(), { + method: "PATCH", + body: JSON.stringify({ gameJoinRestriction: body }), + searchParams, + }); } /** From 1ec8557754fe2e245b3419e5186ab4b7b1e1c150 Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:33:35 +1300 Subject: [PATCH 2/7] Added options to updateUserRestriction --- src/resources/universes.ts | 31 +++++++++++++++++-------------- src/types/universes.ts | 6 ++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/resources/universes.ts b/src/resources/universes.ts index ee3728f..6fd1d12 100644 --- a/src/resources/universes.ts +++ b/src/resources/universes.ts @@ -1,7 +1,6 @@ import { HttpClient } from "../http"; -import { ListOptions } from "../types"; +import { ListOptions, UpdateUserRestrictionOptions } from "../types"; import { - GameJoinRestriction, PublishMessageBody, SpeechAssetBody, SpeechAssetOperation, @@ -156,33 +155,37 @@ export class Universes { * * *Requires `universe.user-restriction:write` scope.* * - * @param universeId - The universe ID (numeric string) * @param userRestrictionId - The user ID (numeric string) - * @param body - The user restriction data to update - * @param placeId - The place ID (optional) (numeric string) + * @param options - The options for updating the user restriction + * @param options.universeId - The universe ID (numeric string) + * @param options.placeId - The place ID (optional) (numeric string) + * @param options.body - The user restriction data to update * @returns Promise resolving to the user restriction response * @throws {AuthError} If API key is invalid * @throws {OpenCloudError} If an API error occurs * * @example * ```typescript - * const userRestriction = await client.universes.updateUserRestriction('123456789', '123456789', { - * active: true, - * duration: "3s", - * privateReason: "some private reason", - * displayReason: "some display reason", - * excludeAltAccounts: true + * const userRestriction = await client.universes.updateUserRestriction('1210019099', { + * universeId: '1234', + * placeId: '5678', + * body: { + * active: true, + * duration: "3s", + * privateReason: "some private reason", + * displayReason: "some display reason", + * excludeAltAccounts: true + * } * }); * ``` * * @see https://create.roblox.com/docs/cloud/reference/UserRestriction#Cloud_UpdateUserRestriction__Using_Universes_Places */ async updateUserRestriction( - universeId: string, userRestrictionId: string, - body: GameJoinRestriction, - placeId?: string, + options: UpdateUserRestrictionOptions, ): Promise { + const { body, universeId, placeId } = options; const searchParams = new URLSearchParams(); searchParams.set("updateMask", "game_join_restriction"); diff --git a/src/types/universes.ts b/src/types/universes.ts index c1e4567..495deb9 100644 --- a/src/types/universes.ts +++ b/src/types/universes.ts @@ -84,6 +84,12 @@ export interface SpeechAssetResponse extends SpeechAssetOperation { }; } +export interface UpdateUserRestrictionOptions { + universeId: string; + placeId?: string; + body: GameJoinRestriction; +} + export interface GameJoinRestriction { active: boolean; duration?: string; From ab86f517f722c1bedcd3ba5775a92159c3af7022 Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:33:51 +1300 Subject: [PATCH 3/7] Updated updateUserRestriction test --- test/universes.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/universes.test.ts b/test/universes.test.ts index 9ded4c1..f77a465 100644 --- a/test/universes.test.ts +++ b/test/universes.test.ts @@ -294,9 +294,12 @@ describe("Universes", () => { }; const result = await openCloud.universes.updateUserRestriction( - "123456789", "111111111", - body, + { + universeId: "123456789", + placeId: "987654321", + body, + }, ); expect(result.user).toBe("users/111111111"); @@ -305,7 +308,7 @@ describe("Universes", () => { const url = calls[0]?.url.toString(); expect(url).toContain( - `${baseUrl}/cloud/v2/universes/123456789/user-restrictions/111111111`, + `${baseUrl}/cloud/v2/universes/123456789/places/987654321/user-restrictions/111111111`, ); expect(url).toContain("updateMask=game_join_restriction"); expect(url).toContain("idempotencyKey.key="); From 5c65efe614de3052dbcee693ba6731670763425f Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:05:28 +1300 Subject: [PATCH 4/7] fix: handle empty duration in user restriction update Prevents API errors by setting 'duration' to undefined if it is an empty string when updating user restrictions. This avoids sending invalid data that would result in a 400 response from the API. --- src/resources/universes.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/resources/universes.ts b/src/resources/universes.ts index 6fd1d12..f9a91ec 100644 --- a/src/resources/universes.ts +++ b/src/resources/universes.ts @@ -27,7 +27,7 @@ export class Universes { * * @param http - HTTP client for making API requests */ - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) { } /** * Retrieves a universe's information by universe ID. @@ -205,6 +205,9 @@ export class Universes { resourcePath += `/user-restrictions/${userRestrictionId}`; + // API returns 400 if duration is set as an empty string + if (body.duration && body.duration === "") body.duration = undefined; + return this.http.request(resourcePath.toString(), { method: "PATCH", body: JSON.stringify({ gameJoinRestriction: body }), From 14603fdcca7a010d42c26d0468ee118fbc5c5bbb Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:07:21 +1300 Subject: [PATCH 5/7] fix: linting --- src/resources/universes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/universes.ts b/src/resources/universes.ts index f9a91ec..21d6615 100644 --- a/src/resources/universes.ts +++ b/src/resources/universes.ts @@ -27,7 +27,7 @@ export class Universes { * * @param http - HTTP client for making API requests */ - constructor(private http: HttpClient) { } + constructor(private http: HttpClient) {} /** * Retrieves a universe's information by universe ID. From 28620fcd3ed49a434f4606be79b769ccb7416857 Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:20:43 +1300 Subject: [PATCH 6/7] fix: handle empty duration in user restriction updates Refactored Universes.updateUserRestriction to set duration as undefined if an empty string is provided, preventing API 400 errors. Updated related test to verify correct handling of empty duration values. --- src/resources/universes.ts | 15 ++++++++++----- test/universes.test.ts | 5 ++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/resources/universes.ts b/src/resources/universes.ts index 21d6615..b2257ac 100644 --- a/src/resources/universes.ts +++ b/src/resources/universes.ts @@ -186,8 +186,16 @@ export class Universes { options: UpdateUserRestrictionOptions, ): Promise { const { body, universeId, placeId } = options; - const searchParams = new URLSearchParams(); + const payload = { ...body }; + + // API returns 400 if duration is set as an empty string + if (payload.duration == "") { + console.log(payload); + payload.duration = undefined; + console.log(payload); + } + const searchParams = new URLSearchParams(); searchParams.set("updateMask", "game_join_restriction"); const idempotencyKey = generateIdempotencyKey(); @@ -205,12 +213,9 @@ export class Universes { resourcePath += `/user-restrictions/${userRestrictionId}`; - // API returns 400 if duration is set as an empty string - if (body.duration && body.duration === "") body.duration = undefined; - return this.http.request(resourcePath.toString(), { method: "PATCH", - body: JSON.stringify({ gameJoinRestriction: body }), + body: JSON.stringify({ gameJoinRestriction: payload }), searchParams, }); } diff --git a/test/universes.test.ts b/test/universes.test.ts index f77a465..20de86d 100644 --- a/test/universes.test.ts +++ b/test/universes.test.ts @@ -267,7 +267,6 @@ describe("Universes", () => { user: "users/111111111", gameJoinRestriction: { active: true, - duration: "7200s", privateReason: "Updated reason", displayReason: "Updated display reason", excludeAltAccounts: false, @@ -287,7 +286,7 @@ describe("Universes", () => { const body: GameJoinRestriction = { active: true, - duration: "7200s", + duration: "", privateReason: "Updated reason", displayReason: "Updated display reason", excludeAltAccounts: false, @@ -304,7 +303,7 @@ describe("Universes", () => { expect(result.user).toBe("users/111111111"); expect(result.gameJoinRestriction.active).toBe(true); - expect(result.gameJoinRestriction.duration).toBe("7200s"); + expect(result.gameJoinRestriction.duration).toBe(undefined); const url = calls[0]?.url.toString(); expect(url).toContain( From 16d552624017c5a2ebe8d1d42273f8546169425d Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Sun, 23 Nov 2025 17:29:58 +1300 Subject: [PATCH 7/7] Refactor updateUserRestriction parameter order Swapped the order of universeId and userRestrictionId in updateUserRestriction to match API expectations. Updated related type definitions, documentation, and tests for consistency. --- src/resources/universes.ts | 31 ++++++++++--------------------- src/types/universes.ts | 2 +- test/universes.test.ts | 4 ++-- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/resources/universes.ts b/src/resources/universes.ts index b2257ac..ad1b7f7 100644 --- a/src/resources/universes.ts +++ b/src/resources/universes.ts @@ -155,9 +155,9 @@ export class Universes { * * *Requires `universe.user-restriction:write` scope.* * - * @param userRestrictionId - The user ID (numeric string) + * @param universeId - The universe ID (numeric string) * @param options - The options for updating the user restriction - * @param options.universeId - The universe ID (numeric string) + * @param options.userRestrictionId - The user ID (numeric string) * @param options.placeId - The place ID (optional) (numeric string) * @param options.body - The user restriction data to update * @returns Promise resolving to the user restriction response @@ -166,8 +166,8 @@ export class Universes { * * @example * ```typescript - * const userRestriction = await client.universes.updateUserRestriction('1210019099', { - * universeId: '1234', + * const userRestriction = await client.universes.updateUserRestriction('123456789', { + * userRestrictionId: '1210019099', * placeId: '5678', * body: { * active: true, @@ -182,18 +182,11 @@ export class Universes { * @see https://create.roblox.com/docs/cloud/reference/UserRestriction#Cloud_UpdateUserRestriction__Using_Universes_Places */ async updateUserRestriction( - userRestrictionId: string, + universeId: string, options: UpdateUserRestrictionOptions, ): Promise { - const { body, universeId, placeId } = options; - const payload = { ...body }; - - // API returns 400 if duration is set as an empty string - if (payload.duration == "") { - console.log(payload); - payload.duration = undefined; - console.log(payload); - } + const { body, userRestrictionId, placeId } = options; + body.duration = body.duration === "" ? undefined : body.duration; const searchParams = new URLSearchParams(); searchParams.set("updateMask", "game_join_restriction"); @@ -205,17 +198,13 @@ export class Universes { searchParams.set("idempotencyKey.firstSent", firstSent); let resourcePath = `/cloud/v2/universes/${universeId}`; - // This resource has two different paths, one for universe-level restrictions, and another for place-level restrictions - if (placeId) { - resourcePath += `/places/${placeId}`; - } - + if (placeId) resourcePath += `/places/${placeId}`; resourcePath += `/user-restrictions/${userRestrictionId}`; - return this.http.request(resourcePath.toString(), { + return this.http.request(resourcePath, { method: "PATCH", - body: JSON.stringify({ gameJoinRestriction: payload }), + body: JSON.stringify({ gameJoinRestriction: body }), searchParams, }); } diff --git a/src/types/universes.ts b/src/types/universes.ts index 495deb9..6144e97 100644 --- a/src/types/universes.ts +++ b/src/types/universes.ts @@ -85,7 +85,7 @@ export interface SpeechAssetResponse extends SpeechAssetOperation { } export interface UpdateUserRestrictionOptions { - universeId: string; + userRestrictionId: string; placeId?: string; body: GameJoinRestriction; } diff --git a/test/universes.test.ts b/test/universes.test.ts index 20de86d..9327c66 100644 --- a/test/universes.test.ts +++ b/test/universes.test.ts @@ -293,9 +293,9 @@ describe("Universes", () => { }; const result = await openCloud.universes.updateUserRestriction( - "111111111", + "123456789", { - universeId: "123456789", + userRestrictionId: "111111111", placeId: "987654321", body, },