all repos — discord-utils @ fe1626583ce351ffa2bf22c5d83dd638aa409753

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
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> {
	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>;

		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";