all repos — stealth-developers @ 06108c2919e2b4beec9596fb10b030321bfefa5d

src/utils/bans.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
import config from "@/config";
import type { GetUserOkResponse, GetUserResponse } from "@/types/roblox";
import lily from "@/utils/logging";
import { ContainerBuilder, TextDisplayBuilder } from "discord.js";

const logger = lily.child("bans");
const projects = Object.values(config.data.projects);
const BASE_URL = "https://apis.roblox.com/user/cloud/v2/universes";

export type Restriction = {
	active: true | false;
	startTime: string;
	privateReason: string;
	displayReason: string;
	excludeAltAccounts: boolean;
	inherited: boolean;
};

export type GetBansResponse = {
	path: string;
	user: string;
	gameJoinRestriction: Restriction;
};

export type GetBansResponseWithProject = GetBansResponse & {
	project: string;
};

export async function getBans(
	userId: string,
): Promise<GetBansResponseWithProject[] | null> {
	if (!config.data.roblox?.cookie) return null;

	const fetches = projects.map(async (project) => {
		const url = `${BASE_URL}/${project.universe}/user-restrictions/${userId}`;
		const bans = await fetch(url, {
			headers: {
				Cookie: config.data.roblox?.cookie || "",
			},
		});

		if (!bans.ok) {
			logger.error(
				`failed to fetch bans for user ${userId} in project ${project.name}:`,
				bans.statusText,
			);
			return null;
		}

		const data: GetBansResponse = await bans.json();
		return { ...data, project: project.displayName };
	});

	const results = await Promise.all(fetches);
	const bansArray = results.filter(
		(data): data is GetBansResponseWithProject => data !== null,
	);

	return bansArray;
}

export async function buildBansContainer(
	user: GetUserOkResponse,
): Promise<ContainerBuilder> {
	const bansContainer = new ContainerBuilder();
	{
		const bansTitle = new TextDisplayBuilder().setContent(
			`**Bans for ${user.displayName || user.name}**`,
		);

		const bans = await getBans(user.id);

		const constructBan = (ban: GetBansResponseWithProject) => {
			if (!ban.gameJoinRestriction.active) {
				return new TextDisplayBuilder().setContent(
					`**${ban.project}**: no active ban`,
				);
			}

			const restriction: Restriction = ban.gameJoinRestriction;
			return new TextDisplayBuilder().setContent(
				[
					`**${ban.project}**`,
					`> **Active since** <t:${Math.round(new Date(restriction.startTime).getTime() / 1000)}:R>`,
					`> **Display reason:** ${restriction.displayReason}`,
					`> **Reason:** ${restriction.privateReason}`,
				].join("\n"),
			);
		};

		if (bans && bans.length > 0) {
			const banComponents = bans.map(constructBan);
			bansContainer.addTextDisplayComponents(bansTitle, ...banComponents);
		} else {
			bansContainer.addTextDisplayComponents(
				bansTitle,
				new TextDisplayBuilder().setContent(
					"no active bans found for this user",
				),
			);
		}
	}

	return bansContainer;
}