src/roblox/index.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
import { container } from "@sapphire/framework";
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<RobloxUser[]> {
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<RobloxUser> {
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<RestrictionsResponse> {
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<BanResponse> {
container.logger.debug(`banning ${userId} in universe ${universeId}`, {
name: "ban",
...options,
});
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<string, unknown>;
container.logger.debug("ban response", { ...data });
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";
|