| export const Platform = { |
| CHESSCOM: 'chesscom', |
| LICHESS: 'lichess', |
| PGN: 'pgn' |
| } |
|
|
| export class GameLoader { |
| constructor() {} |
|
|
| static parsePGN(pgn) { |
| const data = {}; |
| const metadataRegex = /\[(\w+)\s+"([^"]+)"\]/g; |
| let match; |
| |
| while ((match = metadataRegex.exec(pgn)) !== null) { |
| data[match[1].toLowerCase()] = match[2]; |
| } |
| |
| return data; |
| } |
|
|
| static async fetchSingleLichessGame(gameId) { |
| try { |
| const response = await fetch(`https://lichess.org/game/export/${gameId}`, { |
| headers: { 'Accept': 'application/x-chess-pgn' } |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`Failed to fetch Lichess game: ${response.status}`); |
| } |
| |
| const pgn = await response.text(); |
| return { |
| pgn: pgn.trim(), |
| url: `https://lichess.org/${gameId}` |
| }; |
| } catch (error) { |
| console.error('Error fetching single Lichess game:', error); |
| return null; |
| } |
| } |
|
|
| static async fetchSingleChessComGame(username, gameId) { |
| try { |
| const archivesResponse = await fetch(`https://api.chess.com/pub/player/${username}/games/archives`); |
| if (!archivesResponse.ok) throw new Error(`Failed to fetch archives: ${archivesResponse.status}`); |
| |
| const { archives = [] } = await archivesResponse.json(); |
| for (const archiveUrl of archives.reverse()) { |
| try { |
| const archiveResponse = await fetch(archiveUrl); |
| if (!archiveResponse.ok) continue; |
| |
| const archiveData = await archiveResponse.json(); |
| const game = archiveData.games?.find(g => g.url?.includes(gameId)); |
| |
| if (game) { |
| return { |
| pgn: game.pgn, |
| url: game.url, |
| rated: game.rated |
| }; |
| } |
| } catch (error) {} |
| } |
| |
| console.error(`Game ${gameId} not found in archives`); |
| } catch (error) { |
| console.error(`Error fetching single Chess.com game: ${error}`); |
| return null; |
| } |
| } |
|
|
| static async fetchSingleGame(username, gameId, platform = Platform.CHESSCOM) { |
| try { |
| if (platform === Platform.LICHESS) { |
| return await this.fetchSingleLichessGame(gameId); |
| } else { |
| return await this.fetchSingleChessComGame(username, gameId); |
| } |
| } catch (error) { |
| console.error(`Error fetching single game:`, error); |
| return null; |
| } |
| } |
|
|
| static async fetchPlayerAvatar(username, platform = Platform.CHESSCOM) { |
| try { |
| if (platform === Platform.LICHESS) { |
| |
| return null; |
| } else { |
| const response = await fetch(`https://api.chess.com/pub/player/${username}`); |
| if (response.ok) { |
| const data = await response.json(); |
| return data.avatar; |
| } |
| } |
| } catch (error) { |
| console.warn(`Failed to fetch avatar for ${username}:`, error); |
| } |
| return null; |
| } |
|
|
| static async loadGameFromURL() { |
| const params = new URLSearchParams(document.location.search); |
| const username = params.get("user"); |
| const gameId = params.get("id"); |
| const platform = params.get("platform") || Platform.CHESSCOM; |
| |
| if (!username || !gameId) { |
| return console.info('Missing username or gameId in URL parameters'); |
| } |
|
|
| try { |
| const game = await this.fetchSingleGame(username, gameId, platform); |
| |
| if (!game?.pgn?.trim()) { |
| console.error(`Game not found or has no PGN: ${gameId}`); |
| return this.loadEmptyGame(); |
| } |
|
|
| console.info(`Successfully loaded game: ${gameId}`); |
|
|
| const pgnData = this.parsePGN(game.pgn); |
|
|
| |
| const avatarSupport = platform === Platform.CHESSCOM; |
| const whiteAvatar = avatarSupport ? this.fetchPlayerAvatar(pgnData.white) : undefined; |
| const blackAvatar = avatarSupport ? this.fetchPlayerAvatar(pgnData.black) : undefined; |
|
|
| return { |
| username: username, |
| pgn: game.pgn, |
| result: pgnData.result || '*', |
| white: { |
| name: pgnData.white || 'Unknown', |
| elo: pgnData.whiteelo || 'Unrated', |
| avatar: whiteAvatar |
| }, |
| black: { |
| name: pgnData.black || 'Unknown', |
| elo: pgnData.blackelo || 'Unrated', |
| avatar: blackAvatar |
| }, |
| } |
| } catch (error) { |
| console.error(`Error loading game:`, error); |
| return null; |
| } |
| } |
|
|
| static loadEmptyGame() { |
| return { |
| username: 'White', |
| pgn: '', |
| result: '*', |
| white: { |
| name: 'White', |
| elo: 'Unrated' |
| }, |
| black: { |
| name: 'Black', |
| elo: 'Unrated' |
| }, |
| } |
| } |
|
|
| static loadGameFromPGN(pgn) { |
| |
| const pgnData = this.parsePGN(pgn); |
| const white = pgnData.white || 'White'; |
| const black = pgnData.black || 'Black'; |
|
|
| const whiteElo = pgnData.whiteelo || 'Unrated'; |
| const blackElo = pgnData.blackelo || 'Unrated'; |
|
|
| const username = white || black; |
|
|
| return { |
| username: username, |
| pgn: pgn, |
| result: pgnData.result || '*', |
| white: { |
| name: white, |
| elo: whiteElo |
| }, |
| black: { |
| name: black, |
| elo: blackElo |
| }, |
| } |
| } |
| } |