Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions src/resources/users.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { HttpClient } from "../http";
import { OpenCloudError } from "../errors";
import type {
AssetQuotasPage,
GameJoinRestriction,
GenerateThumbnailOptions,
InventoryItemsPage,
ListOptions,
User,
UserNotificationBody,
UserNotificationResponse,
UserRestrictionLogsPage,
UserRestrictionResponse,
UserRestrictionsPage,
UserThumbnail,
} from "../types";

Expand Down Expand Up @@ -227,4 +232,182 @@
},
);
}

/**
* List user restrictions for users that have ever been banned in either a universe or a specific place.
*
* *Requires `universe.user-restriction:read` scope.*
*
* @param universeId - The universe ID. (numeric string)
* @param options - List options including pagination and filtering
* @param options.maxPageSize - Maximum items per page (default set by API)
* @param options.pageToken - Token from previous response for next page
* @returns Promise resolving to the user restriction response
* @throws {AuthError} If API key is invalid
* @throws {OpenCloudError} If the universeId is invalid or other API error occurs
*
* @example
* ```typescript
* const userRestriction = await client.users.listUserRestrictions('123456789', { maxPageSize: 50 });
* console.log(userRestriction);
* ```
*
* @see https://create.roblox.com/docs/cloud/reference/UserRestriction#Cloud_UpdateUserRestriction__Using_Universes_Places
*/
async listUserRestrictions(
universeId: string,
options: ListOptions = {},
): Promise<UserRestrictionsPage> {
const searchParams = new URLSearchParams();
if (options.maxPageSize)
searchParams.set("maxPageSize", options.maxPageSize.toString());
if (options.pageToken) searchParams.set("pageToken", options.pageToken);

return this.http.request<UserRestrictionsPage>(
`/cloud/v2/universes/${universeId}/user-restrictions`,
{
method: "GET",
searchParams,
},
);
}

/**
* Get the user restriction.
*
* *Requires `universe.user-restriction:read` scope.*
*
* @param universeId - The universe ID. (numeric string)
* @param userRestrictionId - The user ID. (numeric string)
* @returns Promise resolving to the user restriction response
* @throws {AuthError} If API key is invalid
* @throws {OpenCloudError} If the user is not found or other API error occurs
*
* @example
* ```typescript
* const userRestriction = await client.users.getUserRestriction('123456789', '123456789');
* console.log(userRestriction);
* ```
*
* @see https://create.roblox.com/docs/cloud/reference/UserRestriction#Cloud_UpdateUserRestriction__Using_Universes_Places
*/
async getUserRestriction(
universeId: string,
userRestrictionId: string,
): Promise<UserRestrictionResponse> {
return this.http.request<UserRestrictionResponse>(
`/cloud/v2/universes/${universeId}/user-restrictions/${userRestrictionId}`,
{
method: "GET",
},
);
}

/**
* Update the user restriction.
*
* *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 body containing the payload
* @param updateMask - The list of fields to update; only "game_join_restriction" is supported
* @returns Promise resolving to the user restriction response
* @throws {AuthError} If API key is invalid
* @throws {OpenCloudError} If updateMask is invalid or an API error occurs
*
* @example
* ```typescript
* const userRestrction = await client.users.updateUserRestriction('123456789', '123456789', {
* 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,
updateMask?: string,
): Promise<UserRestrictionResponse> {
const idempotencyKey = crypto.randomUUID();

Check failure on line 338 in src/resources/users.ts

View workflow job for this annotation

GitHub Actions / Node 18 - ubuntu-latest

test/users.test.ts > Users > PATCH /universes/{id}/user-restrictions/{userId} with valid updateMask

ReferenceError: crypto is not defined ❯ Users.updateUserRestriction src/resources/users.ts:338:28 ❯ test/users.test.ts:367:42
const firstSent = new Date().toISOString();

if (updateMask && updateMask !== "game_join_restriction") {
throw new OpenCloudError(
`Invalid update mask: "${updateMask}". Only "game_join_restriction" or none is supported.`,
400,
"INVALID_ARGUMENT",
{
field: "game_join_restriction",
allowed: ["game_join_restriction"],
},
);
}

const searchParams = new URLSearchParams({
"idempotencyKey.key": idempotencyKey,
"idempotencyKey.firstSent": firstSent,
});
if (updateMask) searchParams.append("updateMask", updateMask);

return this.http.request<UserRestrictionResponse>(
`/cloud/v2/universes/${universeId}/user-restrictions/${userRestrictionId}`,
{
method: "PATCH",
body: JSON.stringify(body),
searchParams,
},
);
}

/**
* List changes to UserRestriction resources within a given universe. This includes both universe-level and place-level restrictions.
* For universe-level restriction logs, the place field will be empty.
*
* *Requires `universe.user-restriction:read` scope.*
*
* @param universeId - The universe ID. (numeric string)
* @param options - List options including pagination and filtering
* @param options.maxPageSize - Maximum items per page (default set by API)
* @param options.pageToken - Token from previous response for next page
* @param options.filter - Filter expression (e.g., "user == 'users/123'" && "place == 'places/456'")
* @returns Promise resolving to the user restriction response
* @throws {AuthError} If API key is invalid
* @throws {OpenCloudError} If the universeId is invalid or other API error occurs
*
* @example
* ```typescript
* const userRestriction = await client.users.listUserRestrictions('123456789', {
* maxPageSize: 50,
* filter: `"user == 'users/123'" && "place == 'places/456'"`
* });
* console.log(userRestriction);
* ```
*
* @see https://create.roblox.com/docs/cloud/reference/UserRestriction#Cloud_UpdateUserRestriction__Using_Universes_Places
*/
async listUserRestrictionLogs(
universeId: string,
options: ListOptions & { filter?: string } = {},
): Promise<UserRestrictionLogsPage> {
const searchParams = new URLSearchParams();
if (options.maxPageSize)
searchParams.set("maxPageSize", options.maxPageSize.toString());
if (options.pageToken) searchParams.set("pageToken", options.pageToken);
if (options.filter) searchParams.set("filter", options.filter);

return this.http.request<UserRestrictionLogsPage>(
`/cloud/v2/universes/${universeId}/user-restrictions:listLogs`,
{
method: "GET",
searchParams,
},
);
}
}
39 changes: 39 additions & 0 deletions src/types/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,42 @@ export interface UserNotificationBody {
source: UserNotificationSource;
payload: UserNotificationPayload;
}

export type UserRestrictionsPage = Page<
UserRestrictionResponse,
"userRestrictions"
>;
export type UserRestrictionLogsPage = Page<UserRestrictionLog, "logs">;

export interface GameJoinRestriction {
active: boolean;
duration: string;
privateReason: string;
displayReason: string;
excludeAltAccounts: boolean;
}

export interface GameJoinRestrictionResponse extends GameJoinRestriction {
startTime: string;
inherited: boolean;
}

export interface UserRestrictionLog extends GameJoinRestriction {
user: string;
place: string;
moderator: {
robloxUser: string;
};
createTime: string;
startTime: string;
restrictionType: {
gameJoinRestriction: GameJoinRestriction;
};
}

export interface UserRestrictionResponse {
path: string;
updateTime: string;
user: string;
gameJoinRestriction: GameJoinRestrictionResponse;
}
Loading
Loading