import config from "@/config"; import type { BanResponse, FriendsResponse, RestrictionsResponse, RobloxUser } from "./types"; const COOKIE = config.roblox.cookie; let CSRF_TOKEN: string | undefined; const request = (url: URL, options?: RequestInit) => fetch(url, { headers: { Cookie: COOKIE, }, ...options, }); export async function batchUsers(ids: number[]): Promise { const url = new URL("https://apis.roblox.com/user-profile-api/v1/user/profiles/get-profiles"); const fields = ["names.combinedName", "names.username"]; const response = await request(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userIds: ids, fields }), }); type Profile = { userId: number; names: { combinedName: string; username: string; }; }; type BatchResponse = { profileDetails: Profile[]; }; const res = (await response.json()) as BatchResponse; return res.profileDetails.map( (detail) => ({ id: detail.userId, displayName: detail.names.combinedName, name: detail.names.username, }) as RobloxUser, ); } export async function getUser(id: string): Promise { const url = new URL(`https://users.roblox.com/v1/users/${id}`); const response = await request(url); return (await response.json()) as RobloxUser; } export async function getFriends(id: string, options: { cursor: string | null; page: number }) { const url = new URL(`https://friends.roblox.com/v1/users/${id}/friends/find`); if (options.cursor) url.searchParams.set("cursor", options.cursor); url.searchParams.set("limit", "20"); const response = await request(url); const data = (await response.json()) as FriendsResponse; return { cursor: data.NextCursor, ids: data.PageItems.map((item) => item.id) }; } export async function fetchRestriction(id: string): Promise { const url = new URL( `https://apis.roblox.com/user/cloud/v2/universes/21449357/user-restrictions/${id}`, ); const response = await request(url); return (await response.json()) as RestrictionsResponse; } export async function ban( userId: string, universeId: string, options: { duration: string | undefined; excludeAltAccounts: boolean; displayReason: string; privateReason: string; }, ): Promise { const makeRequest = async () => { const res = await fetch( `https://apis.roblox.com/user/cloud/v2/universes/${universeId}/user-restrictions/${userId}`, { method: "PATCH", headers: { "Content-Type": "application/json", Cookie: config.roblox.cookie, Host: "apis.roblox.com", Origin: "https://create.roblox.com", Referer: "https://create.roblox.com", "x-csrf-token": CSRF_TOKEN ?? "", }, body: JSON.stringify({ gameJoinRestriction: { active: true, ...options, }, }), }, ); const data = (await res.json()) as Record; if (res.status === 403) { const csrfHeader = res.headers.get("x-csrf-token"); if (csrfHeader && csrfHeader !== "") { CSRF_TOKEN = csrfHeader; return await makeRequest(); } } if (!res.ok) { throw new Error(`failed to ban user: ${res.status} ${res.statusText}`); } return data as BanResponse; }; return await makeRequest(); } export * from "./types";