diff --git a/package.json b/package.json index 79a05826..1cf748ba 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "@tiptap/pm": "2.0.0-beta.220", "@tiptap/react": "2.0.0-beta.220", "@tiptap/starter-kit": "2.0.0-beta.220", - "chess-tcn": "^1.0.3", "chess.js": "1.0.0-beta.3", "chessground": "^8.3.7", "dayjs": "^1.11.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64b66f43..5b9e3f3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,6 @@ specifiers: '@types/node': ^18.14.6 '@types/react': ^18.0.28 '@types/react-dom': ^18.0.11 - chess-tcn: ^1.0.3 chess.js: 1.0.0-beta.3 chessground: ^8.3.7 dayjs: ^1.11.7 @@ -70,7 +69,6 @@ dependencies: '@tiptap/pm': 2.0.0-beta.220 '@tiptap/react': 2.0.0-beta.220_3gpavhzg2mvb4vpkw66uqh52dq '@tiptap/starter-kit': 2.0.0-beta.220_@tiptap+pm@2.0.0-beta.220 - chess-tcn: 1.0.3 chess.js: 1.0.0-beta.3 chessground: 8.3.7 dayjs: 1.11.7 @@ -4407,10 +4405,6 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: false - /chess-tcn/1.0.3: - resolution: {integrity: sha512-KvXD/+5+mf3oO5Gtnk0fF8H23CVssGzfO4riv3bohDcLEFBi0aI/aCIFcM1LoouswWX+7YFHpraiJ95VNMXUjg==} - dev: false - /chess.js/1.0.0-beta.3: resolution: {integrity: sha512-zu8QVvP0BmMzrstlsrXL2LxdHfAkrtyNftVRaJXGI5mn2ZSsgTJujvZx5BVJsPMwBkRZ+omCdZkVyVLBlKn2HA==} dev: false @@ -6713,7 +6707,7 @@ packages: resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} engines: {node: '>= 4.0'} os: [darwin] - deprecated: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2. + deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2 requiresBuild: true dependencies: bindings: 1.5.0 diff --git a/src/components/tabs/NewTabHome.tsx b/src/components/tabs/NewTabHome.tsx index 51b3b387..a7cef74c 100644 --- a/src/components/tabs/NewTabHome.tsx +++ b/src/components/tabs/NewTabHome.tsx @@ -241,8 +241,7 @@ function ImportModal({ if (!link) return; let pgn = ""; if (link.includes("chess.com")) { - const gameId = link.split("/").pop()!; - pgn = await getChesscomGame(gameId); + pgn = await getChesscomGame(link); } else if (link.includes("lichess")) { const gameId = link.split("/")[3]; pgn = await getLichessGame(gameId); diff --git a/src/utils/chess.ts b/src/utils/chess.ts index 8768ece9..fade9a91 100644 --- a/src/utils/chess.ts +++ b/src/utils/chess.ts @@ -621,10 +621,10 @@ export function getCompleteGame(pgn: string): CompleteGame { return game; } -export function handleMove(chess: Chess, orig: Key, dest: Key): Square | null { +export function handleMove(chess: Chess, orig: Key, dest: Key): Square { if (orig === "a0" || dest === "a0") { // NOTE: Idk if this can happen - return null; + throw new Error("Invalid move"); } // allow castling to the rooks if (chess.get(orig).type === KING && chess.get(dest).type === ROOK) { diff --git a/src/utils/chesscom.ts b/src/utils/chesscom.ts index 6c886ae9..d4c76d95 100644 --- a/src/utils/chesscom.ts +++ b/src/utils/chesscom.ts @@ -1,8 +1,9 @@ import { writeTextFile } from "@tauri-apps/api/fs"; import { fetch } from "@tauri-apps/api/http"; import { appDataDir, resolve } from "@tauri-apps/api/path"; -import tcn from "chess-tcn"; import { Chess } from "chess.js"; +import { handleMove } from "./chess"; +import { decodeTCN } from "./tcn"; const base_url = "https://api.chess.com"; type ChessComPerf = { @@ -97,10 +98,20 @@ export async function downloadChessCom( ); } -export async function getChesscomGame(gameId: string) { - const apiData = await fetch<{ game: { moveList: string, pgnHeaders: any } }>( - `https://www.chess.com/callback/live/game/${gameId}` - ); +export async function getChesscomGame(gameURL: string) { + const regex = /.*\/game\/(live|daily)\/(\d+)/; + const match = gameURL.match(regex); + + if (!match) { + return ""; + } + + const gameType = match[1]; + const gameId = match[2]; + + const apiData = await fetch<{ + game: { moveList: string; pgnHeaders: any }; + }>(`https://www.chess.com/callback/${gameType}/game/${gameId}`); const apiDataJson = await apiData.data; const moveList = apiDataJson.game.moveList; const headers = apiDataJson.game.pgnHeaders; @@ -113,7 +124,9 @@ export async function getChesscomGame(gameId: string) { chess.header(header, headers[header]); } moves.forEach((move) => { - const m = tcn.decode(move); + const m = decodeTCN(move); + const newDest = handleMove(chess, m.from, m.to); + m.to = newDest; chess.move(m); }); return chess.pgn(); diff --git a/src/utils/tcn.ts b/src/utils/tcn.ts new file mode 100644 index 00000000..c1543a21 --- /dev/null +++ b/src/utils/tcn.ts @@ -0,0 +1,38 @@ +import { Move, PieceSymbol, Square } from "chess.js"; + +const pieceEncoding = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?{~}(^)[_]@#$,./&-*++="; + +export function decodeTCN(moveCode: string): Move { + // @ts-ignore + const move: Move = {}; + const codeLength = moveCode.length; + const decodedMoves: Move[] = []; + + for (let i = 0; i < codeLength; i += 2) { + const pieceIndex1 = pieceEncoding.indexOf(moveCode[i]); + let pieceIndex2 = pieceEncoding.indexOf(moveCode[i + 1]); + + if (pieceIndex2 > 63) { + const promotion = "qnrbkp"[Math.floor((pieceIndex2 - 64) / 3)]; + const newIndex = + pieceIndex1 + + (pieceIndex1 < 16 ? -8 : 8) + + ((pieceIndex2 - 1) % 3) - + 1; + + move.promotion = promotion as PieceSymbol; + pieceIndex2 = newIndex; + } + + move.from = (pieceEncoding[pieceIndex1 % 8] + + (Math.floor(pieceIndex1 / 8) + 1).toString()) as Square; + + move.to = (pieceEncoding[pieceIndex2 % 8] + + (Math.floor(pieceIndex2 / 8) + 1).toString()) as Square; + + decodedMoves.push(move); + } + + return decodedMoves[0]; +}