npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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-sdk

Node >= 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: url no 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. fileSource abre 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 plataformas

Las 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 typecheck

Los 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