128 lines
3.2 KiB
TypeScript
128 lines
3.2 KiB
TypeScript
import { LineEval, PositionEval } from "@/types/eval";
|
|
import { sortLines } from "./engine/helpers/parseResults";
|
|
import {
|
|
LichessError,
|
|
LichessEvalBody,
|
|
LichessGame,
|
|
LichessResponse,
|
|
} from "@/types/lichess";
|
|
import { logErrorToSentry } from "./sentry";
|
|
import { formatUciPv } from "./chess";
|
|
import { LoadedGame } from "@/types/game";
|
|
|
|
export const getLichessEval = async (
|
|
fen: string,
|
|
multiPv = 1
|
|
): Promise<PositionEval> => {
|
|
try {
|
|
const data = await fetchLichessEval(fen, multiPv);
|
|
|
|
if ("error" in data) {
|
|
if (data.error === LichessError.NotFound) {
|
|
return {
|
|
bestMove: "",
|
|
lines: [],
|
|
};
|
|
}
|
|
throw new Error(data.error);
|
|
}
|
|
|
|
const lines: LineEval[] = data.pvs.map((pv, index) => ({
|
|
pv: formatUciPv(fen, pv.moves.split(" ")),
|
|
cp: pv.cp,
|
|
mate: pv.mate,
|
|
depth: data.depth,
|
|
multiPv: index + 1,
|
|
}));
|
|
|
|
lines.sort(sortLines);
|
|
const isWhiteToPlay = fen.split(" ")[1] === "w";
|
|
if (!isWhiteToPlay) lines.reverse();
|
|
|
|
const bestMove = lines[0].pv[0];
|
|
const linesToKeep = lines.slice(0, multiPv);
|
|
|
|
return {
|
|
bestMove,
|
|
lines: linesToKeep,
|
|
};
|
|
} catch (error) {
|
|
logErrorToSentry(error, { fen, multiPv });
|
|
|
|
return {
|
|
bestMove: "",
|
|
lines: [],
|
|
};
|
|
}
|
|
};
|
|
|
|
export const getLichessUserRecentGames = async (
|
|
username: string,
|
|
signal?: AbortSignal
|
|
): Promise<LoadedGame[]> => {
|
|
const res = await fetch(
|
|
`https://lichess.org/api/games/user/${username}?until=${Date.now()}&max=50&pgnInJson=true&sort=dateDesc&clocks=true`,
|
|
{ method: "GET", headers: { accept: "application/x-ndjson" }, signal }
|
|
);
|
|
|
|
if (res.status >= 400) {
|
|
throw new Error("Error fetching games from Lichess");
|
|
}
|
|
|
|
const rawData = await res.text();
|
|
const games: LichessGame[] = rawData
|
|
.split("\n")
|
|
.filter((game) => game.length > 0)
|
|
.map((game) => JSON.parse(game));
|
|
|
|
return games.map(formatLichessGame);
|
|
};
|
|
|
|
const fetchLichessEval = async (
|
|
fen: string,
|
|
multiPv: number
|
|
): Promise<LichessResponse<LichessEvalBody>> => {
|
|
try {
|
|
const res = await fetch(
|
|
`https://lichess.org/api/cloud-eval?fen=${fen}&multiPv=${multiPv}`,
|
|
{ method: "GET", signal: AbortSignal.timeout(200) }
|
|
);
|
|
|
|
return res.json();
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
return { error: LichessError.NotFound };
|
|
}
|
|
};
|
|
|
|
const formatLichessGame = (data: LichessGame): LoadedGame => {
|
|
return {
|
|
id: data.id,
|
|
pgn: data.pgn || "",
|
|
white: {
|
|
name: data.players.white.user?.name || "White",
|
|
rating: data.players.white.rating,
|
|
title: data.players.white.user?.title,
|
|
},
|
|
black: {
|
|
name: data.players.black.user?.name || "Black",
|
|
rating: data.players.black.rating,
|
|
title: data.players.black.user?.title,
|
|
},
|
|
result: getGameResult(data),
|
|
timeControl: `${Math.floor(data.clock?.initial / 60 || 0)}+${data.clock?.increment || 0}`,
|
|
date: new Date(data.createdAt || data.lastMoveAt).toLocaleDateString(),
|
|
movesNb: data.moves?.split(" ").length || 0,
|
|
url: `https://lichess.org/${data.id}`,
|
|
};
|
|
};
|
|
|
|
const getGameResult = (data: LichessGame): string => {
|
|
if (data.status === "draw") return "1/2-1/2";
|
|
|
|
if (data.winner) return data.winner === "white" ? "1-0" : "0-1";
|
|
|
|
return "*";
|
|
};
|