Files
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

92 lines
4.0 KiB
JavaScript

// lichess user — fetch a public Lichess player profile.
//
// Hits `/api/user/<username>`. Returns the agent-useful slice: handle, title,
// flags (online / patron), counts, top-rated perf, profile bio.
import { cli, Strategy } from '@jackwener/opencli/registry';
import { EmptyResultError } from '@jackwener/opencli/errors';
import { LICHESS_BASE, formatTimestamp, lichessFetch, requireUsername } from './utils.js';
cli({
site: 'lichess',
name: 'user',
access: 'read',
description: 'Fetch a Lichess player profile by username (rating, perfs, counts)',
domain: 'lichess.org',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'username', positional: true, required: true, help: 'Lichess username (case-insensitive)' },
],
columns: [
'username',
'id',
'title',
'patron',
'online',
'tosViolation',
'createdAt',
'seenAt',
'gamesAll',
'gamesWin',
'gamesLoss',
'gamesDraw',
'topPerfName',
'topPerfRating',
'topPerfGames',
'fideRating',
'country',
'bio',
'url',
],
func: async (args) => {
const username = requireUsername(args.username);
const url = `${LICHESS_BASE}/api/user/${encodeURIComponent(username)}`;
const body = await lichessFetch(url, 'lichess user');
if (!body || typeof body !== 'object') {
throw new EmptyResultError('lichess user', `Lichess user "${username}" returned empty payload.`);
}
// Lichess marks closed accounts with `disabled: true` and strips data.
// Surface as EmptyResultError instead of a row of nulls (silent-fallback).
if (body.disabled === true) {
throw new EmptyResultError('lichess user', `Lichess user "${username}" is closed/disabled.`);
}
const perfs = body.perfs && typeof body.perfs === 'object' ? body.perfs : {};
// Pick the perf with the most games (excluding puzzle/storm/racer ephemera).
const playablePerfs = Object.entries(perfs).filter(([k, v]) => v && typeof v === 'object' && !['puzzle', 'storm', 'racer', 'streak'].includes(k));
let topPerfName = null;
let topPerfRating = null;
let topPerfGames = null;
for (const [name, p] of playablePerfs) {
const games = typeof p.games === 'number' ? p.games : 0;
if (topPerfGames == null || games > topPerfGames) {
topPerfName = name;
topPerfGames = games;
topPerfRating = typeof p.rating === 'number' ? p.rating : null;
}
}
const counts = body.count && typeof body.count === 'object' ? body.count : {};
const profile = body.profile && typeof body.profile === 'object' ? body.profile : {};
return [{
username: typeof body.username === 'string' ? body.username : username,
id: typeof body.id === 'string' ? body.id : null,
title: typeof body.title === 'string' ? body.title : null,
patron: body.patron === true,
online: body.online === true,
tosViolation: body.tosViolation === true,
createdAt: formatTimestamp(body.createdAt),
seenAt: formatTimestamp(body.seenAt),
gamesAll: typeof counts.all === 'number' ? counts.all : null,
gamesWin: typeof counts.win === 'number' ? counts.win : null,
gamesLoss: typeof counts.loss === 'number' ? counts.loss : null,
gamesDraw: typeof counts.draw === 'number' ? counts.draw : null,
topPerfName,
topPerfRating,
topPerfGames,
fideRating: typeof profile.fideRating === 'number' ? profile.fideRating : null,
country: typeof profile.country === 'string' ? profile.country : null,
bio: typeof profile.bio === 'string' ? profile.bio.trim() : null,
url: `${LICHESS_BASE}/@/${encodeURIComponent(typeof body.username === 'string' ? body.username : username)}`,
}];
},
});