csrep-sdk
v0.1.0
Published
Cliente TypeScript/JavaScript no oficial para la API de CSRep.gg. Sube demos de CS2 para su análisis anticheat y consulta la reputación de los jugadores.
Downloads
227
Maintainers
Readme
csrep-sdk
Cliente TypeScript/JavaScript no oficial para la API de CSRep.gg.
Sirve para dos cosas: subir demos de CS2 para que CSRep las parsee y corra su detección de trampas, y leer la reputación de los jugadores (trust score, autoflags, sanciones).
npm install csrep-sdkNode >= 18 (usa el fetch global). Sin dependencias en runtime.
Uso
import { CsrepClient } from "csrep-sdk";
const csrep = new CsrepClient({ apiKey: process.env.CSREP_API_KEY! });
// Subir una demo y crear la partida, en un paso
const match = await csrep.importDemo("/demos/match_123_de_mirage.dem", {
onProgress: (p) => console.log(`${Math.round(p.ratio * 100)}%`),
});
console.log(`https://csrep.gg/match/${match.id}`);
// La reputación de los diez de una sala, en un request
const players = await csrep.players.list(steamIds);
for (const p of players) {
console.log(p.name, p.reputation?.trust_score);
}Por qué hay que subir el archivo
POST /matches/import acepta sólo dos cosas:
| Campo | Cuándo sirve |
|---|---|
| share_code | Sólo matchmaking de Valve. Una partida de servidor comunitario no tiene. |
| file_id | UUID de una demo ya subida a CSRep. |
No existe import por URL de demo ni por estadísticas ya calculadas: CSRep parsea el archivo él mismo. Así que para cualquier partida que no venga del matchmaking oficial —un servidor con MatchZy, por ejemplo— el único camino es empujar la demo entera.
Si mandás el DTO vacío, CSRep responde "Either (share_code) or (url) must be provided". Ese mensaje miente:
urlno es un campo de este endpoint, pertenece a/matches/import/faceit. Mandarlo acá se ignora.
Demos grandes
Una demo de CS2 puede pasar 1 GiB. El SDK está hecho para eso:
- Nunca carga el archivo en memoria.
fileSourceabre un descriptor y lee cada parte de forma posicional. - El reintento es por parte, no por archivo. 1,2 GiB son ~123 partes; descartar todo porque una dio 500 significaría remandar el gigabyte entero. Reintentando la parte, un corte cuesta 10 MiB.
- Las firmas se piden dentro del reintento. Vencen, y una subida larga puede durar más que la ventana de la primera.
const fileId = await csrep.uploadDemo("/demos/match.dem", {
chunkSize: 10 * 1024 * 1024, // 10 MiB, el mismo que usa el front de CSRep
concurrency: 4,
partRetries: 3,
onProgress: ({ partsDone, partsTotal }) => console.log(`${partsDone}/${partsTotal}`),
});
await csrep.waitForFile(fileId); // hasta AVAILABLE
await csrep.matches.import({ file_id: fileId });importDemo hace esos tres pasos de una. Tarda —la subida son cientos de megas y el procesamiento del otro lado son minutos—, así que conviene llamarla desde un job y no desde un request.
Fuentes propias
Si los bytes no están en el disco, implementá PartSource:
import { type PartSource, bufferSource } from "csrep-sdk";
await csrep.uploadDemo(bufferSource(bytes, "match.dem"));
const s3Source: PartSource = {
size, filename,
readPart: (offset, length) => fetchRange(offset, length),
};Jugadores
Se identifican por SteamID64, sin mapeo de ids.
const player = await csrep.players.get("76561198000000000");
player.reputation?.trust_score; // el número que casi todos buscan
player.autoflag; // flag automático de la detección
player.bans; // sanciones, propias y de otras plataformasLas sanciones traen source (STEAM · FACEIT · GAMERSCLUB · CSREP) y type (VAC · GAME · OVERWATCH · AUTOFLAG · CHEATING · SMURFING · …). El veredicto del Overwatch de CSRep llega como un ban con source: "CSREP" y type: "OVERWATCH"; el proceso de Overwatch en sí es interno y no tiene API pública — se consume el resultado, no el proceso.
players.list() es batch: preferilo a N llamadas sueltas. players.refresh() tiene cooldown del lado de CSRep (su propia config declara 5 min para el manual, 7 días para el automático), así que no lo llames por cada sala.
Ojo con redacted y steam_privacy: un jugador puede ocultar su perfil y dejarte sin señal. El código que lo consuma tiene que tolerar "no sé nada de este jugador".
Errores
import { CsrepApiError, CsrepFileRejectedError, CsrepTimeoutError } from "csrep-sdk";
try {
await csrep.importDemo(path);
} catch (error) {
if (error instanceof CsrepFileRejectedError) {
// Terminal: no es un .dem válido. Reintentar no cambia nada.
} else if (error instanceof CsrepTimeoutError) {
// Todavía procesando: se puede volver a consultar con el mismo fileId.
} else if (error instanceof CsrepApiError && error.isRetryable) {
// 5xx / 429 / 408
}
}isRetryable existe para no gastar una subida de un gigabyte en un pedido que está mal: un 4xx que no sea 408/429 significa clave inválida, DTO incorrecto o archivo rechazado, y reintentarlo sólo repite el gasto.
Un status: "ERROR" en el cuerpo se convierte en CsrepApiError aunque el HTTP haya sido 200 — CSRep usa las dos formas.
Endpoints
| | |
|---|---|
| client.matches | import · importFaceit · get · getByFaceitId · getByGamersClubId |
| client.players | list · get · search · refresh |
| client.uploads | createSession · presignPart · complete · abort · getFile |
| client.http | el transporte, por si hace falta una petición a medida |
Sobre la documentación de CSRep
/matches/* y /players/* salen del OpenAPI público que sirve csrep.gg/docs/api-reference.
/uploads/* y /files/{id} no están ahí. Se dedujeron del propio front de csrep.gg, que es su único consumidor conocido. Responden con la misma barrera de API key que el resto, así que se asume que la aceptan, pero es la parte del SDK que puede cambiar sin aviso.
Desarrollo
npm install
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
npm test # vitest
npm run typecheckLos tests corren contra un stub del API que reimplementa el sobre, la subida multiparte y el ciclo de vida del archivo. Verifican, entre otras cosas, que el archivo llegue byte a byte igual (un offset mal calculado haría que CSRep analice una demo corrupta y el veredicto no valga nada) y que la API key no viaje en el PUT prefirmado (rompería la firma).
Licencia
MIT
