UNPKG

@hardbulls/wbsc-crawler

Version:

Tool to crawl events, leagues and statistics from WBSC based websites.

256 lines (255 loc) 9.99 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LivescoreCrawler = void 0; const date_fns_tz_1 = require("date-fns-tz"); const GameStatus_1 = require("./Model/GameStatus"); const fetch_1 = require("./fetch"); const LIVESCORES_URL = "https://game.wbsc.org/gamedata/livescores.json"; const GAMEDATA_BASE_URL = "https://game.wbsc.org/gamedata"; function parseInning(raw) { const match = raw.match(/^([TB])(\d+)/); if (!match) { return null; } return { half: match[1] === "T" ? "top" : "bottom", number: parseInt(match[2], 10), }; } function mapStatus(raw) { const code = parseInt(raw, 10); if (code === 1) return GameStatus_1.GameStatus.ONGOING; if (code === 2 || code === 3) return GameStatus_1.GameStatus.FINISHED; if (code === 4) return GameStatus_1.GameStatus.FORFEIT; if (code === -2) return GameStatus_1.GameStatus.SUSPENDED; if (code === -3) return GameStatus_1.GameStatus.CANCELED; return GameStatus_1.GameStatus.SCHEDULED; } function mapLivescore(raw) { var _a, _b; const inning = parseInning(raw.inning); return { gameId: raw.gameid, tournamentId: raw.tournamentid, inning: (_a = inning === null || inning === void 0 ? void 0 : inning.number) !== null && _a !== void 0 ? _a : null, inningHalf: (_b = inning === null || inning === void 0 ? void 0 : inning.half) !== null && _b !== void 0 ? _b : null, homeRuns: parseInt(raw.homeruns, 10) || 0, awayRuns: parseInt(raw.awayruns, 10) || 0, balls: parseInt(raw.balls, 10) || 0, strikes: parseInt(raw.strikes, 10) || 0, outs: parseInt(raw.outs, 10) || 0, runner1: raw.runner1 === "1", runner2: raw.runner2 === "1", runner3: raw.runner3 === "1", pitcher: raw.pitcher, batter: raw.batter, status: mapStatus(raw.status), start: (0, date_fns_tz_1.fromZonedTime)(raw.start, raw.start_tz), }; } function resolveRunner(raw, boxscore) { var _a, _b; if (!raw || raw === 0 || raw === "0") return null; return (_b = (_a = boxscore[String(raw)]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : null; } function mapInningHalf(currentInning) { const upper = currentInning.toUpperCase(); if (upper.startsWith("TOP")) return "top"; if (upper.startsWith("BOT")) return "bottom"; return null; } function parseBatterRecord(raw) { const match = raw.match(/^(\d+) for (\d+)/); return match ? { hits: parseInt(match[1], 10), atBats: parseInt(match[2], 10) } : { hits: 0, atBats: 0 }; } function mapPitchOutcome(r1) { switch (r1) { case "2": return "ball"; case "4": return "calledStrike"; case "5": return "swingingStrike"; case "6": return "foul"; case "9": return "inPlay"; default: return "unknown"; } } function mapPitchType(type) { if (type === 1) return "ball"; if (type === 2) return "strike"; if (type === 3) return "inPlay"; return "event"; } function mapLineup(boxscore, teamCode) { const entries = Object.entries(boxscore) .filter(([, e]) => e.teamcode === teamCode) .sort(([a], [b]) => parseInt(a, 10) - parseInt(b, 10)) .map(([, e]) => e); const batters = entries .filter((e) => e.POS !== undefined) .map((e) => { var _a, _b, _c, _d, _e, _f, _g, _h; return ({ name: e.name, playerId: e.playerid, image: e.image, position: e.POS, plateAppearances: (_a = e.PA) !== null && _a !== void 0 ? _a : 0, atBats: (_b = e.AB) !== null && _b !== void 0 ? _b : 0, runs: (_c = e.R) !== null && _c !== void 0 ? _c : 0, hits: (_d = e.H) !== null && _d !== void 0 ? _d : 0, rbi: (_e = e.RBI) !== null && _e !== void 0 ? _e : 0, walks: (_f = e.BB) !== null && _f !== void 0 ? _f : 0, strikeouts: (_g = e.SO) !== null && _g !== void 0 ? _g : 0, average: (_h = e.AVG) !== null && _h !== void 0 ? _h : ".000", }); }); const pitchers = entries .filter((e) => e.PITCHIP !== undefined) .map((e) => { var _a, _b, _c, _d, _e; return ({ name: e.name, playerId: e.playerid, image: e.image, inningsPitched: e.PITCHIP, earnedRuns: (_a = e.PITCHER) !== null && _a !== void 0 ? _a : 0, hitsAllowed: (_b = e.PITCHH) !== null && _b !== void 0 ? _b : 0, walksAllowed: (_c = e.PITCHBB) !== null && _c !== void 0 ? _c : 0, strikeouts: (_d = e.PITCHSO) !== null && _d !== void 0 ? _d : 0, earnedRunAverage: (_e = e.ERA) !== null && _e !== void 0 ? _e : "0.00", }); }); return { batters, pitchers }; } function mapLivePlay(raw) { var _a, _b; const pitchSequence = [...raw.platecount] .reverse() .map((entry) => ({ type: mapPitchType(entry.type), description: entry.label, })); const situation = { currentInning: raw.situation.currentinning, inning: parseInt(raw.situation.inning, 10) || 0, inningHalf: mapInningHalf(raw.situation.currentinning), batter: { name: raw.situation.batter, playerId: raw.situation.batterid }, batterRecord: parseBatterRecord(raw.situation.batting), batterAverage: raw.situation.avg, pitcher: { name: raw.situation.pitcher, playerId: raw.situation.pitcherid }, earnedRunAverage: raw.situation.pitcherera, inningsPitched: raw.situation.pitcherip, pitchSequence, runner1: resolveRunner(raw.situation.runner1, raw.boxscore), runner2: resolveRunner(raw.situation.runner2, raw.boxscore), runner3: resolveRunner(raw.situation.runner3, raw.boxscore), outs: raw.situation.outs, balls: raw.situation.balls, strikes: raw.situation.strikes, }; let currentHalf = null; const plays = [...raw.playdata] .reverse() .reduce((acc, entry) => { if (entry.p === 0 || entry.p === "0") { if (entry.n.includes("***TOP")) currentHalf = "top"; else if (entry.n.includes("***BOT")) currentHalf = "bottom"; return acc; } acc.push({ description: entry.n, inning: parseInt(entry.i, 10) || 0, inningHalf: currentHalf, pitchOutcome: mapPitchOutcome(entry.r1), }); return acc; }, []); return { lastPlay: raw.lastplayloaded, gameId: raw.gameid, gameOver: raw.gameover !== 0, eventLocation: raw.eventlocation, eventHome: raw.eventhome, eventAway: raw.eventaway, eventHomeId: raw.eventhomeid, eventAwayId: raw.eventawayid, regulationInnings: parseInt(raw.innings, 10), situation, homeLineup: mapLineup(raw.boxscore, raw.eventhome), awayLineup: mapLineup(raw.boxscore, raw.eventaway), linescore: { awayRunsByInning: raw.linescore.awayruns, homeRunsByInning: raw.linescore.homeruns, awayTotals: { runs: raw.linescore.awaytotals.R, hits: raw.linescore.awaytotals.H, errors: raw.linescore.awaytotals.E, }, homeTotals: { runs: raw.linescore.hometotals.R, hits: raw.linescore.hometotals.H, errors: raw.linescore.hometotals.E, }, }, plays, lastPlayDescription: (_b = (_a = raw.playdata[0]) === null || _a === void 0 ? void 0 : _a.n) !== null && _b !== void 0 ? _b : "", }; } function fetchLivePlay(gameId) { return __awaiter(this, void 0, void 0, function* () { const latestRes = yield (0, fetch_1.fetchUrl)(`${GAMEDATA_BASE_URL}/${gameId}/latest.json`, { method: "GET" }); const latestPlay = yield latestRes.json(); const playRes = yield (0, fetch_1.fetchUrl)(`${GAMEDATA_BASE_URL}/${gameId}/play${latestPlay}.json`, { method: "GET" }); const raw = yield playRes.json(); return mapLivePlay(raw); }); } exports.LivescoreCrawler = { crawl: () => __awaiter(void 0, void 0, void 0, function* () { const response = yield (0, fetch_1.fetchUrl)(LIVESCORES_URL, { method: "GET" }); const data = yield response.json(); return data.map(mapLivescore); }), crawlPlay: (gameId) => __awaiter(void 0, void 0, void 0, function* () { return fetchLivePlay(gameId); }), crawlByGameId: (gameId) => __awaiter(void 0, void 0, void 0, function* () { const response = yield (0, fetch_1.fetchUrl)(LIVESCORES_URL, { method: "GET" }); const data = yield response.json(); const raw = data.find((entry) => entry.gameid === gameId); if (!raw) return null; const livescore = mapLivescore(raw); livescore.play = yield fetchLivePlay(gameId); return livescore; }), };