main.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. import { writeFileSync } from 'fs';
  2. import 'dotenv/config';
  3. import { pinnacleRequest, getPsteryRelations, updateBaseEvents, notifyException } from "./libs/pinnacleClient.js";
  4. import { Logs } from "./libs/logs.js";
  5. const cacheFilePath = 'data/gamesCache.json';
  6. const GLOBAL_DATA = {
  7. filtedLeagues: [],
  8. filtedGames: [],
  9. gamesMap: {},
  10. straightFixturesVersion: 0,
  11. straightFixturesCount: 0,
  12. specialFixturesVersion: 0,
  13. specialFixturesCount: 0,
  14. straightOddsVersion: 0,
  15. specialsOddsVersion: 0,
  16. requestErrorCount: 0,
  17. loopActive: false,
  18. loopResultTime: Date.now(),
  19. };
  20. /**
  21. * 获取指定时区当前日期或时间
  22. * @param {number} offsetHours - 时区相对 UTC 的偏移(例如:-4 表示 GMT-4)
  23. * @param {boolean} [withTime=false] - 是否返回完整时间(默认只返回日期)
  24. * @returns {string} 格式化的日期或时间字符串
  25. */
  26. const getDateInTimezone = (offsetHours, withTime = false) => {
  27. const nowUTC = new Date();
  28. const targetTime = new Date(nowUTC.getTime() + offsetHours * 60 * 60 * 1000);
  29. const year = targetTime.getUTCFullYear();
  30. const month = String(targetTime.getUTCMonth() + 1).padStart(2, '0');
  31. const day = String(targetTime.getUTCDate()).padStart(2, '0');
  32. if (!withTime) {
  33. return `${year}-${month}-${day}`;
  34. }
  35. const hours = String(targetTime.getUTCHours()).padStart(2, '0');
  36. const minutes = String(targetTime.getUTCMinutes()).padStart(2, '0');
  37. const seconds = String(targetTime.getUTCSeconds()).padStart(2, '0');
  38. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  39. }
  40. const pinnacleGet = async (endpoint, params) => {
  41. return pinnacleRequest({
  42. endpoint,
  43. params,
  44. username: process.env.PINNACLE_USERNAME,
  45. password: process.env.PINNACLE_PASSWORD,
  46. proxy: process.env.NODE_HTTP_PROXY,
  47. })
  48. .catch(err => {
  49. const source = { endpoint, params };
  50. if (err?.response?.data) {
  51. const data = err.response.data;
  52. Object.assign(source, { data });
  53. }
  54. err.source = source;
  55. return Promise.reject(err);
  56. });
  57. };
  58. // const pinnaclePost = async(endpoint, data) => {
  59. // return pinnacleRequest({
  60. // endpoint,
  61. // data,
  62. // method: 'POST',
  63. // username: process.env.PINNACLE_USERNAME,
  64. // password: process.env.PINNACLE_PASSWORD,
  65. // proxy: process.env.NODE_HTTP_PROXY,
  66. // });
  67. // };
  68. const updateFiltedGames = async () => {
  69. return getPsteryRelations()
  70. .then(res => {
  71. if (res.statusCode !== 200) {
  72. throw new Error(`Failed to update filted leagues: ${res.message}`);
  73. }
  74. Logs.outDev('update filted games', res.data);
  75. const games = res.data.map(game => {
  76. const { eventId, leagueId } = game?.rel?.ps ?? {};
  77. return {
  78. eventId,
  79. leagueId,
  80. };
  81. });
  82. GLOBAL_DATA.filtedLeagues = [...new Set(games.map(game => game.leagueId).filter(leagueId => leagueId))];
  83. GLOBAL_DATA.filtedGames = games.map(game => game.eventId).filter(eventId => eventId);
  84. })
  85. .catch(err => {
  86. Logs.err(err.message);
  87. })
  88. .finally(() => {
  89. setTimeout(updateFiltedGames, 1000 * 30);
  90. });
  91. }
  92. const getStraightFixtures = async () => {
  93. const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  94. let since = GLOBAL_DATA.straightFixturesVersion;
  95. if (GLOBAL_DATA.straightFixturesCount > 12) {
  96. since = 0;
  97. GLOBAL_DATA.straightFixturesCount = 0;
  98. }
  99. if (since == 0) {
  100. Logs.outDev('full update straight fixtures');
  101. }
  102. return pinnacleGet('/v3/fixtures', { sportId: 29, leagueIds, since })
  103. .then(data => {
  104. const { league, last } = data;
  105. if (!last) {
  106. return {};
  107. }
  108. GLOBAL_DATA.straightFixturesVersion = last;
  109. const games = league?.map(league => {
  110. const { id: leagueId, events } = league;
  111. return events?.map(event => {
  112. const { starts } = event;
  113. const timestamp = new Date(starts).getTime();
  114. return { leagueId, ...event, timestamp };
  115. });
  116. })
  117. .flat() ?? [];
  118. const update = since == 0 ? 'full' : 'increment';
  119. return { games, update };
  120. });
  121. }
  122. const updateStraightFixtures = async () => {
  123. return getStraightFixtures()
  124. .then(data => {
  125. const { games, update } = data;
  126. const { gamesMap } = GLOBAL_DATA;
  127. if (games?.length) {
  128. games.forEach(game => {
  129. const { id } = game;
  130. if (!gamesMap[id]) {
  131. gamesMap[id] = game;
  132. }
  133. else {
  134. Object.assign(gamesMap[id], game);
  135. }
  136. });
  137. }
  138. if (update && update == 'full') {
  139. const gamesSet = new Set(games.map(game => game.id));
  140. Object.keys(gamesMap).forEach(key => {
  141. if (!gamesSet.has(+key)) {
  142. delete gamesMap[key];
  143. }
  144. });
  145. }
  146. });
  147. }
  148. const getStraightOdds = async () => {
  149. const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  150. const since = GLOBAL_DATA.straightOddsVersion;
  151. return pinnacleGet('/v3/odds', { sportId: 29, oddsFormat: 'Decimal', leagueIds, since })
  152. .then(data => {
  153. const { leagues, last } = data;
  154. if (!last) {
  155. return [];
  156. }
  157. GLOBAL_DATA.straightOddsVersion = last;
  158. const games = leagues?.flatMap(league => league.events);
  159. return games?.map(item => {
  160. const { periods, ...rest } = item;
  161. const period = periods?.find(period => period.number == 0) ?? {};
  162. return { ...rest, period };
  163. }) ?? [];
  164. });
  165. }
  166. const updateStraightOdds = async () => {
  167. return getStraightOdds()
  168. .then(games => {
  169. if (games.length) {
  170. const { gamesMap } = GLOBAL_DATA;
  171. games.forEach(game => {
  172. const { id, ...rest } = game;
  173. const localGame = gamesMap[id];
  174. if (localGame) {
  175. Object.assign(localGame, rest);
  176. }
  177. });
  178. }
  179. });
  180. }
  181. const getSpecialFixtures = async () => {
  182. const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  183. let since = GLOBAL_DATA.specialFixturesVersion;
  184. if (GLOBAL_DATA.specialFixturesCount > 18) {
  185. since = 0;
  186. GLOBAL_DATA.specialFixturesCount = 6;
  187. }
  188. if (since == 0) {
  189. Logs.outDev('full update special fixtures');
  190. }
  191. return pinnacleGet('/v2/fixtures/special', { sportId: 29, leagueIds, since })
  192. .then(data => {
  193. const { leagues, last } = data;
  194. if (!last) {
  195. return [];
  196. }
  197. GLOBAL_DATA.specialFixturesVersion = last;
  198. const specials = leagues?.map(league => {
  199. const { specials } = league;
  200. return specials?.filter(special => special.event)
  201. .map(special => {
  202. const { event: { id: eventId }, ...rest } = special ?? { event: {} };
  203. return { eventId, ...rest };
  204. }) ?? [];
  205. })
  206. .flat()
  207. .filter(special => {
  208. if (special.name != 'Winning Margin' && special.name != 'Exact Total Goals') {
  209. return false;
  210. }
  211. return true;
  212. }) ?? [];
  213. const update = since == 0 ? 'full' : 'increment';
  214. return { specials, update };
  215. });
  216. }
  217. const mergeContestants = (localContestants=[], remoteContestants=[]) => {
  218. const localContestantsMap = new Map(localContestants.map(contestant => [contestant.id, contestant]));
  219. remoteContestants.forEach(contestant => {
  220. const localContestant = localContestantsMap.get(contestant.id);
  221. if (localContestant) {
  222. Object.assign(localContestant, contestant);
  223. }
  224. else {
  225. localContestants.push(contestant);
  226. }
  227. });
  228. const remoteContestantsMap = new Map(remoteContestants.map(contestant => [contestant.id, contestant]));
  229. for (let i = localContestants.length - 1; i >= 0; i--) {
  230. if (!remoteContestantsMap.has(localContestants[i].id)) {
  231. localContestants.splice(i, 1);
  232. }
  233. }
  234. return localContestants;
  235. }
  236. const updateSpecialFixtures = async () => {
  237. return getSpecialFixtures()
  238. .then(data => {
  239. const { specials, update } = data;
  240. if (specials?.length) {
  241. const { gamesMap } = GLOBAL_DATA;
  242. const gamesSpecialsMap = {};
  243. specials.forEach(special => {
  244. const { eventId } = special;
  245. if (!gamesSpecialsMap[eventId]) {
  246. gamesSpecialsMap[eventId] = {};
  247. }
  248. if (special.name == 'Winning Margin') {
  249. gamesSpecialsMap[eventId].winningMargin = special;
  250. }
  251. else if (special.name == 'Exact Total Goals') {
  252. gamesSpecialsMap[eventId].exactTotalGoals = special;
  253. }
  254. });
  255. Object.keys(gamesSpecialsMap).forEach(eventId => {
  256. if (!gamesMap[eventId]) {
  257. return;
  258. }
  259. if (!gamesMap[eventId].specials) {
  260. gamesMap[eventId].specials = {};
  261. }
  262. const localSpecials = gamesMap[eventId].specials;
  263. const remoteSpecials = gamesSpecialsMap[eventId];
  264. if (localSpecials.winningMargin && remoteSpecials.winningMargin) {
  265. const { contestants: winningMarginContestants, ...winningMarginRest } = remoteSpecials.winningMargin;
  266. Object.assign(localSpecials.winningMargin, winningMarginRest);
  267. mergeContestants(localSpecials.winningMargin.contestants, winningMarginContestants);
  268. }
  269. else if (localSpecials.winningMargin && !remoteSpecials.winningMargin) {
  270. Logs.outDev('delete winningMargin', localSpecials.winningMargin);
  271. delete localSpecials.winningMargin;
  272. }
  273. else if (remoteSpecials.winningMargin) {
  274. localSpecials.winningMargin = remoteSpecials.winningMargin;
  275. }
  276. if (localSpecials.exactTotalGoals && remoteSpecials.exactTotalGoals) {
  277. const { contestants: exactTotalGoalsContestants, ...exactTotalGoalsRest } = remoteSpecials.exactTotalGoals;
  278. Object.assign(localSpecials.exactTotalGoals, exactTotalGoalsRest);
  279. mergeContestants(localSpecials.exactTotalGoals.contestants, exactTotalGoalsContestants);
  280. }
  281. else if (localSpecials.exactTotalGoals && !remoteSpecials.exactTotalGoals) {
  282. Logs.outDev('delete exactTotalGoals', localSpecials.exactTotalGoals);
  283. delete localSpecials.exactTotalGoals;
  284. }
  285. else if (remoteSpecials.exactTotalGoals) {
  286. localSpecials.exactTotalGoals = remoteSpecials.exactTotalGoals;
  287. }
  288. });
  289. }
  290. });
  291. }
  292. const getSpecialsOdds = async () => {
  293. const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  294. const since = GLOBAL_DATA.specialsOddsVersion;
  295. return pinnacleGet('/v2/odds/special', { sportId: 29, oddsFormat: 'Decimal', leagueIds, since })
  296. .then(data => {
  297. const { leagues, last } = data;
  298. if (!last) {
  299. return [];
  300. }
  301. GLOBAL_DATA.specialsOddsVersion = last;
  302. return leagues?.flatMap(league => league.specials);
  303. });
  304. }
  305. const updateSpecialsOdds = async () => {
  306. return getSpecialsOdds()
  307. .then(specials => {
  308. if (specials.length) {
  309. const { gamesMap } = GLOBAL_DATA;
  310. const contestants = Object.values(gamesMap)
  311. .filter(game => game.specials)
  312. .map(game => {
  313. const { specials } = game;
  314. const contestants = Object.values(specials).map(special => {
  315. const { contestants } = special;
  316. return contestants.map(contestant => [contestant.id, contestant]);
  317. });
  318. return contestants;
  319. }).flat(2);
  320. const contestantsMap = new Map(contestants);
  321. const lines = specials.flatMap(special => special.contestantLines);
  322. lines.forEach(line => {
  323. const { id, handicap, lineId, max, price } = line;
  324. const contestant = contestantsMap.get(id);
  325. if (!contestant) {
  326. return;
  327. }
  328. contestant.handicap = handicap;
  329. contestant.lineId = lineId;
  330. contestant.max = max;
  331. contestant.price = price;
  332. });
  333. }
  334. });
  335. }
  336. const getInRunning = async () => {
  337. return pinnacleGet('/v2/inrunning')
  338. .then(data => {
  339. const sportId = 29;
  340. const leagues = data.sports?.find(sport => sport.id == sportId)?.leagues ?? [];
  341. return leagues.filter(league => {
  342. const { id } = league;
  343. const filtedLeaguesSet = new Set(GLOBAL_DATA.filtedLeagues);
  344. return filtedLeaguesSet.has(id);
  345. }).flatMap(league => league.events);
  346. });
  347. }
  348. const updateInRunning = async () => {
  349. return getInRunning()
  350. .then(games => {
  351. if (!games.length) {
  352. return;
  353. }
  354. const { gamesMap } = GLOBAL_DATA;
  355. games.forEach(game => {
  356. const { id, state, elapsed } = game;
  357. const localGame = gamesMap[id];
  358. if (localGame) {
  359. Object.assign(localGame, { state, elapsed });
  360. }
  361. });
  362. });
  363. }
  364. const ratioAccept = (ratio) => {
  365. if (ratio > 0) {
  366. return 'a'
  367. }
  368. return ''
  369. }
  370. const ratioString = (ratio) => {
  371. ratio = Math.abs(ratio);
  372. ratio = ratio.toString();
  373. ratio = ratio.replace(/\./, '');
  374. return ratio;
  375. }
  376. const parseSpreads = (spreads, wm) => {
  377. // 让分盘
  378. if (!spreads?.length) {
  379. return null;
  380. }
  381. const events = {};
  382. spreads.forEach(spread => {
  383. const { hdp, home, away } = spread;
  384. if (!(hdp % 1) || !!(hdp % 0.5)) {
  385. // 整数或不能被0.5整除的让分盘不处理
  386. return;
  387. }
  388. const ratio_ro = hdp;
  389. const ratio_r = ratio_ro - wm;
  390. events[`ior_r${ratioAccept(ratio_r)}h_${ratioString(ratio_r)}`] = {
  391. v: home,
  392. r: wm != 0 ? `ior_r${ratioAccept(ratio_ro)}h_${ratioString(ratio_ro)}` : undefined
  393. };
  394. events[`ior_r${ratioAccept(-ratio_r)}c_${ratioString(ratio_r)}`] = {
  395. v: away,
  396. r: wm != 0 ? `ior_r${ratioAccept(-ratio_ro)}c_${ratioString(ratio_ro)}` : undefined
  397. };
  398. });
  399. return events;
  400. }
  401. const parseMoneyline = (moneyline) => {
  402. // 胜平负
  403. if (!moneyline) {
  404. return null;
  405. }
  406. const { home, away, draw } = moneyline;
  407. return {
  408. 'ior_mh': { v: home },
  409. 'ior_mc': { v: away },
  410. 'ior_mn': { v: draw },
  411. }
  412. }
  413. const parsePeriod = (period, wm) => {
  414. const { cutoff='', status=0, spreads=[], moneyline={} } = period;
  415. const cutoffTime = new Date(cutoff).getTime();
  416. const nowTime = Date.now();
  417. if (status != 1 || cutoffTime < nowTime) {
  418. return null;
  419. }
  420. const events = {};
  421. Object.assign(events, parseSpreads(spreads, wm));
  422. Object.assign(events, parseMoneyline(moneyline));
  423. return events;
  424. }
  425. const parseWinningMargin = (winningMargin, home, away) => {
  426. const { cutoff='', status='', contestants=[] } = winningMargin;
  427. const cutoffTime = new Date(cutoff).getTime();
  428. const nowTime = Date.now();
  429. if (status != 'O' || cutoffTime < nowTime || !contestants?.length) {
  430. return null;
  431. }
  432. const events = {};
  433. contestants.forEach(contestant => {
  434. const { name, price } = contestant;
  435. const nr = name.match(/\d+$/)?.[0];
  436. if (!nr) {
  437. return;
  438. }
  439. let side;
  440. if (name.startsWith(home)) {
  441. side = 'h';
  442. }
  443. else if (name.startsWith(away)) {
  444. side = 'c';
  445. }
  446. else {
  447. return;
  448. }
  449. events[`ior_wm${side}_${nr}`] = { v: price };
  450. });
  451. return events;
  452. }
  453. const parseExactTotalGoals = (exactTotalGoals) => {
  454. const { cutoff='', status='', contestants=[] } = exactTotalGoals;
  455. const cutoffTime = new Date(cutoff).getTime();
  456. const nowTime = Date.now();
  457. if (status != 'O' || cutoffTime < nowTime || !contestants?.length) {
  458. return null;
  459. }
  460. const events = {};
  461. contestants.forEach(contestant => {
  462. const { name, price } = contestant;
  463. if (+name >= 1 && +name <= 7) {
  464. events[`ior_ot_${name}`] = { v: price };
  465. }
  466. });
  467. return events;
  468. }
  469. const parseRbState = (state) => {
  470. let stage = null;
  471. if (state == 1) {
  472. stage = '1H';
  473. }
  474. else if (state == 2) {
  475. stage = 'HT';
  476. }
  477. else if (state == 3) {
  478. stage = '2H';
  479. }
  480. return stage;
  481. }
  482. const parseGame = (game) => {
  483. const { eventId=0, originId=0, period={}, specials={}, home, away, marketType, state, elapsed, homeScore=0, awayScore=0 } = game;
  484. const { winningMargin={}, exactTotalGoals={} } = specials;
  485. const wm = homeScore - awayScore;
  486. const score = `${homeScore}-${awayScore}`;
  487. const events = parsePeriod(period, wm) ?? {};
  488. const stage = parseRbState(state);
  489. const retime = elapsed ? `${elapsed}'` : '';
  490. const evtime = Date.now();
  491. Object.assign(events, parseWinningMargin(winningMargin, home, away));
  492. Object.assign(events, parseExactTotalGoals(exactTotalGoals));
  493. return { eventId, originId, events, evtime, stage, retime, score, wm, marketType };
  494. }
  495. const getGames = () => {
  496. const { filtedGames, gamesMap } = GLOBAL_DATA;
  497. const filtedGamesSet = new Set(filtedGames);
  498. const nowTime = Date.now();
  499. const gamesData = {};
  500. Object.values(gamesMap).forEach(game => {
  501. const { id, liveStatus, parentId, resultingUnit, timestamp } = game;
  502. if (resultingUnit !== 'Regular') {
  503. return false; // 非常规赛事不处理
  504. }
  505. const gmtMinus4Date = getDateInTimezone(-4);
  506. const todayEndTime = new Date(`${gmtMinus4Date} 23:59:59 GMT-4`).getTime();
  507. const tomorrowEndTime = todayEndTime + 24 * 60 * 60 * 1000;
  508. if (liveStatus == 1 && timestamp < nowTime) {
  509. game.marketType = 2; // 滚球赛事
  510. }
  511. else if (liveStatus != 1 && timestamp > nowTime && timestamp <= todayEndTime) {
  512. game.marketType = 1; // 今日赛事
  513. }
  514. else if (liveStatus != 1 && timestamp > todayEndTime && timestamp <= tomorrowEndTime) {
  515. game.marketType = 0; // 明日早盘赛事
  516. }
  517. else {
  518. game.marketType = -1; // 非近期赛事
  519. }
  520. if (game.marketType < 0) {
  521. return false; // 非近期赛事不处理
  522. }
  523. let actived = false;
  524. if (liveStatus != 1 && filtedGamesSet.has(id)) {
  525. actived = true; // 在赛前列表中
  526. game.eventId = id;
  527. game.originId = 0;
  528. }
  529. else if (liveStatus == 1 && filtedGamesSet.has(parentId)) {
  530. actived = true; // 在滚球列表中
  531. game.eventId = parentId;
  532. game.originId = id;
  533. }
  534. if (actived) {
  535. const gameInfo = parseGame(game);
  536. const { marketType, ...rest } = gameInfo;
  537. if (!gamesData[marketType]) {
  538. gamesData[marketType] = [];
  539. }
  540. gamesData[marketType].push(rest);
  541. }
  542. });
  543. return gamesData;
  544. }
  545. const pinnacleDataLoop = () => {
  546. updateStraightFixtures()
  547. .then(() => {
  548. return Promise.all([
  549. updateStraightOdds(),
  550. updateSpecialFixtures(),
  551. updateInRunning(),
  552. ]);
  553. })
  554. .then(() => {
  555. return updateSpecialsOdds();
  556. })
  557. .then(() => {
  558. if (!GLOBAL_DATA.loopActive) {
  559. GLOBAL_DATA.loopActive = true;
  560. Logs.out('loop active');
  561. }
  562. if (GLOBAL_DATA.requestErrorCount > 0) {
  563. GLOBAL_DATA.requestErrorCount = 0;
  564. Logs.out('request error count reset');
  565. }
  566. const nowTime = Date.now();
  567. const loopDuration = nowTime - GLOBAL_DATA.loopResultTime;
  568. GLOBAL_DATA.loopResultTime = nowTime;
  569. Logs.outDev('loop duration', loopDuration);
  570. const { straightFixturesVersion: sfv, specialFixturesVersion: pfv, straightOddsVersion: sov, specialsOddsVersion: pov } = GLOBAL_DATA;
  571. const timestamp = Math.max(sfv, pfv, sov, pov);
  572. const games = getGames();
  573. const data = { games, timestamp };
  574. updateBaseEvents(data);
  575. Logs.outDev('games data', data);
  576. writeFileSync(cacheFilePath, JSON.stringify(GLOBAL_DATA.gamesMap, null, 2));
  577. })
  578. .catch(err => {
  579. Logs.err(err.message, err.source);
  580. GLOBAL_DATA.requestErrorCount++;
  581. if (GLOBAL_DATA.loopActive && GLOBAL_DATA.requestErrorCount > 5) {
  582. Logs.out('request error count reached the limit');
  583. GLOBAL_DATA.loopActive = false;
  584. Logs.out('loop inactive');
  585. notifyException('Pinnacle API request errors have reached the limit.');
  586. }
  587. })
  588. .finally(() => {
  589. const { loopActive } = GLOBAL_DATA;
  590. let loopDelay = 1000 * 5;
  591. if (!loopActive) {
  592. loopDelay = 1000 * 60;
  593. GLOBAL_DATA.straightFixturesVersion = 0;
  594. GLOBAL_DATA.specialFixturesVersion = 0;
  595. GLOBAL_DATA.straightOddsVersion = 0;
  596. GLOBAL_DATA.specialsOddsVersion = 0;
  597. GLOBAL_DATA.straightFixturesCount = 0;
  598. GLOBAL_DATA.specialFixturesCount = 0;
  599. }
  600. else {
  601. GLOBAL_DATA.straightFixturesCount++;
  602. GLOBAL_DATA.specialFixturesCount++;
  603. }
  604. setTimeout(pinnacleDataLoop, loopDelay);
  605. });
  606. }
  607. (() => {
  608. if (!process.env.PINNACLE_USERNAME || !process.env.PINNACLE_PASSWORD) {
  609. Logs.err('USERNAME or PASSWORD is not set');
  610. return;
  611. }
  612. GLOBAL_DATA.loopActive = true;
  613. updateFiltedGames().then(pinnacleDataLoop);
  614. })();