feat : add lichess user games import
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { ChessComGame } from "@/types/chessCom";
|
import { ChessComGame } from "@/types/chessCom";
|
||||||
import { getPaddedMonth } from "./helpers";
|
import { getPaddedMonth } from "./helpers";
|
||||||
|
|
||||||
export const getUserRecentGames = async (
|
export const getChessComUserRecentGames = async (
|
||||||
username: string
|
username: string
|
||||||
): Promise<ChessComGame[]> => {
|
): Promise<ChessComGame[]> => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { sortLines } from "./engine/helpers/parseResults";
|
|||||||
import {
|
import {
|
||||||
LichessError,
|
LichessError,
|
||||||
LichessEvalBody,
|
LichessEvalBody,
|
||||||
|
LichessGame,
|
||||||
LichessResponse,
|
LichessResponse,
|
||||||
} from "@/types/lichess";
|
} from "@/types/lichess";
|
||||||
|
|
||||||
@@ -54,3 +55,22 @@ export const getLichessEval = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLichessUserRecentGames = async (
|
||||||
|
username: string
|
||||||
|
): Promise<LichessGame[]> => {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://lichess.org/api/games/user/${username}?until=${Date.now()}&max=50&pgnInJson=true&sort=dateDesc`,
|
||||||
|
{ method: "GET", headers: { accept: "application/x-ndjson" } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status === 404) return [];
|
||||||
|
|
||||||
|
const rawData = await res.text();
|
||||||
|
const games: LichessGame[] = rawData
|
||||||
|
.split("\n")
|
||||||
|
.filter((game) => game.length > 0)
|
||||||
|
.map((game) => JSON.parse(game));
|
||||||
|
|
||||||
|
return games;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||||
import { getUserRecentGames } from "@/lib/chessCom";
|
import { getChessComUserRecentGames } from "@/lib/chessCom";
|
||||||
import { capitalize } from "@/lib/helpers";
|
import { capitalize } from "@/lib/helpers";
|
||||||
import { ChessComGame } from "@/types/chessCom";
|
import { ChessComGame } from "@/types/chessCom";
|
||||||
import {
|
import {
|
||||||
@@ -33,7 +33,7 @@ export default function ChessComInput({ pgn, setPgn }: Props) {
|
|||||||
|
|
||||||
const timeout = setTimeout(
|
const timeout = setTimeout(
|
||||||
async () => {
|
async () => {
|
||||||
const games = await getUserRecentGames(chessComUsername);
|
const games = await getChessComUserRecentGames(chessComUsername);
|
||||||
setGames(games);
|
setGames(games);
|
||||||
},
|
},
|
||||||
requestCount === 0 ? 0 : 500
|
requestCount === 0 ? 0 : 500
|
||||||
|
|||||||
101
src/sections/loadGame/lichessInput.tsx
Normal file
101
src/sections/loadGame/lichessInput.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||||
|
import { getLichessUserRecentGames } from "@/lib/lichess";
|
||||||
|
import { capitalize } from "@/lib/helpers";
|
||||||
|
import { LichessGame } from "@/types/lichess";
|
||||||
|
import {
|
||||||
|
CircularProgress,
|
||||||
|
FormControl,
|
||||||
|
Grid,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
pgn: string;
|
||||||
|
setPgn: (pgn: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LichessInput({ pgn, setPgn }: Props) {
|
||||||
|
const [requestCount, setRequestCount] = useState(0);
|
||||||
|
const [lichessUsername, setLichessUsername] = useLocalStorage(
|
||||||
|
"lichess-username",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
const [games, setGames] = useState<LichessGame[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!lichessUsername) {
|
||||||
|
setGames([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = setTimeout(
|
||||||
|
async () => {
|
||||||
|
const games = await getLichessUserRecentGames(lichessUsername);
|
||||||
|
setGames(games);
|
||||||
|
},
|
||||||
|
requestCount === 0 ? 0 : 500
|
||||||
|
);
|
||||||
|
|
||||||
|
setRequestCount((prev) => prev + 1);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [lichessUsername]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormControl sx={{ m: 1, width: 300 }}>
|
||||||
|
<TextField
|
||||||
|
label="Enter your Lichess username..."
|
||||||
|
variant="outlined"
|
||||||
|
value={lichessUsername ?? ""}
|
||||||
|
onChange={(e) => setLichessUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{lichessUsername && (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
item
|
||||||
|
xs={12}
|
||||||
|
gap={2}
|
||||||
|
justifyContent="center"
|
||||||
|
alignContent="center"
|
||||||
|
minHeight={100}
|
||||||
|
>
|
||||||
|
{games.map((game) => (
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => setPgn(game.pgn)}
|
||||||
|
selected={pgn === game.pgn}
|
||||||
|
style={{ width: 350, maxWidth: 350 }}
|
||||||
|
key={game.id}
|
||||||
|
>
|
||||||
|
<ListItemText
|
||||||
|
primary={`${
|
||||||
|
capitalize(game.players.white.user?.name || "white") ||
|
||||||
|
"White"
|
||||||
|
} (${game.players?.white?.rating || "?"}) vs ${
|
||||||
|
capitalize(game.players.black.user?.name || "black") ||
|
||||||
|
"Black"
|
||||||
|
} (${game.players?.black?.rating || "?"})`}
|
||||||
|
secondary={`${capitalize(game.speed)} played at ${new Date(
|
||||||
|
game.lastMoveAt
|
||||||
|
)
|
||||||
|
.toLocaleString()
|
||||||
|
.slice(0, -3)}`}
|
||||||
|
primaryTypographyProps={{ noWrap: true }}
|
||||||
|
secondaryTypographyProps={{ noWrap: true }}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{games.length === 0 && <CircularProgress />}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import { useState } from "react";
|
|||||||
import GamePgnInput from "./gamePgnInput";
|
import GamePgnInput from "./gamePgnInput";
|
||||||
import ChessComInput from "./chessComInput";
|
import ChessComInput from "./chessComInput";
|
||||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||||
|
import LichessInput from "./lichessInput";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -105,6 +106,10 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
|||||||
<ChessComInput pgn={pgn} setPgn={setPgn} />
|
<ChessComInput pgn={pgn} setPgn={setPgn} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{gameOrigin === GameOrigin.Lichess && (
|
||||||
|
<LichessInput pgn={pgn} setPgn={setPgn} />
|
||||||
|
)}
|
||||||
|
|
||||||
{parsingError && (
|
{parsingError && (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<Typography color="red" textAlign="center" marginTop={1}>
|
<Typography color="red" textAlign="center" marginTop={1}>
|
||||||
@@ -133,4 +138,5 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
|||||||
const gameOriginLabel: Record<GameOrigin, string> = {
|
const gameOriginLabel: Record<GameOrigin, string> = {
|
||||||
[GameOrigin.Pgn]: "PGN",
|
[GameOrigin.Pgn]: "PGN",
|
||||||
[GameOrigin.ChessCom]: "Chess.com",
|
[GameOrigin.ChessCom]: "Chess.com",
|
||||||
|
[GameOrigin.Lichess]: "Lichess.org",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export enum GameOrigin {
|
export enum GameOrigin {
|
||||||
Pgn = "pgn",
|
Pgn = "pgn",
|
||||||
ChessCom = "chesscom",
|
ChessCom = "chesscom",
|
||||||
|
Lichess = "lichess",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EngineName {
|
export enum EngineName {
|
||||||
|
|||||||
@@ -16,3 +16,19 @@ export type LichessResponse<T> = T | LichessErrorBody;
|
|||||||
export enum LichessError {
|
export enum LichessError {
|
||||||
NotFound = "Not found",
|
NotFound = "Not found",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LichessGame {
|
||||||
|
id: string;
|
||||||
|
speed: string;
|
||||||
|
lastMoveAt: number;
|
||||||
|
players: {
|
||||||
|
white: LichessGameUser;
|
||||||
|
black: LichessGameUser;
|
||||||
|
};
|
||||||
|
pgn: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LichessGameUser {
|
||||||
|
user?: { id: string; name: string };
|
||||||
|
rating?: number;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user