Platforms.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { fork } from "child_process";
  2. import Store from "../state/store.js";
  3. import ProcessData from "../libs/processData.js";
  4. import Logs from "../libs/logs.js";
  5. import { getGamesRelationsMap } from "../libs/getGamesRelations.js";
  6. import { getSoccerGames, getObossOdds } from "./Partner.js";
  7. const getChildOptions = (inspect=9230) => {
  8. return process.env.NODE_ENV == 'development' ? {
  9. execArgv: [`--inspect=${inspect}`],
  10. stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
  11. env: { ...process.env }
  12. } : {
  13. stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
  14. env: { ...process.env }
  15. };
  16. }
  17. const triangleProcess = fork("triangle/main.js", [], getChildOptions(9229));
  18. const triangleData = new ProcessData(triangleProcess, 'triangle');
  19. triangleData.registerResponse('gamesRelations', async () => {
  20. const gamesRelations = Object.values(getGamesRelationsMap(true));
  21. return Promise.resolve(gamesRelations);
  22. });
  23. triangleData.registerRequest('solutions', solutions => {
  24. const oldSolutions = new Map((Store.get('solutions') ?? []).map(item => [item.sid, item]));
  25. const newSolutions = new Map(solutions.map(item => [item.sid, item]));
  26. const changed = {
  27. add: [],
  28. update: [],
  29. remove: [],
  30. }
  31. oldSolutions.forEach((item, sid) => {
  32. if (!newSolutions.has(sid)) {
  33. changed.remove.push(sid);
  34. }
  35. else if (newSolutions.get(sid).sol.win_profit_rate != item.sol.win_profit_rate || JSON.stringify(newSolutions.get(sid).cpr) != JSON.stringify(item.cpr)) {
  36. changed.update.push(sid);
  37. }
  38. });
  39. newSolutions.forEach((item, sid) => {
  40. if (!oldSolutions.has(sid)) {
  41. changed.add.push(sid);
  42. }
  43. });
  44. if (changed.update.length || changed.add.length || changed.remove.length) {
  45. Store.set('solutions', solutions);
  46. const profitableSolutions = solutions.filter(solution => solution.sol.win_profit_rate > 0);
  47. Logs.outDev('profitable solutions', profitableSolutions);
  48. }
  49. });
  50. /**
  51. * 通用的平台数据更新函数
  52. * @param {string} platform - 平台名称
  53. * @param {Array} newItems - 新的数据项数组
  54. * @param {string} storeKey - Store 中的键名
  55. * @returns {Promise}
  56. */
  57. const updatePlatformData = async ({ platform, newItems, storeKey }) => {
  58. if (!platform || !newItems?.length) {
  59. return Promise.reject(new Error('invalid request', { cause: 400 }));
  60. }
  61. let changed = false;
  62. const storeData = Store.get(storeKey) ?? {};
  63. const { [platform]: storePlatformItems = [] } = storeData;
  64. const storePlatformItemsMap = new Map(storePlatformItems.map(item => [item.id, item]));
  65. const newPlatformItemsMap = new Map(newItems.map(item => [item.id, item]));
  66. // 删除不存在的项
  67. storePlatformItemsMap.forEach(item => {
  68. if (!newPlatformItemsMap.has(item.id)) {
  69. storePlatformItemsMap.delete(item.id);
  70. changed = true;
  71. }
  72. });
  73. // 添加新的项
  74. newPlatformItemsMap.forEach(item => {
  75. if (!storePlatformItemsMap.has(item.id)) {
  76. storePlatformItemsMap.set(item.id, item);
  77. changed = true;
  78. }
  79. });
  80. // 更新 Store 中的数据
  81. if (changed) {
  82. const updatedPlatformItems = Array.from(storePlatformItemsMap.values());
  83. storeData[platform] = updatedPlatformItems;
  84. Store.set(storeKey, storeData);
  85. }
  86. return Promise.resolve();
  87. };
  88. /**
  89. * 更新联赛数据
  90. * @param {string} platform - 平台名称
  91. * @param {Array} leagues - 联赛数据
  92. * @returns
  93. */
  94. export const updateLeagues = async ({ platform, leagues }) => {
  95. return updatePlatformData({ platform, newItems: leagues, storeKey: 'leagues' });
  96. };
  97. /**
  98. * 获取过滤后的联赛数据
  99. * @param {string} platform - 平台名称
  100. * @returns
  101. */
  102. export const getRelatedLeagues = async (platform) => {
  103. const polymarketLeagues = Store.get('polymarket', 'leagues') ?? [];
  104. const polymarketLeaguesSet = new Set(polymarketLeagues.map(item => item.id));
  105. const leaguesRelations = Store.get('leaguesRelations') ?? {};
  106. const filteredLeagues = Object.values(leaguesRelations).filter(relation => {
  107. return polymarketLeaguesSet.has(relation.platforms.polymarket.id);
  108. }).map(relation => relation.platforms[platform]);
  109. return filteredLeagues;
  110. }
  111. /**
  112. * 更新比赛数据
  113. * @param {string} platform - 平台名称
  114. * @param {Array} games - 比赛数据
  115. * @returns
  116. */
  117. export const updateGames = async ({ platform, games }) => {
  118. return updatePlatformData({ platform, newItems: games, storeKey: 'games' });
  119. };
  120. /**
  121. * 获取过滤后的比赛数据
  122. * @param {string} platform - 平台名称
  123. * @returns
  124. */
  125. export const getRelatedGames = async (platform) => {
  126. const gamesRelations = Store.get('gamesRelations') ?? {};
  127. const filteredGames = Object.values(gamesRelations).map(relation => relation.platforms[platform]);
  128. return filteredGames;
  129. }
  130. /**
  131. * 更新赔率数据
  132. * @param {string} platform - 平台名称
  133. * @param {Array} games - 赔率数据
  134. * @param {number} timestamp - 时间戳
  135. * @returns
  136. */
  137. export const updateOdds = async ({ platform, games, timestamp }) => {
  138. Store.set(platform, { games, timestamp }, 'odds');
  139. return Promise.resolve();
  140. };
  141. /**
  142. * 同步QBoss赔率数据
  143. * @param {*} relationsData
  144. */
  145. const syncObossOdds = (relationsData) => {
  146. const timestamp = Date.now();
  147. const pinnacle = [];
  148. const obsports = [];
  149. const huangguan = [];
  150. relationsData.forEach(item => {
  151. Object.entries(item.rel).forEach(([key, value]) => {
  152. const { eventId: id, ...rest } = value;
  153. const game = { id, ...rest }
  154. if (key === 'pc') {
  155. pinnacle.push(game);
  156. }
  157. else if (key === 'ob') {
  158. obsports.push(game);
  159. }
  160. else if (key === 'hg') {
  161. huangguan.push(game);
  162. }
  163. });
  164. });
  165. Promise.all([
  166. updateOdds({ platform: 'pinnacle', games: pinnacle, timestamp }),
  167. updateOdds({ platform: 'obsports', games: obsports, timestamp }),
  168. updateOdds({ platform: 'huangguan', games: huangguan, timestamp }),
  169. ]);
  170. }
  171. /**
  172. * 定时更新QBoss赔率数据
  173. */
  174. const updateObossOdds = () => {
  175. getObossOdds()
  176. .then(res => {
  177. if (res.statusCode === 200) {
  178. // Logs.outDev('update oboss odds', res.data);
  179. return syncObossOdds(res.data);
  180. }
  181. return Promise.reject(new Error(`status code ${res.statusCode}`));
  182. })
  183. .catch(error => {
  184. Logs.err('failed to update oboss odds', error.message);
  185. })
  186. .finally(() => {
  187. setTimeout(() => {
  188. updateObossOdds();
  189. }, 1000 * 2);
  190. });
  191. }
  192. updateObossOdds();
  193. /**
  194. * 同步关联比赛数据
  195. * @param {*} relationsData
  196. */
  197. const syncGamesRelations = (relationsData) => {
  198. const storeRelations = Store.get('gamesRelations') ?? {};
  199. const newRelations = relationsData.map(item => {
  200. const {
  201. event_id: pmId,
  202. ps_event_id: pcId,
  203. ob_event_id: obId,
  204. hg_event_id: hgId,
  205. start_time: startTime,
  206. } = item;
  207. const timestamp = new Date(startTime).getTime();
  208. return {
  209. id: pmId,
  210. timestamp,
  211. platforms: {
  212. polymarket: { id: +pmId },
  213. pinnacle: { id: +pcId },
  214. huangguan: { id: +hgId },
  215. obsports: { id: +obId }
  216. }
  217. }
  218. });
  219. const changed = { add: 0, update: 0, remove: 0 }
  220. const newRelationsSet = new Set(newRelations.map(item => item.id));
  221. Object.keys(storeRelations).forEach(id => {
  222. if (!newRelationsSet.has(+id)) {
  223. delete storeRelations[id];
  224. changed.remove++;
  225. }
  226. });
  227. newRelations.forEach(item => {
  228. if (!storeRelations[item.id]) {
  229. storeRelations[item.id] = item;
  230. changed.add++;
  231. }
  232. });
  233. if (changed.add || changed.update || changed.remove) {
  234. Store.set('gamesRelations', storeRelations);
  235. Logs.outDev('sync games relations', changed, storeRelations);
  236. }
  237. }
  238. /**
  239. * 定时更新关联比赛
  240. */
  241. const updateGamesRelations = () => {
  242. getSoccerGames()
  243. .then(res => {
  244. if (res.success) {
  245. return syncGamesRelations(res.data);
  246. }
  247. return Promise.reject(new Error(res.message));
  248. })
  249. .catch(error => {
  250. Logs.err('failed to update games relations', error.message);
  251. })
  252. .finally(() => {
  253. setTimeout(() => {
  254. updateGamesRelations();
  255. }, 1000 * 30);
  256. });
  257. }
  258. updateGamesRelations();
  259. export default {
  260. updateLeagues, getRelatedLeagues,
  261. updateGames, getRelatedGames,
  262. updateOdds,
  263. };