/** * 解析让球方 */ const ratioAccept = (ratio) => { if (ratio > 0) { return 'a'; } return ''; } /** * 将比率转换为字符串 * @param {*} ratio * @returns */ const ratioString = (ratio) => { ratio = Math.abs(ratio); ratio = ratio.toString(); ratio = ratio.replace(/\./, ''); return ratio; } /** * 解析胜平负盘口 * @param {*} moneyline * @returns */ const parseMoneyline = (moneyline) => { // 胜平负 if (!moneyline) { return null; } const { home, away, draw } = moneyline; return { 'ior_mh': { v: home }, 'ior_mc': { v: away }, 'ior_mn': { v: draw }, } } /** * 解析让分盘口 * @param {*} spreads * @param {*} wm * @returns */ const parseSpreads = (spreads, wm) => { // 让分盘 if (!spreads?.length) { return null; } const events = {}; spreads.forEach(spread => { const { hdp, home, away } = spread; // if (!(hdp % 1) || !!(hdp % 0.5)) { // // 整数或不能被0.5整除的让分盘不处理 // return; // } const ratio_ro = hdp; const ratio_r = ratio_ro - wm; events[`ior_r${ratioAccept(ratio_r)}h_${ratioString(ratio_r)}`] = { v: home, r: wm != 0 ? `ior_r${ratioAccept(ratio_ro)}h_${ratioString(ratio_ro)}` : undefined }; events[`ior_r${ratioAccept(-ratio_r)}c_${ratioString(ratio_r)}`] = { v: away, r: wm != 0 ? `ior_r${ratioAccept(-ratio_ro)}c_${ratioString(ratio_ro)}` : undefined }; }); return events; } /** * 解析大小球盘口 * @param {*} totals * @returns */ const parseTotals = (totals) => { // 大小球盘 if (!totals?.length) { return null; } const events = {}; totals.forEach(total => { const { points, over, under } = total; events[`ior_ouc_${ratioString(points)}`] = { v: over }; events[`ior_ouh_${ratioString(points)}`] = { v: under }; }); return events; } /** * 解析直赛盘口 * @param {*} straight * @param {*} wm * @returns */ const parseStraight = (straight, wm) => { if (!straight) { return null; } const { cutoff='', status=0, spreads=[], moneyline={}, totals=[] } = straight; const cutoffTime = new Date(cutoff).getTime(); const nowTime = Date.now(); if (status != 1 || cutoffTime < nowTime) { return null; } const events = {}; Object.assign(events, parseSpreads(spreads, wm)); Object.assign(events, parseMoneyline(moneyline)); Object.assign(events, parseTotals(totals)); return events; } /** * 解析净胜球盘口 * @param {*} winningMargin * @param {*} home * @param {*} away * @returns */ const parseWinningMargin = (winningMargin, home, away) => { if (!winningMargin) { return null; } const { cutoff='', status='', contestants=[] } = winningMargin; const cutoffTime = new Date(cutoff).getTime(); const nowTime = Date.now(); if (status != 'O' || cutoffTime < nowTime || !contestants?.length) { return null; } const events = {}; contestants.forEach(contestant => { const { name, price } = contestant; const nr = name.match(/\d+$/)?.[0]; if (!nr) { return; } let side; if (name.startsWith(home)) { side = 'h'; } else if (name.startsWith(away)) { side = 'c'; } else { return; } events[`ior_wm${side}_${nr}`] = { v: price }; }); return events; } /** * 解析进球数盘口 * @param {*} exactTotalGoals * @returns */ const parseExactTotalGoals = (exactTotalGoals) => { if (!exactTotalGoals) { return null; } const { cutoff='', status='', contestants=[] } = exactTotalGoals; const cutoffTime = new Date(cutoff).getTime(); const nowTime = Date.now(); if (status != 'O' || cutoffTime < nowTime || !contestants?.length) { return null; } const events = {}; contestants.forEach(contestant => { const { name, price } = contestant; if (+name >= 1 && +name <= 7) { events[`ior_ot_${name}`] = { v: price }; } }); return events; } /** * 解析滚球场次状态 * @param {*} state * @returns */ const parseRbState = (state) => { let stage = null; if (state == 1) { stage = '1H'; } else if (state == 2) { stage = 'HT'; } else if (state == 3) { stage = '2H'; } else if (state >= 4) { stage = 'ET'; } return stage; } /** * 解析比赛数据 * @param {*} game * @returns */ export const parseGame = (game, filtedGames) => { const { eventId=0, originId=0, periods={}, specials={}, home, away, marketType, state, elapsed, homeScore=0, awayScore=0 } = game; const { straight, straight1st } = periods; const { winningMargin={}, exactTotalGoals={}, winningMargin1st={}, exactTotalGoals1st={} } = specials; const filtedGamesSet = new Set(filtedGames); const wm = homeScore - awayScore; const score = `${homeScore}-${awayScore}`; const events = parseStraight(straight, wm) ?? {}; const stage = parseRbState(state); const retime = elapsed ? `${elapsed}'` : ''; const evtime = Date.now(); Object.assign(events, parseWinningMargin(winningMargin, home, away)); Object.assign(events, parseExactTotalGoals(exactTotalGoals)); const gameInfos = []; gameInfos.push({ eventId, originId, events, evtime, stage, retime, score, wm, marketType }); const halfEventId = eventId * -1; if (filtedGamesSet.has(halfEventId)) { const events = parseStraight(straight1st, wm) ?? {}; Object.assign(events, parseWinningMargin(winningMargin1st, home, away)); Object.assign(events, parseExactTotalGoals(exactTotalGoals1st)); gameInfos.push({ eventId: halfEventId, originId, events, evtime, stage, retime, score, wm, marketType }); } return gameInfos; } /* * 解析比率信息 */ const parseRatio = (ratioString) => { if (!ratioString) { return null; } return parseFloat(`${ratioString[0]}.${ratioString.slice(1)}`); } /** * 解析盘口信息 * @param {*} ior * @returns */ const parseIor = (ior) => { const iorMatch = ior.match(/ior_(m|r|ou|wm|ot)([ao])?([hcn])?_?(\d+)?/); if (!iorMatch) { return null; } const [, type, action, side, ratio] = iorMatch; return { type, action, side, ratio }; } /** * 解析盘口详情 * @param {*} ior * @param {*} id * @param {*} gamesMap * @returns */ export const parseIorDetail = (ior, id, gamesMap) => { const gamesData = gamesMap[id]; if (!gamesData) { return { ior, id, message: 'games data not found', cause: 400 }; } const iorOptions = parseIor(ior); if (!iorOptions) { return { ior, id, message: 'ior options not found', cause: 400 }; } const { type, action, side, ratio } = iorOptions; const { leagueId, id: eventId, home: homeTeamName, away: awayTeamName, periods={}, specials={}} = gamesData; const straightData = periods.straight ?? {}; const { lineId: straightLineId, moneyline, spreads, totals } = straightData; const { winningMargin, exactTotalGoals } = specials; if (type === 'm' && moneyline && !action && !ratio) { const sideKey = side === 'h' ? 'home' : side === 'c' ? 'away' : 'draw'; const team = side === 'h' ? 'TEAM1' : side === 'c' ? 'TEAM2' : 'DRAW'; const odds = moneyline[sideKey]; return { leagueId, eventId, betType: 'MONEYLINE', team, lineId: straightLineId, odds }; } else if (type === 'r' && spreads) { let ratioDirection = 1; if (side === 'c' && action === 'a' || side === 'h' && !action) { ratioDirection = -1; } const ratioKey = parseRatio(ratio) * ratioDirection; const itemSpread = spreads.find(spread => spread.hdp == ratioKey); if (!itemSpread) { return { ior, id, message: 'item spread not found', cause: 400 }; } const { altLineId=null, home, away } = itemSpread; const odds = side === 'h' ? home : away; const team = side === 'h' ? 'TEAM1' : 'TEAM2'; const handicap = ratioKey * (side === 'h' ? 1 : -1); return { leagueId, eventId, handicap, betType: 'SPREAD', team, lineId: straightLineId, altLineId, odds }; } else if (type === 'ou' && totals) { const ratioKey = parseRatio(ratio); const itemTotal = totals.find(total => total.points == ratioKey); if (!itemTotal) { return { ior, id, message: 'item total not found', cause: 400 }; } const { altLineId=null, over, under } = itemTotal; const odds = side === 'c' ? over : under; const sideKey = side === 'c' ? 'OVER' : 'UNDER'; return { leagueId, eventId, handicap: ratioKey, betType: 'TOTAL_POINTS', side: sideKey, lineId: straightLineId, altLineId, odds }; } else if (type === 'wm' && winningMargin) { const ratioKey = parseRatio(ratio); const { id: specialId } = winningMargin; const wmName = side === 'h' ? `${homeTeamName} By ${ratioKey}` : side === 'c' ? `${awayTeamName} By ${ratioKey}` : ''; const wmItem = winningMargin.contestants.find(contestant => contestant.name == wmName); if (!wmItem) { return { ior, id, message: 'item winning margin not found', cause: 400 }; } const { id: contestantId, lineId, price } = wmItem; return { leagueId, eventId, specialId, contestantId, lineId, odds: price }; } else if (type === 'ot' && exactTotalGoals) { const ratioKey = parseRatio(ratio); const { id: specialId } = exactTotalGoals; const otItem = exactTotalGoals.contestants.find(contestant => contestant.name == ratioKey); if (!otItem) { Logs.outDev('pinnacle item exact total goals not found', id, type, action, side, ratio); return { ior, id, message: 'item exact total goals not found', cause: 400 }; } const { id: contestantId, lineId, price } = otItem; return { leagueId, eventId, specialId, contestantId, lineId, odds: price }; } else { return { ior, id, message: 'ior type not found', cause: 400 }; } }