GamesPs.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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. Logs.out('submitOdds', JSON.stringify(games));
  146. axios.post(`${BASE_URL}/syncOdds`, { platform, mk, games})
  147. .then(res => {
  148. Logs.out('syncOdds', { platform, mk, count: games.length }, res.data);
  149. })
  150. .catch(err => {
  151. Logs.out('syncOdds', { platform, mk }, err.message);
  152. });
  153. }
  154. /**
  155. * 同步基准盘口
  156. */
  157. const syncBaseEvents = ({ mk, games, outrights }) => {
  158. const marketType = getMarketType(mk);
  159. const baseList = GAMES.Baselist;
  160. if (!baseList[marketType]) {
  161. return;
  162. }
  163. const baseMap = new Map(baseList[marketType].map(item => [item.eventId, item]));
  164. games?.forEach(game => {
  165. const { eventId, evtime, events } = game;
  166. const baseGame = baseMap.get(eventId);
  167. if (baseGame) {
  168. baseGame.evtime = evtime;
  169. baseGame.events = events;
  170. }
  171. });
  172. outrights?.forEach(outright => {
  173. const { parentId, sptime, special } = outright;
  174. const baseGame = baseMap.get(parentId);
  175. if (baseGame) {
  176. baseGame.sptime = sptime;
  177. baseGame.special = special;
  178. }
  179. });
  180. if (games?.length) {
  181. const gamesList = baseList[marketType]?.map(game => {
  182. const { evtime, events, sptime, special, ...gameInfo } = game;
  183. const expireTime = Date.now() - 15000;
  184. let odds = {};
  185. if (evtime > expireTime) {
  186. odds = { ...odds, ...events };
  187. }
  188. if (sptime > expireTime) {
  189. odds = { ...odds, ...special };
  190. }
  191. const matches = PS_IOR_KEYS.map(([label, ...keys]) => {
  192. const match = keys.map(key => ({
  193. key,
  194. value: odds[key] ?? 0
  195. }));
  196. return {
  197. label,
  198. match
  199. };
  200. }).filter(item => item.match.every(entry => entry.value !== 0));
  201. return { ...gameInfo, matches };
  202. });
  203. // Logs.out('baseList', baseList[marketType]);
  204. submitOdds({ platform: 'ps', mk, games: gamesList });
  205. }
  206. }
  207. /**
  208. * 更新比赛盘口
  209. */
  210. const updateGamesEvents = ({ platform, mk, games, outrights }) => {
  211. return new Promise((resolve, reject) => {
  212. if (!platform || (!games && !outrights)) {
  213. return reject(new Error('PLATFORM_GAMES_INVALID'));
  214. }
  215. if (platform == 'ps') {
  216. syncBaseEvents({ mk, games, outrights });
  217. }
  218. const relatedGames = Object.values(GAMES.Relations).map(item => item.rel?.[platform] ?? {});
  219. if (!relatedGames.length) {
  220. return resolve({ update: 0 });
  221. }
  222. const updateCount = {
  223. update: 0
  224. };
  225. const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
  226. games?.forEach(game => {
  227. const { eventId, evtime, events } = game;
  228. const relatedGame = relatedMap.get(eventId);
  229. if (relatedGame) {
  230. relatedGame.evtime = evtime;
  231. relatedGame.events = events;
  232. updateCount.update ++;
  233. }
  234. });
  235. outrights?.forEach(outright => {
  236. const { parentId, sptime, special } = outright;
  237. const relatedGame = relatedMap.get(parentId);
  238. if (relatedGame) {
  239. relatedGame.sptime = sptime;
  240. relatedGame.special = special;
  241. updateCount.update ++;
  242. }
  243. });
  244. resolve(updateCount);
  245. });
  246. }
  247. /**
  248. * 获取关联比赛
  249. */
  250. const fetchGamesRelation = async (mk='') => {
  251. return axios.get(`${BASE_URL}/getGameTast?mk=${mk}`)
  252. .then(res => {
  253. if (res.data.code == 0) {
  254. const now = Date.now();
  255. const gamesRelation = res.data.data?.filter(item => {
  256. const timestamp = new Date(item.timestamp).getTime();
  257. return timestamp > now;
  258. }).map(item => {
  259. const {
  260. id, mk,
  261. event_id: ps_event_id,
  262. league_id: ps_league_id,
  263. ob_event_id, ob_league_id,
  264. hg_event_id, hg_league_id,
  265. } = item;
  266. const rel = {
  267. ps: {
  268. eventId: +ps_event_id,
  269. leagueId: +ps_league_id
  270. },
  271. ob: ob_event_id ? {
  272. eventId: +ob_event_id,
  273. leagueId: +ob_league_id
  274. } : null,
  275. hg: hg_event_id ? {
  276. eventId: +hg_event_id,
  277. leagueId: +hg_league_id
  278. } : null
  279. };
  280. return { id, mk, rel };
  281. }) ?? [];
  282. return gamesRelation;
  283. }
  284. return Promise.reject(new Error(res.data.message));
  285. });
  286. }
  287. const getGamesRelation = ({ mk, listEvents }) => {
  288. const relations = Object.values(GAMES.Relations).filter(item => {
  289. if (!mk) {
  290. return true;
  291. }
  292. return item.mk == mk;
  293. });
  294. if (listEvents) {
  295. return relations;
  296. }
  297. return relations.map(item => {
  298. const { rel, ...relationInfo } = item;
  299. Object.keys(rel).forEach(platform => {
  300. const { events, evtime, sptime, special, ...gameInfo } = rel[platform];
  301. rel[platform] = gameInfo;
  302. });
  303. return { ...relationInfo, rel };
  304. });
  305. }
  306. /**
  307. * 定时更新关联比赛列表
  308. */
  309. const updateGamesRelation = () => {
  310. fetchGamesRelation()
  311. .then(res => {
  312. const gamesRelation = res.flat();
  313. gamesRelation.forEach(item => {
  314. const { id } = item;
  315. if (!GAMES.Relations[id]) {
  316. GAMES.Relations[id] = item;
  317. }
  318. });
  319. const relations = new Set(gamesRelation.map(item => +item.id));
  320. Object.keys(GAMES.Relations).forEach(id => {
  321. if (!relations.has(+id)) {
  322. delete GAMES.Relations[id];
  323. }
  324. });
  325. })
  326. .catch(err => {
  327. Logs.out('updateGamesRelation', err.message);
  328. })
  329. .finally(() => {
  330. setTimeout(updateGamesRelation, 60000);
  331. });
  332. }
  333. updateGamesRelation();
  334. /**
  335. * 同步比赛结果
  336. */
  337. const syncGamesResult = async (result) => {
  338. if (IS_DEV) {
  339. return Logs.out('updateGamesResult', result);
  340. }
  341. axios.post(`${BASE_URL}/syncMatchResult`, result)
  342. .then(res => {
  343. Logs.out('syncMatchResult', res.data);
  344. })
  345. .catch(err => {
  346. Logs.out('syncMatchResult', err.message);
  347. });
  348. }
  349. /**
  350. * 更新比赛结果
  351. */
  352. const updateGamesResult = (result) => {
  353. syncGamesResult(result);
  354. return Promise.resolve();
  355. }
  356. /**
  357. * 获取后台设置
  358. */
  359. const getSetting = async () => {
  360. return Setting.get();
  361. }
  362. /**
  363. * 从子进程获取数据
  364. */
  365. const getDataFromChild = (type, callback) => {
  366. const id = ++Request.count;
  367. Request.callbacks[id] = callback;
  368. events_child.send({ method: 'get', id, type });
  369. }
  370. /**
  371. * 处理子进程消息
  372. */
  373. events_child.on('message', async (message) => {
  374. const { callbacks } = Request;
  375. const { method, id, type, data } = message;
  376. if (method == 'get' && id) {
  377. let responseData = null;
  378. if (type == 'getGamesRelation') {
  379. responseData = getGamesRelation({ listEvents: true });
  380. Logs.out('getGamesRelation', responseData);
  381. }
  382. else if (type == 'getSetting') {
  383. responseData = await getSetting();
  384. }
  385. // else if (type == 'getSolutionHistory') {
  386. // responseData = getSolutionHistory();
  387. // }
  388. events_child.send({ type: 'response', id, data: responseData });
  389. }
  390. else if (method == 'post') {
  391. if (type == 'setSolutions') {
  392. setSolutions(data);
  393. }
  394. }
  395. else if (method == 'response' && id && callbacks[id]) {
  396. callbacks[id](data);
  397. delete callbacks[id];
  398. }
  399. });
  400. module.exports = {
  401. updateLeaguesList, getFilteredLeagues,
  402. updateGamesList, updateGamesEvents,
  403. getGamesRelation,
  404. updateGamesResult,
  405. }