GamesPs.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. const axios = require('axios');
  2. const Logs = require('../libs/logs');
  3. const Setting = require('./Setting');
  4. const childOptions = process.env.NODE_ENV == 'development' ? {
  5. execArgv: ['--inspect=9228']
  6. } : {};
  7. const { fork } = require('child_process');
  8. const events_child = fork('./triangle/eventsMatch.js', [], childOptions);
  9. const PS_IOR_KEYS = [
  10. ['0', 'ior_mh', 'ior_mn', 'ior_mc'],
  11. // ['0', 'ior_rh_05', 'ior_mn', 'ior_rc_05'],
  12. ['-1', 'ior_rh_15', 'ior_wmh_1', 'ior_rac_05'],
  13. ['-2', 'ior_rh_25', 'ior_wmh_2', 'ior_rac_15'],
  14. ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
  15. ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
  16. ];
  17. const BASE_URL = 'https://api.isthe.me/api/p';
  18. const IS_DEV = process.env.NODE_ENV == 'development';
  19. const GAMES = {
  20. Leagues: {},
  21. List: {},
  22. Baselist: {},
  23. Relations: {},
  24. };
  25. const Request = {
  26. callbacks: {},
  27. count: 0,
  28. }
  29. /**
  30. * 精确浮点数字
  31. * @param {number} number
  32. * @param {number} x
  33. * @returns {number}
  34. */
  35. const fixFloat = (number, x=2) => {
  36. return parseFloat(number.toFixed(x));
  37. }
  38. /**
  39. * 获取市场类型
  40. */
  41. const getMarketType = (mk) => {
  42. return mk == 0 ? 'early' : 'today';
  43. }
  44. /**
  45. * 同步联赛列表
  46. */
  47. const syncLeaguesList = ({ mk, leagues }) => {
  48. if (IS_DEV) {
  49. return Logs.out('syncLeaguesList', { mk, leagues });
  50. }
  51. axios.post(`${BASE_URL}/syncLeague`, { mk, leagues })
  52. .then(res => {
  53. Logs.out('syncLeaguesList', res.data);
  54. })
  55. .catch(err => {
  56. Logs.out('syncLeaguesList', err.message);
  57. });
  58. }
  59. /**
  60. * 更新联赛列表
  61. */
  62. const updateLeaguesList = ({ mk, leagues }) => {
  63. const leaguesList = GAMES.Leagues;
  64. if (JSON.stringify(leaguesList[mk]) != JSON.stringify(leagues)) {
  65. leaguesList[mk] = leagues;
  66. syncLeaguesList({ mk, leagues });
  67. return leagues.length;
  68. }
  69. return 0;
  70. }
  71. /**
  72. * 获取筛选过的联赛
  73. */
  74. const getFilteredLeagues = async (mk) => {
  75. return axios.get(`${BASE_URL}/getLeagueTast?mk=${mk}`)
  76. .then(res => {
  77. if (res.data.code == 0) {
  78. return res.data.data;
  79. }
  80. return Promise.reject(new Error(res.data.message));
  81. });
  82. }
  83. /**
  84. * 同步比赛列表到服务器
  85. */
  86. const syncGamesList = ({ platform, mk, games }) => {
  87. if (IS_DEV) {
  88. return Logs.out('syncGamesList', { platform, mk, games });
  89. }
  90. axios.post(`${BASE_URL}/syncGames`, { platform, mk, games })
  91. .then(res => {
  92. Logs.out('syncGamesList', { platform, mk, count: games.length }, res.data);
  93. })
  94. .catch(err => {
  95. Logs.out('syncGamesList', { platform, mk }, err.message);
  96. });
  97. }
  98. /**
  99. * 同步基准比赛列表
  100. */
  101. const syncBaseList = ({ marketType, games }) => {
  102. const baseList = GAMES.Baselist;
  103. if (!baseList[marketType]) {
  104. baseList[marketType] = games;
  105. }
  106. const newMap = new Map(games.map(item => [item.eventId, item]));
  107. // 删除不存在的项
  108. for (let i = baseList[marketType].length - 1; i >= 0; i--) {
  109. if (!newMap.has(baseList[marketType][i].eventId)) {
  110. baseList[marketType].splice(i, 1);
  111. }
  112. }
  113. // 添加或更新
  114. const oldIds = new Set(baseList[marketType].map(item => item.eventId));
  115. games.forEach(game => {
  116. if (!oldIds.has(game.eventId)) {
  117. // 添加新项
  118. baseList[marketType].push(game);
  119. }
  120. });
  121. }
  122. /**
  123. * 更新比赛列表
  124. */
  125. const updateGamesList = (({ platform, mk, games } = {}) => {
  126. return new Promise((resolve, reject) => {
  127. if (!platform || !games) {
  128. return reject(new Error('PLATFORM_GAMES_INVALID'));
  129. }
  130. const marketType = getMarketType(mk);
  131. syncGamesList({ platform, mk, games });
  132. if (platform == 'ps') {
  133. syncBaseList({ marketType, games });
  134. }
  135. resolve();
  136. });
  137. });
  138. /**
  139. * 提交盘口数据
  140. */
  141. const submitOdds = ({ platform, mk, games }) => {
  142. if (IS_DEV) {
  143. return Logs.out('syncOdds', { platform, mk, games });
  144. }
  145. axios.post(`${BASE_URL}/syncOdds`, { platform, mk, games})
  146. .then(res => {
  147. Logs.out('syncOdds', { platform, mk, count: games.length }, res.data);
  148. })
  149. .catch(err => {
  150. Logs.out('syncOdds', { platform, mk }, err.message);
  151. });
  152. }
  153. /**
  154. * 同步基准盘口
  155. */
  156. const syncBaseEvents = ({ mk, games, outrights }) => {
  157. const marketType = getMarketType(mk);
  158. const baseList = GAMES.Baselist;
  159. if (!baseList[marketType]) {
  160. return;
  161. }
  162. const baseMap = new Map(baseList[marketType].map(item => [item.eventId, item]));
  163. games?.forEach(game => {
  164. const { eventId, evtime, events } = game;
  165. const baseGame = baseMap.get(eventId);
  166. if (baseGame) {
  167. baseGame.evtime = evtime;
  168. baseGame.events = events;
  169. }
  170. });
  171. outrights?.forEach(outright => {
  172. const { parentId, sptime, special } = outright;
  173. const baseGame = baseMap.get(parentId);
  174. if (baseGame) {
  175. baseGame.sptime = sptime;
  176. baseGame.special = special;
  177. }
  178. });
  179. if (games?.length) {
  180. const gamesList = baseList[marketType]?.map(game => {
  181. const { evtime, events, sptime, special, ...gameInfo } = game;
  182. const expireTime = Date.now() - 15000;
  183. let odds = {};
  184. if (evtime > expireTime) {
  185. odds = { ...odds, ...events };
  186. }
  187. if (sptime > expireTime) {
  188. odds = { ...odds, ...special };
  189. }
  190. const matches = PS_IOR_KEYS.map(([label, ...keys]) => {
  191. const match = keys.map(key => ({
  192. key,
  193. value: odds[key] ?? 0
  194. }));
  195. return {
  196. label,
  197. match
  198. };
  199. }).filter(item => item.match.every(entry => entry.value !== 0));
  200. return { ...gameInfo, matches };
  201. });
  202. // Logs.out('baseList', baseList[marketType]);
  203. submitOdds({ platform: 'ps', mk, games: gamesList });
  204. }
  205. }
  206. /**
  207. * 更新比赛盘口
  208. */
  209. const updateGamesEvents = ({ platform, mk, games, outrights }) => {
  210. return new Promise((resolve, reject) => {
  211. if (!platform || (!games && !outrights)) {
  212. return reject(new Error('PLATFORM_GAMES_INVALID'));
  213. }
  214. if (platform == 'ps') {
  215. syncBaseEvents({ mk, games, outrights });
  216. }
  217. const relatedGames = Object.values(GAMES.Relations).map(item => item.rel?.[platform] ?? {});
  218. if (!relatedGames.length) {
  219. return resolve({ update: 0 });
  220. }
  221. const updateCount = {
  222. update: 0
  223. };
  224. const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
  225. games?.forEach(game => {
  226. const { eventId, evtime, events } = game;
  227. const relatedGame = relatedMap.get(eventId);
  228. if (relatedGame) {
  229. relatedGame.evtime = evtime;
  230. relatedGame.events = events;
  231. updateCount.update ++;
  232. }
  233. });
  234. outrights?.forEach(outright => {
  235. const { parentId, sptime, special } = outright;
  236. const relatedGame = relatedMap.get(parentId);
  237. if (relatedGame) {
  238. relatedGame.sptime = sptime;
  239. relatedGame.special = special;
  240. updateCount.update ++;
  241. }
  242. });
  243. resolve(updateCount);
  244. });
  245. }
  246. /**
  247. * 获取关联比赛
  248. */
  249. const fetchGamesRelation = async (mk='') => {
  250. return axios.get(`${BASE_URL}/getGameTast?mk=${mk}`)
  251. .then(res => {
  252. if (res.data.code == 0) {
  253. const now = Date.now();
  254. const gamesRelation = res.data.data?.filter(item => {
  255. const timestamp = new Date(item.timestamp).getTime();
  256. return timestamp > now;
  257. }).map(item => {
  258. const {
  259. id, mk,
  260. event_id: ps_event_id,
  261. league_id: ps_league_id,
  262. ob_event_id, ob_league_id,
  263. hg_event_id, hg_league_id,
  264. } = item;
  265. const rel = {
  266. ps: {
  267. eventId: +ps_event_id,
  268. leagueId: +ps_league_id
  269. },
  270. ob: ob_event_id ? {
  271. eventId: +ob_event_id,
  272. leagueId: +ob_league_id
  273. } : null,
  274. hg: hg_event_id ? {
  275. eventId: +hg_event_id,
  276. leagueId: +hg_league_id
  277. } : null
  278. };
  279. return { id, mk, rel };
  280. }) ?? [];
  281. return gamesRelation;
  282. }
  283. return Promise.reject(new Error(res.data.message));
  284. });
  285. }
  286. const getGamesRelation = ({ mk, listEvents }) => {
  287. const relations = Object.values(GAMES.Relations).filter(item => {
  288. if (!mk) {
  289. return true;
  290. }
  291. return item.mk == mk;
  292. });
  293. if (listEvents) {
  294. return relations;
  295. }
  296. return relations.map(item => {
  297. const { rel, ...relationInfo } = item;
  298. Object.keys(rel).forEach(platform => {
  299. const { events, evtime, sptime, special, ...gameInfo } = rel[platform];
  300. rel[platform] = gameInfo;
  301. });
  302. return { ...relationInfo, rel };
  303. });
  304. }
  305. /**
  306. * 定时更新关联比赛列表
  307. */
  308. const updateGamesRelation = () => {
  309. fetchGamesRelation()
  310. .then(res => {
  311. const gamesRelation = res.flat();
  312. gamesRelation.forEach(item => {
  313. const { id } = item;
  314. if (!GAMES.Relations[id]) {
  315. GAMES.Relations[id] = item;
  316. }
  317. });
  318. const relations = new Set(gamesRelation.map(item => +item.id));
  319. Object.keys(GAMES.Relations).forEach(id => {
  320. if (!relations.has(+id)) {
  321. delete GAMES.Relations[id];
  322. }
  323. });
  324. })
  325. .catch(err => {
  326. Logs.out('updateGamesRelation', err.message);
  327. })
  328. .finally(() => {
  329. setTimeout(updateGamesRelation, 60000);
  330. });
  331. }
  332. updateGamesRelation();
  333. /**
  334. * 同步比赛结果
  335. */
  336. const syncGamesResult = async (result) => {
  337. if (IS_DEV) {
  338. return Logs.out('updateGamesResult', result);
  339. }
  340. axios.post(`${BASE_URL}/syncMatchResult`, result)
  341. .then(res => {
  342. Logs.out('syncMatchResult', res.data);
  343. })
  344. .catch(err => {
  345. Logs.out('syncMatchResult', err.message);
  346. });
  347. }
  348. /**
  349. * 更新比赛结果
  350. */
  351. const updateGamesResult = (result) => {
  352. syncGamesResult(result);
  353. return Promise.resolve();
  354. }
  355. /**
  356. * 获取后台设置
  357. */
  358. const getSetting = async () => {
  359. return Setting.get();
  360. }
  361. /**
  362. * 从子进程获取数据
  363. */
  364. const getDataFromChild = (type, callback) => {
  365. const id = ++Request.count;
  366. Request.callbacks[id] = callback;
  367. events_child.send({ method: 'get', id, type });
  368. }
  369. /**
  370. * 处理子进程消息
  371. */
  372. events_child.on('message', async (message) => {
  373. const { callbacks } = Request;
  374. const { method, id, type, data } = message;
  375. if (method == 'get' && id) {
  376. let responseData = null;
  377. if (type == 'getGamesRelation') {
  378. responseData = getGamesRelation({ listEvents: true });
  379. Logs.out('getGamesRelation', responseData);
  380. }
  381. else if (type == 'getSetting') {
  382. responseData = await getSetting();
  383. }
  384. // else if (type == 'getSolutionHistory') {
  385. // responseData = getSolutionHistory();
  386. // }
  387. events_child.send({ type: 'response', id, data: responseData });
  388. }
  389. else if (method == 'post') {
  390. if (type == 'setSolutions') {
  391. setSolutions(data);
  392. }
  393. }
  394. else if (method == 'response' && id && callbacks[id]) {
  395. callbacks[id](data);
  396. delete callbacks[id];
  397. }
  398. });
  399. module.exports = {
  400. updateLeaguesList, getFilteredLeagues,
  401. updateGamesList, updateGamesEvents,
  402. getGamesRelation,
  403. updateGamesResult,
  404. }