GamesPs.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. const axios = require('axios');
  2. const Logs = require('../libs/logs');
  3. const Cache = require('../libs/cache');
  4. const Setting = require('./Setting');
  5. const { eventSolutions } = require('../triangle/eventSolutions');
  6. const { calcTotalProfit, calcTotalProfitWithFixedFirst } = require('../triangle/totalProfitCalc');
  7. const { getSetting, updateSetting } = require('../triangle/settings');
  8. const fs = require('fs');
  9. const path = require('path');
  10. const GamesCacheFile = path.join(__dirname, '../data/games.cache');
  11. const DevGameTastFile = path.join(__dirname, '../data/gameTast.json');
  12. const childOptions = process.env.NODE_ENV == 'development' ? {
  13. execArgv: ['--inspect=9230'],
  14. stdio: ['pipe', 'pipe', 'pipe', 'ipc']
  15. } : {};
  16. const { fork } = require('child_process');
  17. const events_child = fork('./triangle/eventsMatch.js', [], childOptions);
  18. const PS_IOR_KEYS = [
  19. ['0', 'ior_mh', 'ior_mn', 'ior_mc'],
  20. ['-1', 'ior_rh_15', 'ior_wmh_1', 'ior_rac_05'],
  21. ['-2', 'ior_rh_25', 'ior_wmh_2', 'ior_rac_15'],
  22. ['-3', 'ior_rh_35', 'ior_wmh_3', 'ior_rac_25'],
  23. ['-4', 'ior_rh_45', 'ior_wmh_4', 'ior_rac_35'],
  24. ['-5', 'ior_rh_55', 'ior_wmh_5', 'ior_rac_45'],
  25. ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
  26. ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
  27. ['+3', 'ior_rah_25', 'ior_wmc_3', 'ior_rc_35'],
  28. ['+4', 'ior_rah_35', 'ior_wmc_4', 'ior_rc_45'],
  29. ['+5', 'ior_rah_45', 'ior_wmc_5', 'ior_rc_55'],
  30. ['jqs', 'ior_ot_1', 'ior_ot_2', 'ior_ot_3', 'ior_ot_4', 'ior_ot_5', 'ior_ot_6', 'ior_ot_7'],
  31. ];
  32. const IOR_KEYS_TYPE = {
  33. A: 1, L: 1,
  34. D: 2, K: 2,
  35. }
  36. // 测试环境
  37. // const BASE_API_URL = 'https://dev.api.czxd8.com/api/p';
  38. const IS_DEV = process.env.NODE_ENV == 'development';
  39. const BASE_API_URL = IS_DEV ? 'https://api.qboss.vip/api' : 'http://172.17.222.37/api';
  40. const GAMES = {
  41. Leagues: {},
  42. Baselist: {},
  43. Relations: {},
  44. Solutions: {},
  45. ObOriginalData: {},
  46. UpdateTimestamp: {},
  47. };
  48. const Request = {
  49. callbacks: {},
  50. count: 0,
  51. }
  52. /**
  53. * 精确浮点数字
  54. * @param {number} number
  55. * @param {number} x
  56. * @returns {number}
  57. */
  58. const fixFloat = (number, x=2) => {
  59. return parseFloat(number.toFixed(x));
  60. }
  61. /**
  62. * 获取市场类型
  63. */
  64. const getMarketType = (mk) => {
  65. if (mk == 0) {
  66. return 'early';
  67. }
  68. else if (mk == 2) {
  69. return 'rollball';
  70. }
  71. return 'today';
  72. }
  73. /**
  74. * 获取策略类型
  75. */
  76. const getRuleType = (rule) => {
  77. const rulePrefix = rule.split(':')[0];
  78. return IOR_KEYS_TYPE[rulePrefix];
  79. }
  80. /**
  81. * 关键词匹配比赛
  82. */
  83. const matchGame = (relation, sk) => {
  84. const keys = [];
  85. Object.keys(relation).forEach(platform => {
  86. const { leagueName, teamHomeName, teamAwayName } = relation[platform];
  87. if (platform == 'ps') {
  88. keys.push(leagueName);
  89. }
  90. keys.push(teamHomeName, teamAwayName);
  91. });
  92. return keys.some(key => key.includes(sk));
  93. }
  94. /**
  95. * 同步联赛列表
  96. */
  97. const syncLeaguesList = ({ mk, leagues }) => {
  98. if (IS_DEV) {
  99. return Logs.out('syncLeaguesList', { mk, leagues });
  100. }
  101. axios.post(`${BASE_API_URL}/p/syncLeague`, { mk, leagues }, { proxy: false })
  102. .then(res => {
  103. // Logs.out('syncLeaguesList', res.data);
  104. })
  105. .catch(err => {
  106. Logs.out('syncLeaguesList', err.message);
  107. });
  108. }
  109. /**
  110. * 更新联赛列表
  111. */
  112. const updateLeaguesList = ({ mk, leagues, platform='ps' }) => {
  113. const { Leagues } = GAMES;
  114. if (!Leagues[platform]) {
  115. Leagues[platform] = {};
  116. }
  117. const leaguesMap = Leagues[platform];
  118. const nowTime = Date.now();
  119. const expireTime = nowTime - 1000 * 60 * 5;
  120. if (!leaguesMap[mk]) {
  121. leaguesMap[mk] = {
  122. timestamp: 0,
  123. leagues: [],
  124. };
  125. }
  126. if (leaguesMap[mk].timestamp < expireTime ||
  127. JSON.stringify(leaguesMap[mk].leagues) != JSON.stringify(leagues)) {
  128. leaguesMap[mk].leagues = leagues;
  129. leaguesMap[mk].timestamp = nowTime;
  130. if (platform == 'ps') {
  131. syncLeaguesList({ mk, leagues });
  132. }
  133. return leagues.length;
  134. }
  135. return 0;
  136. }
  137. /**
  138. * 获取筛选过的联赛
  139. */
  140. const getFilteredLeagues = async (mk) => {
  141. return axios.get(`${BASE_API_URL}/p/getLeagueTast?mk=${mk ?? ''}`, { proxy: false })
  142. .then(res => {
  143. if (res.data.code == 0) {
  144. return res.data.data;
  145. }
  146. return Promise.reject(new Error(res.data.message));
  147. });
  148. }
  149. /**
  150. * 更新OB原始数据
  151. */
  152. const updateOriginalData = ({ leagues, matches }) => {
  153. const { ObOriginalData } = GAMES;
  154. ObOriginalData.leagues = leagues;
  155. ObOriginalData.matches = matches;
  156. ObOriginalData.timestamp = Date.now();
  157. }
  158. /**
  159. * 获取OB原始数据
  160. */
  161. const getOriginalData = () => {
  162. const { ObOriginalData } = GAMES;
  163. return ObOriginalData;
  164. }
  165. /**
  166. * 同步比赛列表到服务器
  167. */
  168. const syncGamesList = ({ platform, mk, games }) => {
  169. if (IS_DEV) {
  170. return Logs.out('syncGamesList', { platform, mk, games });
  171. }
  172. axios.post(`${BASE_API_URL}/p/syncGames`, { platform, mk, games })
  173. .then(res => {
  174. // Logs.out('syncGamesList', { platform, mk, count: games.length }, res.data);
  175. })
  176. .catch(err => {
  177. Logs.out('syncGamesList', { platform, mk }, err.message);
  178. });
  179. }
  180. /**
  181. * 同步基准比赛列表
  182. */
  183. const syncBaseList = ({ marketType, games }) => {
  184. const baseList = GAMES.Baselist;
  185. // 直接创建新列表
  186. if (!baseList[marketType]) {
  187. baseList[marketType] = games;
  188. return;
  189. }
  190. const newMap = new Map(games.map(item => [item.eventId, item]));
  191. // 删除不存在的项
  192. for (let i = baseList[marketType].length - 1; i >= 0; i--) {
  193. if (!newMap.has(baseList[marketType][i].eventId)) {
  194. baseList[marketType].splice(i, 1);
  195. }
  196. }
  197. // 添加或更新
  198. const oldIds = new Set(baseList[marketType].map(item => item.eventId));
  199. games.forEach(game => {
  200. if (!oldIds.has(game.eventId)) {
  201. // 添加新项
  202. baseList[marketType].push(game);
  203. }
  204. });
  205. }
  206. /**
  207. * 清理基准比赛列表
  208. */
  209. // const cleanupBaseList = () => {
  210. // const baseList = GAMES.Baselist;
  211. // const nowTime = Date.now();
  212. // const expireTime = nowTime - 1000*60*60*3;
  213. // Object.keys(baseList).forEach(marketType => {
  214. // baseList[marketType] = baseList[marketType].filter(item => item.timestamp < expireTime);
  215. // });
  216. // }
  217. /**
  218. * 更新比赛列表
  219. */
  220. const updateGamesList = (({ platform, mk, games } = {}) => {
  221. return new Promise((resolve, reject) => {
  222. if (!platform || !games) {
  223. return reject(new Error('PLATFORM_GAMES_INVALID'));
  224. }
  225. syncGamesList({ platform, mk, games });
  226. resolve();
  227. });
  228. });
  229. /**
  230. * 提交盘口数据
  231. */
  232. // const submitOdds = ({ platform, mk, games }) => {
  233. // if (IS_DEV) {
  234. // return Logs.out('syncOdds', { platform, mk, games });
  235. // }
  236. // axios.post(`${BASE_API_URL}/p/syncOdds`, { platform, mk, games}, { proxy: false })
  237. // .then(res => {
  238. // // Logs.out('syncOdds', { platform, mk, count: games.length }, res.data);
  239. // })
  240. // .catch(err => {
  241. // Logs.out('syncOdds', { platform, mk }, err.message);
  242. // });
  243. // }
  244. /**
  245. * 同步基准盘口
  246. */
  247. const syncBaseEvents = ({ mk, games, outrights }) => {
  248. const {
  249. expireTimeEvents, expireTimeSpecial,
  250. subsidyTime, subsidyAmount,
  251. subsidyRbWmAmount, subsidyRbOtAmount
  252. } = getSetting();
  253. const nowTime = Date.now();
  254. const marketType = getMarketType(mk);
  255. const baseList = GAMES.Baselist;
  256. if (!baseList[marketType]) {
  257. return 0;
  258. }
  259. const baseMap = new Map(baseList[marketType].map(item => [item.eventId, item]));
  260. games?.forEach(game => {
  261. const { eventId, originId, stage, retime, score, wm, evtime, events } = game;
  262. const baseGame = baseMap.get(eventId);
  263. if (baseGame) {
  264. const { mk, timestamp } = baseGame;
  265. const isRb = (mk == 2);
  266. const isSubsidy = timestamp > nowTime && timestamp < nowTime + 1000*60*60*subsidyTime;
  267. Object.keys(events).forEach(ior => {
  268. if (isRb && (ior.startsWith('ior_r') || ior.startsWith('ior_m') || ior.startsWith('ior_wm')) && subsidyRbWmAmount) {
  269. // 滚球胜平负
  270. const sourceOdds = events[ior].v;
  271. events[ior].v = fixFloat(sourceOdds * (1 + subsidyRbWmAmount), 3);
  272. events[ior].s = sourceOdds
  273. }
  274. else if (isRb && ior.startsWith('ior_ot') && subsidyRbOtAmount) {
  275. // 滚球进球数
  276. const sourceOdds = events[ior].v;
  277. events[ior].v = fixFloat(sourceOdds * (1 + subsidyRbOtAmount), 3);
  278. events[ior].s = sourceOdds
  279. }
  280. else if (!isRb && isSubsidy && ior.startsWith('ior_ot') && subsidyAmount) {
  281. // 赛前进球数
  282. const sourceOdds = events[ior].v;
  283. events[ior].v = fixFloat(sourceOdds * (1 + subsidyAmount), 3);
  284. events[ior].s = sourceOdds
  285. }
  286. });
  287. baseGame.originId = originId;
  288. baseGame.stage = stage;
  289. baseGame.retime = retime;
  290. baseGame.score = score;
  291. baseGame.wm = wm;
  292. baseGame.evtime = evtime;
  293. baseGame.events = events;
  294. }
  295. });
  296. outrights?.forEach(outright => {
  297. const { parentId, sptime, special } = outright;
  298. const baseGame = baseMap.get(parentId);
  299. if (baseGame) { // 赛前特殊盘口
  300. const { timestamp } = baseGame;
  301. const isSubsidy = timestamp > nowTime && timestamp < nowTime + 1000*60*60*subsidyTime;
  302. if (isSubsidy) {
  303. Object.keys(special).forEach(ior => {
  304. if (ior.startsWith('ior_ot') && subsidyAmount) {
  305. const sourceOdds = special[ior].v;
  306. special[ior].v = fixFloat(sourceOdds * (1 + subsidyAmount), 3);
  307. special[ior].s = sourceOdds
  308. }
  309. });
  310. }
  311. baseGame.sptime = sptime;
  312. baseGame.special = special;
  313. }
  314. else {
  315. const originBaseMap = new Map(baseList[marketType].map(item => [item.originId, item]));
  316. const originBaseGame = originBaseMap.get(parentId);
  317. if (originBaseGame) { // 滚球特殊盘口
  318. Object.keys(special).forEach(ior => {
  319. if (ior.startsWith('ior_wm') && subsidyRbWmAmount) {
  320. const sourceOdds = special[ior].v;
  321. special[ior].v = fixFloat(sourceOdds * (1 + subsidyRbWmAmount), 3);
  322. special[ior].s = sourceOdds
  323. }
  324. else if (ior.startsWith('ior_ot') && subsidyRbOtAmount) {
  325. const sourceOdds = special[ior].v;
  326. special[ior].v = fixFloat(sourceOdds * (1 + subsidyRbOtAmount), 3);
  327. special[ior].s = sourceOdds
  328. }
  329. });
  330. originBaseGame.sptime = sptime;
  331. originBaseGame.special = special;
  332. }
  333. }
  334. });
  335. if (games?.length) {
  336. const gamesList = baseList[marketType]?.map(game => {
  337. const { evtime, events, sptime, special, ...gameInfo } = game;
  338. const expireTimeEv = nowTime - expireTimeEvents;
  339. const expireTimeSP = nowTime - expireTimeSpecial;
  340. let odds = {};
  341. if (evtime > expireTimeEv) {
  342. odds = { ...odds, ...events };
  343. }
  344. if (sptime > expireTimeSP) {
  345. odds = { ...odds, ...special };
  346. }
  347. const matches = PS_IOR_KEYS.map(([label, ...keys]) => {
  348. let match = keys.map(key => {
  349. return {
  350. key,
  351. value: odds[key]?.v ?? 0,
  352. origin: odds[key]?.r,
  353. source: odds[key]?.s,
  354. }
  355. });
  356. if (label == 'jqs') {
  357. match = match.filter(item => item.value !== 0);
  358. }
  359. return {
  360. label,
  361. match
  362. };
  363. }).filter(item => {
  364. if (item.label == 'jqs') {
  365. return item.match.length;
  366. }
  367. else {
  368. return item.match.every(entry => entry.value !== 0);
  369. }
  370. });
  371. game.matches = matches; // matches 也记录下来
  372. let uptime = evtime ?? 0;
  373. return { ...gameInfo, matches, uptime };
  374. });
  375. // if (gamesList.filter(item => item.uptime > 0).length) {
  376. // // if (mk == 2) {
  377. // // Logs.out('syncBaseEvents', JSON.stringify({ mk, gamesList }, null, 2));
  378. // // }
  379. // submitOdds({ platform: 'ps', mk, games: gamesList });
  380. // }
  381. const relatedGames = Object.values(GAMES.Relations).map(item => item.rel?.['ps'] ?? {});
  382. if (!relatedGames.length) {
  383. return 0;
  384. }
  385. let update = 0;
  386. const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
  387. gamesList?.forEach(game => {
  388. const { eventId, matches, uptime, stage, retime, score, wm } = game;
  389. const relatedGame = relatedMap.get(eventId);
  390. if (relatedGame) {
  391. const events = {};
  392. matches.forEach(({ label, match }) => {
  393. match.forEach(({ key, value, origin, source }) => {
  394. events[key] = {
  395. v: value,
  396. r: origin,
  397. s: source
  398. };
  399. });
  400. });
  401. relatedGame.evtime = uptime;
  402. relatedGame.events = events;
  403. relatedGame.stage = stage;
  404. relatedGame.retime = retime;
  405. relatedGame.score = score;
  406. relatedGame.wm = wm;
  407. update ++;
  408. }
  409. });
  410. return update;
  411. }
  412. }
  413. const updateBaseEvents = ({ games, timestamp }) => {
  414. return new Promise((resolve, reject) => {
  415. if (!games) {
  416. return reject(new Error('GAMES_INVALID'));
  417. }
  418. if (!timestamp) {
  419. return reject(new Error('TIMESTAMP_INVALID'));
  420. }
  421. let update = 0;
  422. const { UpdateTimestamp } = GAMES;
  423. const tp = 'ps_9_9';
  424. if (!UpdateTimestamp[tp] || UpdateTimestamp[tp] < timestamp) {
  425. UpdateTimestamp[tp] = timestamp;
  426. }
  427. else {
  428. return resolve({ update });
  429. }
  430. Object.keys(games).forEach(mk => {
  431. update += syncBaseEvents({ mk, games: games[mk] ?? [] });
  432. });
  433. resolve({ update });
  434. });
  435. }
  436. const updateGamesEvents = ({ platform, mk, games, outrights, timestamp, tp }) => {
  437. return new Promise((resolve, reject) => {
  438. if (!platform || (!games && !outrights)) {
  439. return reject(new Error('PLATFORM_GAMES_INVALID'));
  440. }
  441. const { UpdateTimestamp } = GAMES;
  442. if (!UpdateTimestamp[tp] || UpdateTimestamp[tp] < timestamp) {
  443. // Logs.out('updateGamesEvents', { tp, timestamp });
  444. UpdateTimestamp[tp] = timestamp;
  445. }
  446. else {
  447. return resolve({ update: 0 });
  448. }
  449. if (platform == 'ps') {
  450. const update = syncBaseEvents({ mk, games, outrights });
  451. return resolve({ update });
  452. }
  453. const relatedGames = Object.values(GAMES.Relations).map(item => item.rel?.[platform] ?? {});
  454. if (!relatedGames.length) {
  455. return resolve({ update: 0 });
  456. }
  457. const updateCount = {
  458. update: 0
  459. };
  460. const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
  461. games?.forEach(game => {
  462. const { eventId, evtime, events, stage, retime, score, wm } = game;
  463. const relatedGame = relatedMap.get(eventId);
  464. if (relatedGame) {
  465. relatedGame.evtime = evtime;
  466. relatedGame.events = events;
  467. relatedGame.stage = stage;
  468. relatedGame.retime = retime;
  469. relatedGame.score = score;
  470. relatedGame.wm = wm;
  471. updateCount.update ++;
  472. }
  473. });
  474. outrights?.forEach(outright => {
  475. const { parentId, sptime, special } = outright;
  476. const relatedGame = relatedMap.get(parentId);
  477. if (relatedGame) {
  478. relatedGame.sptime = sptime;
  479. relatedGame.special = special;
  480. updateCount.update ++;
  481. }
  482. });
  483. resolve(updateCount);
  484. });
  485. }
  486. /**
  487. * 获取比赛盘口
  488. */
  489. const getGamesEvents = ({ platform, relIds = [] } = {}) => {
  490. if (!relIds.length) {
  491. return null;
  492. }
  493. const idSet = new Set(relIds);
  494. const relations = { ...GAMES.Relations };
  495. Object.keys(relations).forEach(id => {
  496. if (idSet.size && !idSet.has(+id)) {
  497. delete relations[id];
  498. }
  499. });
  500. if (platform) {
  501. return Object.values(relations).map(rel => rel[platform] ?? {});
  502. }
  503. const gamesEvents = {};
  504. Object.values(relations).forEach(({ rel }) => {
  505. Object.keys(rel).forEach(platform => {
  506. const game = rel[platform] ?? {};
  507. const { eventId, events, special } = game;
  508. if (!gamesEvents[platform]) {
  509. gamesEvents[platform] = {};
  510. }
  511. gamesEvents[platform][eventId] = { ...events, ...special };
  512. });
  513. });
  514. return gamesEvents;
  515. }
  516. /**
  517. * 获取关联比赛
  518. */
  519. const getDevGameTast = () => {
  520. return new Promise((resolve) => {
  521. const data = Cache.getData(DevGameTastFile, true);
  522. resolve({data});
  523. });
  524. }
  525. const fetchGamesRelation = async (mk='') => {
  526. const getGameTast = Promise.all([
  527. getDevGameTast(),
  528. axios.get(`${BASE_API_URL}/p/getGameTast?mk=${mk}`, { proxy: false })
  529. ]);
  530. return getGameTast.then(([res1, res2]) => {
  531. const resData = res1.data ?? res2.data;
  532. if (resData.code == 0) {
  533. const nowTime = Date.now();
  534. const gamesRelation = resData.data?.filter(item => {
  535. const timestamp = new Date(item.timestamp).getTime();
  536. if (nowTime > timestamp) {
  537. item.mk = 2;
  538. }
  539. item.timestamp = timestamp;
  540. const expireTime = timestamp + 1000*60*60*3;
  541. return expireTime > nowTime;
  542. }).map(item => {
  543. const {
  544. id, mk, league_name,
  545. event_id: ps_event_id,
  546. league_id: ps_league_id,
  547. team_home_name: ps_team_home_name,
  548. team_away_name: ps_team_away_name,
  549. ob_event_id, ob_league_id,
  550. ob_team_home_name,
  551. ob_team_away_name,
  552. hg_event_id, hg_league_id,
  553. hg_team_home_name,
  554. hg_team_away_name,
  555. timestamp,
  556. } = item;
  557. const rel = {
  558. ps: {
  559. eventId: +ps_event_id,
  560. leagueId: +ps_league_id,
  561. leagueName: league_name,
  562. teamHomeName: ps_team_home_name,
  563. teamAwayName: ps_team_away_name,
  564. timestamp
  565. },
  566. ob: ob_event_id ? {
  567. eventId: +ob_event_id,
  568. leagueId: +ob_league_id,
  569. leagueName: league_name,
  570. teamHomeName: ob_team_home_name,
  571. teamAwayName: ob_team_away_name,
  572. timestamp
  573. } : null,
  574. hg: hg_event_id ? {
  575. eventId: +hg_event_id,
  576. leagueId: +hg_league_id,
  577. leagueName: league_name,
  578. teamHomeName: hg_team_home_name,
  579. teamAwayName: hg_team_away_name,
  580. timestamp
  581. } : null
  582. };
  583. return { id: ps_event_id, mk, rel, timestamp };
  584. }) ?? [];
  585. return gamesRelation;
  586. }
  587. return Promise.reject(new Error(resData.message));
  588. });
  589. }
  590. const getGamesRelation = ({ mk=-1, ids, listEvents } = {}) => {
  591. let idsSet = null;
  592. if (ids?.length) {
  593. idsSet = new Set(ids);
  594. }
  595. const relations = Object.values(GAMES.Relations).filter(item => {
  596. if (idsSet && !idsSet.has(item.id)) {
  597. return false;
  598. }
  599. return mk == -1 || item.mk == mk;
  600. }).sort((a, b) => a.timestamp - b.timestamp);
  601. if (listEvents) {
  602. return relations;
  603. }
  604. const gamesRelation = relations.map(item => {
  605. const { rel, ...relationInfo } = item;
  606. const tempRel = { ...rel };
  607. Object.keys(tempRel).forEach(platform => {
  608. const { events, evtime, sptime, special, ...gameInfo } = tempRel[platform];
  609. tempRel[platform] = gameInfo;
  610. });
  611. return { ...relationInfo, rel: tempRel };
  612. });
  613. return gamesRelation;
  614. }
  615. /**
  616. * 定时更新关联比赛列表
  617. */
  618. const updateGamesRelation = () => {
  619. fetchGamesRelation()
  620. .then(gamesRelation => {
  621. const baseList = {};
  622. gamesRelation.map(item => {
  623. const baseGame = item.rel?.['ps'] ?? {};
  624. return { ...baseGame, mk: item.mk };
  625. }).forEach(item => {
  626. const marketType = getMarketType(item.mk);
  627. if (!baseList[marketType]) {
  628. baseList[marketType] = [];
  629. }
  630. baseList[marketType].push(item);
  631. });
  632. Object.keys(baseList).forEach(marketType => {
  633. syncBaseList({ marketType, games: baseList[marketType] });
  634. });
  635. const updateCount = {
  636. add: 0,
  637. update: 0,
  638. delete: 0
  639. };
  640. gamesRelation.forEach(item => {
  641. const { id, mk } = item;
  642. const oldItem = GAMES.Relations[id];
  643. if (!oldItem) {
  644. GAMES.Relations[id] = item;
  645. updateCount.add ++;
  646. }
  647. else if (oldItem.mk != mk) {
  648. GAMES.Relations[id] = item;
  649. updateCount.update ++;
  650. }
  651. });
  652. const relations = new Set(gamesRelation.map(item => +item.id));
  653. Object.keys(GAMES.Relations).forEach(id => {
  654. if (!relations.has(+id)) {
  655. delete GAMES.Relations[id];
  656. updateCount.delete ++;
  657. }
  658. });
  659. if (updateCount.add || updateCount.update || updateCount.delete) {
  660. Logs.out('updateGamesRelation', updateCount);
  661. }
  662. else {
  663. Logs.outDev('updateGamesRelation', updateCount);
  664. }
  665. })
  666. .catch(err => {
  667. Logs.out('updateGamesRelation', err.message);
  668. })
  669. .finally(() => {
  670. setTimeout(updateGamesRelation, 60000);
  671. });
  672. }
  673. updateGamesRelation();
  674. const gamesRelationCleanup = () => {
  675. const relations = Object.values(GAMES.Relations);
  676. const expireTime = Date.now() - 1000*60;
  677. relations.forEach(item => {
  678. const { rel } = item;
  679. Object.keys(rel).forEach(platform => {
  680. const { evtime, sptime } = rel[platform];
  681. if (evtime && evtime < expireTime) {
  682. delete rel[platform].events;
  683. delete rel[platform].evtime;
  684. }
  685. if (sptime && sptime < expireTime) {
  686. delete rel[platform].special;
  687. delete rel[platform].sptime;
  688. }
  689. });
  690. });
  691. }
  692. /**
  693. * 同步比赛结果
  694. */
  695. const syncGamesResult = async (result) => {
  696. if (IS_DEV) {
  697. return Logs.out('updateGamesResult', result);
  698. }
  699. // axios.post(`${BASE_API_URL}/p/syncMatchResult`, result, { proxy: false })
  700. // .then(res => {
  701. // // Logs.out('syncMatchResult', res.data);
  702. // })
  703. // .catch(err => {
  704. // Logs.out('syncMatchResult', err.message);
  705. // });
  706. }
  707. /**
  708. * 更新比赛结果
  709. */
  710. const updateGamesResult = (result) => {
  711. syncGamesResult(result);
  712. return Promise.resolve();
  713. }
  714. /**
  715. * 同步中单方案
  716. */
  717. // const syncSolutions = (solutions) => {
  718. // if (IS_DEV) {
  719. // return Logs.out('syncSolutions', solutions);
  720. // }
  721. // axios.post(`${BASE_API_URL}/p/syncDsOpportunity`, solutions, { proxy: false })
  722. // .then(res => {
  723. // // Logs.out('syncSolutions', res.data);
  724. // })
  725. // .catch(err => {
  726. // Logs.out('syncSolutions', err.message);
  727. // });
  728. // }
  729. /**
  730. * 更新中单方案
  731. */
  732. const getCprKey = (cpr) => {
  733. const { k, p, v } = cpr;
  734. return `${k}_${p}_${v}`;
  735. }
  736. const compareCpr = (cpr1, cpr2) => {
  737. const key1 = getCprKey(cpr1);
  738. const key2 = getCprKey(cpr2);
  739. return key1 === key2;
  740. }
  741. const updateSolutions = (solutions, eventsLogsMap) => {
  742. if (solutions?.length) {
  743. const solutionsHistory = GAMES.Solutions;
  744. const updateIds = { add: [], update: [], retain: [], remove: [] }
  745. solutions.forEach(item => {
  746. const { sid, cpr, sol: { win_average, win_profit_rate } } = item;
  747. if (!solutionsHistory[sid]) {
  748. solutionsHistory[sid] = item;
  749. updateIds.add.push(sid);
  750. return;
  751. }
  752. const historySolution = solutionsHistory[sid];
  753. if (historySolution.sol.win_average !== win_average ||
  754. historySolution.sol.win_profit_rate !== win_profit_rate ||
  755. !compareCpr(historySolution.cpr, cpr)) {
  756. solutionsHistory[sid] = item;
  757. updateIds.update.push(sid);
  758. return;
  759. }
  760. const { timestamp } = item;
  761. historySolution.timestamp = timestamp;
  762. updateIds.retain.push(sid);
  763. });
  764. const solutionsMap = new Map(solutions.map(item => [item.sid, item]));
  765. Object.keys(solutionsHistory).forEach(sid => {
  766. if (!solutionsMap.has(sid)) {
  767. delete solutionsHistory[sid];
  768. updateIds.remove.push(sid);
  769. }
  770. });
  771. const solutionUpdate = {};
  772. Object.keys(updateIds).forEach(key => {
  773. if (key == 'retain' || key == 'remove') {
  774. solutionUpdate[key] = updateIds[key];
  775. }
  776. else {
  777. solutionUpdate[key] = updateIds[key].map(sid => solutionsHistory[sid]);
  778. }
  779. });
  780. // syncSolutions(solutionUpdate);
  781. if (updateIds.add.length / solutions.length > 0.2 ||
  782. updateIds.remove.length / solutions.length > 0.2
  783. ) {
  784. const { expireEvents, removeEvents } = eventsLogsMap;
  785. const expireEvemtsMap = {};
  786. expireEvents.forEach(item => {
  787. const { mk, platform, info, evExpire, spExpire, evtime, sptime } = item;
  788. if (!expireEvemtsMap[mk]) {
  789. expireEvemtsMap[mk] = {};
  790. }
  791. if (!expireEvemtsMap[mk][platform]) {
  792. expireEvemtsMap[mk][platform] = {};
  793. }
  794. if (!expireEvemtsMap[mk][platform].list) {
  795. expireEvemtsMap[mk][platform].list = [];
  796. }
  797. if (!expireEvemtsMap[mk][platform].evtime) {
  798. expireEvemtsMap[mk][platform].evtime = evtime;
  799. }
  800. if (!expireEvemtsMap[mk][platform].sptime) {
  801. expireEvemtsMap[mk][platform].sptime = sptime;
  802. }
  803. expireEvemtsMap[mk][platform].list.push({ info, evExpire, spExpire, evtime, sptime });
  804. });
  805. Object.keys(expireEvemtsMap).forEach(mk => {
  806. Object.keys(expireEvemtsMap[mk]).forEach(platform => {
  807. Logs.out('invalid events, mk %d, platform %s, expire %d, evtime %d, sptime %d',
  808. mk, platform,
  809. expireEvemtsMap[mk][platform].list.length,
  810. expireEvemtsMap[mk][platform].evtime,
  811. expireEvemtsMap[mk][platform].sptime,
  812. )
  813. });
  814. });
  815. Logs.out('solutions add %d, update %d, retain %d, remove %d',
  816. updateIds.add.length, updateIds.update.length, updateIds.retain.length, updateIds.remove.length);
  817. }
  818. else {
  819. Logs.outDev('solutions update complete', updateIds);
  820. }
  821. }
  822. }
  823. /**
  824. * 获取中单方案
  825. */
  826. const getSolutions = async ({ win_min, with_events, mk=-1 }) => {
  827. // Logs.out('getSolutions', win_min);
  828. const filterMarketType = +mk;
  829. const { minShowAmount } = getSetting();
  830. const solutionsList = Object.values(GAMES.Solutions);
  831. const gamesRelation = getGamesRelation();
  832. const relationsMap = new Map(gamesRelation.map(item => [item.id, item]));
  833. const mkCount = {
  834. all: 0,
  835. rollball: 0,
  836. today: 0,
  837. early: 0,
  838. }
  839. let solutions = solutionsList.filter(item => {
  840. const { sol: { win_average } } = item;
  841. return win_average >= (win_min ?? minShowAmount);
  842. })
  843. .map(item => {
  844. const { info: { id } } = item;
  845. const { mk, rel } = relationsMap.get(id);
  846. const marketType = getMarketType(mk);
  847. mkCount.all ++;
  848. mkCount[marketType] ++;
  849. return {
  850. ...item,
  851. info: { id, mk, ...rel }
  852. }
  853. });
  854. if (mk >= 0) {
  855. solutions = solutions.filter(item => {
  856. const { info: { mk } } = item;
  857. return mk == filterMarketType;
  858. });
  859. }
  860. solutions = solutions.sort((a, b) => b.sol.win_average - a.sol.win_average);
  861. const relIds = solutions.map(item => item.info.id);
  862. let gamesEvents;
  863. if (with_events) {
  864. gamesEvents = getGamesEvents({ relIds });
  865. }
  866. return { solutions, gamesEvents, mkCount };
  867. }
  868. /**
  869. * 获取中单方案并按照比赛分组
  870. */
  871. const getGamesSolutions = async ({ win_min, with_events, mk=-1, tp=0, sk }) => {
  872. const filterMarketType = +mk;
  873. const filterDataType = +tp;
  874. const { minShowAmount } = getSetting();
  875. const solutionsList = Object.values(GAMES.Solutions);
  876. const gamesRelation = getGamesRelation({ listEvents: with_events });
  877. const relationsMap = new Map(gamesRelation.map(item => [item.id, item]));
  878. const mkCount = {
  879. all: 0,
  880. rollball: 0,
  881. today: 0,
  882. early: 0,
  883. }
  884. const solutionsMap = {};
  885. solutionsList.forEach(item => {
  886. const { info: { id }, ...solution } = item;
  887. const { rule, sol: { win_average } } = solution;
  888. const ruleType = getRuleType(rule);
  889. if ((filterDataType == 0 || filterDataType == ruleType) && (!!sk || win_average >= (win_min ?? minShowAmount))) {
  890. const gameRelation = relationsMap.get(id);
  891. if (!solutionsMap[id]) {
  892. solutionsMap[id] = { ...gameRelation, solutions: [] };
  893. }
  894. solutionsMap[id].solutions.push(solution);
  895. }
  896. })
  897. const gamesSolutions = Object.values(solutionsMap)
  898. .filter(item => {
  899. const { mk, rel, solutions } = item;
  900. const marketType = getMarketType(mk);
  901. if (!sk || matchGame(rel, sk)) {
  902. mkCount.all ++;
  903. mkCount[marketType] ++;
  904. solutions.sort((a, b) => b.sol.win_average - a.sol.win_average);
  905. return filterMarketType == -1 || filterMarketType == mk;
  906. }
  907. return false;
  908. })
  909. .sort((a, b) => b.solutions[0].sol.win_average - a.solutions[0].sol.win_average);
  910. return { gamesSolutions, mkCount };
  911. }
  912. /**
  913. * 获取单个中单方案
  914. */
  915. const getSolution = async (sid) => {
  916. if (!sid) {
  917. return Promise.reject(new Error('sid is required'));
  918. }
  919. const solution = GAMES.Solutions[sid];
  920. if (!solution) {
  921. return Promise.reject(new Error('solution not found'));
  922. }
  923. return solution;
  924. }
  925. /**
  926. * 通过比赛 ID 获取中单方案
  927. */
  928. const getSolutionsByIds = async (ids) => {
  929. const baseList = Object.values(GAMES.Baselist).flat();
  930. const baseMap = new Map(baseList.map(item => [item.eventId, item]));
  931. const result = {};
  932. ids.forEach(id => {
  933. const baseGame = baseMap.get(id);
  934. result[id] = {};
  935. result[id].matches = baseGame?.matches ?? [];
  936. result[id].sols = [];
  937. });
  938. Object.values(GAMES.Solutions).forEach(item => {
  939. const { info: { id } } = item;
  940. if (result[id]) {
  941. result[id].sols.push(item);
  942. }
  943. });
  944. return result;
  945. }
  946. /**
  947. * 清理中单方案
  948. */
  949. const solutionsCleanup = () => {
  950. const solutionsHistory = GAMES.Solutions;
  951. const updateIds = { remove: [] }
  952. Object.keys(solutionsHistory).forEach(sid => {
  953. const { timestamp } = solutionsHistory[sid];
  954. const nowTime = Date.now();
  955. if (nowTime - timestamp > 1000*60) {
  956. delete solutionsHistory[sid];
  957. updateIds.remove.push(sid);
  958. return;
  959. }
  960. const solution = solutionsHistory[sid];
  961. const eventTime = solution.info.timestamp;
  962. if (nowTime > eventTime) {
  963. delete solutionsHistory[sid];
  964. updateIds.remove.push(sid);
  965. }
  966. });
  967. // if (updateIds.remove.length) {
  968. // syncSolutions(updateIds);
  969. // }
  970. }
  971. /**
  972. * 清理更新时间戳
  973. */
  974. const cleanupUpdateTimestamp = () => {
  975. const updateTimestamp = GAMES.UpdateTimestamp;
  976. const nowTime = Date.now();
  977. const expireTime = nowTime - 1000*60;
  978. Object.keys(updateTimestamp).forEach(key => {
  979. if (updateTimestamp[key] < expireTime) {
  980. delete updateTimestamp[key];
  981. }
  982. });
  983. }
  984. /**
  985. * 定时清理中单方案
  986. * 定时清理盘口信息
  987. */
  988. setInterval(() => {
  989. // cleanupBaseList();
  990. solutionsCleanup();
  991. gamesRelationCleanup();
  992. cleanupUpdateTimestamp();
  993. }, 1000*30);
  994. /**
  995. * 获取综合利润
  996. */
  997. const getTotalProfit = async (sol1, sol2, inner_base, inner_rebate) => {
  998. const { innerDefaultAmount, innerRebateRatio } = getSetting();
  999. inner_base = inner_base ? +inner_base : innerDefaultAmount;
  1000. inner_rebate = inner_rebate ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);
  1001. const profit = calcTotalProfit(sol1, sol2, inner_base, inner_rebate);
  1002. return profit;
  1003. }
  1004. /**
  1005. * 通过 sid 获取综合利润
  1006. */
  1007. const getTotalProfitWithSid = async (sid1, sid2, inner_base, inner_rebate) => {
  1008. const preSolution = GAMES.Solutions[sid1];
  1009. const subSolution = GAMES.Solutions[sid2];
  1010. const sol1 = preSolution?.sol;
  1011. const sol2 = subSolution?.sol;
  1012. if (!sol1) {
  1013. return Promise.reject(new Error('sid1 已失效'));
  1014. }
  1015. if (!sol2) {
  1016. return Promise.reject(new Error('sid2 已失效'));
  1017. }
  1018. const profit = await getTotalProfit(sol1, sol2, inner_base, inner_rebate);
  1019. return { profit, solutions: [preSolution, subSolution] };
  1020. }
  1021. /**
  1022. * 通过盘口信息获取综合利润
  1023. */
  1024. const getTotalProfitWithBetInfo = async (betInfo1, betInfo2, fixed=false, inner_base, inner_rebate) => {
  1025. if (!betInfo1?.cross_type) {
  1026. return Promise.reject(new Error('第一个下注信息无效'));
  1027. }
  1028. if (!betInfo2?.cross_type) {
  1029. return Promise.reject(new Error('第二个下注信息无效'));
  1030. }
  1031. const { innerDefaultAmount, innerRebateRatio } = getSetting();
  1032. inner_base = inner_base ? +inner_base : innerDefaultAmount;
  1033. inner_rebate = inner_rebate ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);
  1034. if (fixed) {
  1035. return calcTotalProfitWithFixedFirst(betInfo1, betInfo2, inner_base, inner_rebate);
  1036. }
  1037. const [sol1, sol2] = [betInfo1, betInfo2].map(betinfo => eventSolutions({...betinfo, inner_base, inner_rebate }));
  1038. return getTotalProfit(sol1, sol2, inner_base, inner_rebate);
  1039. }
  1040. /**
  1041. * 同步Qboss平台配置
  1042. */
  1043. const syncQbossConfig = () => {
  1044. const setting = getSetting();
  1045. if (!setting.syncSettingEnabled) {
  1046. // Logs.outDev('syncQbossConfig disabled');
  1047. return setTimeout(syncQbossConfig, 1000*5);
  1048. }
  1049. axios.get(`${BASE_API_URL}/p/QbossSystemConfig`, { proxy: false })
  1050. .then(res => {
  1051. const { data } = res;
  1052. if (!data?.data) {
  1053. throw new Error('syncQbossConfig data is empty');
  1054. }
  1055. const {
  1056. qboss_return_ratio,
  1057. ob_return_ratio, ob_return_type,
  1058. hg_return_ratio, hg_return_type,
  1059. qboss_jq_add_odds, qboss_jq_add_hours,
  1060. qboss_gq_add_dy_odds, qboss_gq_add_jq_odds,
  1061. } = data.data;
  1062. const qbossSetting = {
  1063. innerRebateRatio: +qboss_return_ratio,
  1064. obRebateRatio: +ob_return_ratio,
  1065. obRebateType: +ob_return_type,
  1066. hgRebateRatio: +hg_return_ratio,
  1067. hgRebateType: +hg_return_type,
  1068. subsidyTime: +qboss_jq_add_hours,
  1069. subsidyAmount: +qboss_jq_add_odds,
  1070. subsidyRbWmAmount: +qboss_gq_add_dy_odds,
  1071. subsidyRbOtAmount: +qboss_gq_add_jq_odds,
  1072. };
  1073. const settingFields = {};
  1074. let needUpdate = false;
  1075. Object.keys(qbossSetting).forEach(key => {
  1076. if (qbossSetting[key] !== setting[key]) {
  1077. settingFields[key] = qbossSetting[key];
  1078. needUpdate = true;
  1079. }
  1080. });
  1081. if (needUpdate) {
  1082. Setting.update(settingFields);
  1083. // Logs.outDev('syncQbossConfig', settingFields);
  1084. }
  1085. // else {
  1086. // Logs.outDev('syncQbossConfig no change');
  1087. // }
  1088. })
  1089. .catch(err => {
  1090. Logs.out('syncQbossConfig error', err.message);
  1091. })
  1092. .finally(() => {
  1093. setTimeout(syncQbossConfig, 1000*15);
  1094. });
  1095. }
  1096. syncQbossConfig();
  1097. /**
  1098. * 异常通知
  1099. */
  1100. const notifyException = (message) => {
  1101. if (IS_DEV) {
  1102. return Logs.out('notifyException', { message });
  1103. }
  1104. const chat_id = -1003032820471;
  1105. axios.get(`${BASE_API_URL}/telegram/jump`, { params: { chat_id, message } }, { proxy: false })
  1106. .then(() => {
  1107. Logs.out('notifyException', '通知成功');
  1108. })
  1109. .catch(err => {
  1110. Logs.out('notifyException', err.message);
  1111. });
  1112. }
  1113. /**
  1114. * 从子进程获取数据
  1115. */
  1116. const getDataFromChild = (type, callback) => {
  1117. const id = ++Request.count;
  1118. Request.callbacks[id] = callback;
  1119. events_child.send({ method: 'get', id, type });
  1120. }
  1121. /**
  1122. * 向子进程发送数据
  1123. */
  1124. const postDataToChild = (type, data) => {
  1125. events_child.send({ method: 'post', type, data });
  1126. }
  1127. /**
  1128. * 处理子进程消息
  1129. */
  1130. events_child.on('message', async (message) => {
  1131. const { callbacks } = Request;
  1132. const { method, id, type, data } = message;
  1133. if (method == 'get' && id) {
  1134. let responseData = null;
  1135. if (type == 'getGamesRelation') {
  1136. responseData = getGamesRelation({ listEvents: true });
  1137. }
  1138. else if (type == 'getSetting') {
  1139. responseData = getSetting();
  1140. }
  1141. // else if (type == 'getSolutionHistory') {
  1142. // responseData = getSolutionHistory();
  1143. // }
  1144. events_child.send({ type: 'response', id, data: responseData });
  1145. }
  1146. else if (method == 'post') {
  1147. if (type == 'updateSolutions') {
  1148. updateSolutions(data.solutions, data.eventsLogsMap);
  1149. }
  1150. }
  1151. else if (method == 'response' && id && callbacks[id]) {
  1152. callbacks[id](data);
  1153. delete callbacks[id];
  1154. }
  1155. });
  1156. events_child.stderr?.on('data', data => {
  1157. Logs.out('events_child stderr', data.toString());
  1158. });
  1159. const settingUpdate = (fields) => {
  1160. updateSetting(fields);
  1161. postDataToChild('updateSetting', fields);
  1162. }
  1163. const syncSetting = async () => {
  1164. const setting = await Setting.get();
  1165. settingUpdate(setting.toObject());
  1166. }
  1167. syncSetting();
  1168. Setting.onUpdate(settingUpdate);
  1169. /**
  1170. * 保存GAMES数据到缓存文件
  1171. */
  1172. const saveGamesToCache = () => {
  1173. Cache.setData(GamesCacheFile, GAMES, err => {
  1174. if (err) {
  1175. Logs.out('Failed to save games cache:', err.message);
  1176. }
  1177. else {
  1178. Logs.out('Games cache saved successfully');
  1179. }
  1180. });
  1181. }
  1182. /**
  1183. * 从缓存文件加载GAMES数据
  1184. */
  1185. const loadGamesFromCache = () => {
  1186. const gamesCacheData = Cache.getData(GamesCacheFile, true) ?? {};
  1187. Object.keys(GAMES).forEach(key => {
  1188. if (key in gamesCacheData) {
  1189. GAMES[key] = gamesCacheData[key];
  1190. }
  1191. });
  1192. Logs.out('Games cache loaded successfully');
  1193. }
  1194. // 在模块加载时尝试从缓存恢复数据
  1195. loadGamesFromCache();
  1196. // 监听进程退出事件,保存GAMES数据
  1197. process.on('exit', saveGamesToCache);
  1198. process.on('SIGINT', () => {
  1199. process.exit(0);
  1200. });
  1201. process.on('SIGTERM', () => {
  1202. process.exit(0);
  1203. });
  1204. process.on('SIGUSR2', () => {
  1205. process.exit(0);
  1206. });
  1207. module.exports = {
  1208. updateLeaguesList, getFilteredLeagues,
  1209. updateGamesList, updateGamesEvents, updateBaseEvents,
  1210. getGamesRelation,
  1211. updateGamesResult,
  1212. updateOriginalData, getOriginalData,
  1213. getSolutions, getGamesSolutions, getSolution, getSolutionsByIds,
  1214. getTotalProfitWithSid,
  1215. getTotalProfitWithBetInfo,
  1216. notifyException,
  1217. }