UNPKG

ddnet

Version:

A typescript npm package for interacting with data from ddnet.org

228 lines 6.49 kB
/** * Generated using the help of https://quicktype.io/ * * I did not dive into this because the master server is written in rust, and I don't know rust. * Besides types, this is (mostly) undocumented and I'm not sure how correct are the types. * * @module ddnet/master * @packageDocumentation */ import axios from 'axios'; import { z } from 'zod'; import { Player } from './DDNet.js'; import { DDNetError } from './util.js'; import { renderTee } from './classes/skins/TeeSkinUtils.js'; /** * Zod schema for raw master server info data. * * @remarks * Translated from types generated by quicktype. */ export const _Schema_MasterSrv_Info = z.object({ max_clients: z.number(), max_players: z.number(), passworded: z.boolean(), game_type: z.string(), name: z.string(), map: z.object({ name: z.string(), sha256: z.string().optional(), size: z.number().optional() }), version: z.string(), clients: z.array(z.object({ name: z.string(), clan: z.string(), country: z.number(), score: z.number(), is_player: z.boolean(), skin: z .object({ name: z.string().optional(), color_body: z.number().optional(), color_feet: z.number().optional(), body: z .object({ name: z.string(), color: z.number().optional() }) .optional(), marking: z .object({ name: z.string(), color: z.number().optional() }) .optional(), decoration: z .object({ name: z.string(), color: z.number().optional() }) .optional(), hands: z .object({ name: z.string(), color: z.number().optional() }) .optional(), feet: z .object({ name: z.string(), color: z.number().optional() }) .optional(), eyes: z .object({ name: z.string(), color: z.number().optional() }) .optional() }) .optional(), afk: z.boolean().optional(), team: z.number().optional() })), client_score_kind: z.string().optional(), requires_login: z.boolean().optional(), community: z .object({ id: z.string(), icon: z.string(), admin: z.array(z.string()), public_key: z.string(), signature: z.string() }) .optional(), altameda_net: z.boolean().optional() }); /** * Helper function to distinguish between "official" server info data and others. */ export function isMasterServerInfoData( /** * The data to check. */ data) { return _Schema_MasterSrv_Info.safeParse(data).success; } /** * Zod schema for raw master server data. * * @remarks * Translated from types generated by quicktype. */ export const _Schema_MasterSrv = z.object({ servers: z.array(z.object({ addresses: z.array(z.string()), location: z.string().optional(), info: z.union([_Schema_MasterSrv_Info, z.unknown()]) })) }); /** * Makes a request to the master server. * * @see * https://github.com/ddnet/ddnet/tree/a00d6a311971cafe96f2ec7baf9637a9c5989be4/src/mastersrv * * * https://master1.ddnet.org/ddnet/15/servers.json */ export async function makeMasterRequest( /** * Master server url to use. * * @default "https://master1.ddnet.org/ddnet/15/servers.json" */ masterSrv) { const url = masterSrv ?? `https://master1.ddnet.org/ddnet/15/servers.json`; const response = await axios .get(url, { headers: { 'Cache-Control': 'no-cache' } }) .catch((err) => new DDNetError(err.cause?.message, err)); if (response instanceof DDNetError) return response; const data = response.data; if (typeof data === 'string') return new DDNetError(`Invalid response!`, data); return data; } /** * Makes a request to the master server using {@link makeMasterRequest} and parses the data. */ export async function getMasterSrvData( /** * Master server url to use. * * @default "https://master1.ddnet.org/ddnet/15/servers.json" */ masterSrv) { const data = await makeMasterRequest(masterSrv); if (data instanceof DDNetError) throw data; const parsed = _Schema_MasterSrv.safeParse(data); if (!parsed.success) throw parsed.error; return parsed.data; } /** * Finds a player on the master server by their name or clan. * * @example * ```ts * const players = await findPlayer('nameless tee'); * * if (players === null) { * console.log('Player not found.'); * } else { * console.log(`Found ${players.length} player${players.length > 1 ? 's' : ''}:\n${players.map(p => `${p.server.name} | ${p.server.addresses[0].split('//')[1]} | ${p.name} [${p.clan}]`).join('\n')}`); * } * ``` */ export async function findPlayer( /** * The value for search for. */ value, /** * The kind of search to use. * * @default "name" */ kind = 'name') { const serverList = (await getMasterSrvData()).servers .map(srv => { if (!isMasterServerInfoData(srv.info)) return null; return { name: srv.info.name, addresses: srv.addresses, clients: srv.info.clients, self: srv }; }) .filter(srv => srv !== null); const foundPlayerServers = serverList.filter(srv => { return srv.clients.some(c => (kind === 'name' ? c.name === value : c.clan === value)); }); if (foundPlayerServers.length === 0) return null; return foundPlayerServers.map(srv => { const client = srv.clients.find(c => (kind === 'name' ? c.name === value : c.clan === value)); return { name: client.name, clan: client.clan, self: client, server: { name: srv.name, addresses: srv.addresses, self: srv.self }, toPlayer: async () => await Player.new(client.name), renderSkin: async (renderOpts) => await renderTee(client.skin, renderOpts) }; }); } //# sourceMappingURL=Master.js.map