import dotenv from 'dotenv'; import axios from "axios"; import { HttpsProxyAgent } from "https-proxy-agent"; import { randomUUID } from 'crypto'; import Logs from "./logs.js"; import getDateInTimezone from "./getDateInTimezone.js"; dotenv.config(); const axiosDefaultOptions = { baseURL: "", url: "", method: "GET", headers: {}, params: {}, data: {}, timeout: 10000, }; const pinnacleWebOptions = { ...axiosDefaultOptions, baseURL: "https://www.part987.com", headers: { "accept": "application/json, text/plain, */*", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36", "x-app-data": "directusToken=TwEdnphtyxsfMpXoJkCkWaPsL2KJJ3lo;lang=zh_CN;dpVXz=ZDfaFZUP9" }, } export const pinnacleRequest = async (options) => { const { url, ...optionsRest } = options; const username = process.env.PINNACLE_USERNAME; const password = process.env.PINNACLE_PASSWORD; if (!url || !username || !password) { throw new Error("url、username、password is required"); } const authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`; const axiosConfig = { ...axiosDefaultOptions, ...optionsRest, url, baseURL: "https://api.pinnacle888.com" }; Object.assign(axiosConfig.headers, { "Authorization": authHeader, "Accept": "application/json", }); const proxy = process.env.NODE_HTTP_PROXY; if (proxy) { axiosConfig.proxy = false; axiosConfig.httpsAgent = new HttpsProxyAgent(proxy); } return axios(axiosConfig).then(res => { // Logs.out('pinnacle request', url, axiosConfig, res.data); return res.data; }); } /** * Pinnacle API Get请求 * @param {*} url * @param {*} params * @returns */ export const pinnacleGet = async (url, params) => { return pinnacleRequest({ url, params }) .catch(err => { const source = { url, params }; if (err?.response?.data) { const data = err.response.data; Object.assign(source, { data }); } err.source = source; return Promise.reject(err); }); } /** * Pinnacle API Post请求 * @param {*} url * @param {*} data * @returns */ export const pinnaclePost = async (url, data) => { return pinnacleRequest({ url, method: 'POST', headers: { 'Content-Type': 'application/json', }, data }); } /** * 清理对象中的undefined值 * @param {*} obj * @returns {Object} */ const cleanUndefined = (obj) => { return Object.fromEntries( Object.entries(obj).filter(([, v]) => v !== undefined) ); } /** * 获取直盘线 */ export const getLineInfo = async (info = {}) => { const { leagueId, eventId, betType, handicap, team, side, specialId, contestantId, periodNumber=0, oddsFormat='Decimal', sportId=29 } = info; let url = '/v2/line/'; let data = { sportId, leagueId, eventId, betType, handicap, periodNumber, team, side, oddsFormat }; if (specialId) { url = '/v2/line/special'; data = { specialId, contestantId, oddsFormat }; } data = cleanUndefined(data); return pinnacleGet(url, data) .then(ret => ({ info: ret, line: data })) .catch(err => { Logs.errDev('get line info error', err); return Promise.reject(err); }); } /** * 获取账户余额 */ export const getAccountBalance = async () => { return pinnacleGet('/v1/client/balance'); } /** * 下注 */ export const placeOrder = async ({ info, line, stakeSize }) => { // return Promise.resolve({info, line, stakeSize}); const uuid = randomUUID() if (line.specialId) { const data = cleanUndefined({ oddsFormat: line.oddsFormat, uniqueRequestId: uuid, acceptBetterLine: true, stake: stakeSize, winRiskStake: 'RISK', lineId: info.lineId, specialId: info.specialId, contestantId: info.contestantId, }); Logs.outDev('pinnacle place order data', data); return pinnaclePost('/v4/bets/special', { bets: [data] }) .then(ret => ret.bets?.[0] ?? ret) .then(ret => { Logs.outDev('pinnacle place order', ret, uuid); return ret; }) .catch(err => { Logs.outDev('pinnacle place order error', err.response.data, uuid); Logs.errDev(err); return Promise.reject(err); }); } else { const data = cleanUndefined({ oddsFormat: line.oddsFormat, uniqueRequestId: uuid, acceptBetterLine: true, stake: stakeSize, winRiskStake: 'RISK', lineId: info.lineId, altLineId: info.altLineId, fillType: 'NORMAL', sportId: line.sportId, eventId: line.eventId, periodNumber: line.periodNumber, betType: line.betType, team: line.team, side: line.side, handicap: line.handicap, }); Logs.outDev('pinnacle place order data', data); return pinnaclePost('/v4/bets/place', data) .then(ret => { Logs.outDev('pinnacle place order', ret, uuid); return ret; }) .catch(err => { Logs.outDev('pinnacle place order error', err.response.data, uuid); Logs.errDev(err); return Promise.reject(err); }); } } /** * 获取Web端联赛数据 * @param {*} marketType 0: 早盘赛事, 1: 今日赛事 * @param {*} locale en_US or zh_CN * @returns */ export const pinnacleWebLeagues = async (marketType = 1, locale = "zh_CN") => { const dateString = marketType == 1 ? getDateInTimezone(-4) : getDateInTimezone(-4, Date.now()+24*60*60*1000); const nowTime = Date.now(); const axiosConfig = { ...pinnacleWebOptions, url: "/sports-service/sv/compact/leagues", params: { btg: 1, c: "", d: dateString, l: true, mk: marketType, pa: 0, pn: -1, sp: 29, tm: 0, locale, _: nowTime, withCredentials: true }, }; const proxy = process.env.NODE_HTTP_PROXY; if (proxy) { axiosConfig.proxy = false; axiosConfig.httpsAgent = new HttpsProxyAgent(proxy); } return axios(axiosConfig).then(res => res.data?.[0]?.[2]?.map(item => { const [ id, , name ] = item; return [id, { id, name }]; }) ?? []); } /** * 获取Web比赛列表 */ export const pinnacleWebGames = async (leagues=[], marketType=1, locale="zh_CN") => { const dateString = marketType == 1 ? getDateInTimezone(-4) : getDateInTimezone(-4, Date.now()+24*60*60*1000); const nowTime = Date.now(); const axiosConfig = { ...pinnacleWebOptions, url: "/sports-service/sv/odds/events", params: { sp: 29, lg: leagues.join(','), ev: "", mk: marketType, btg: 1, ot: 1, d: dateString, o: 0, l: 100, v: 0, me: 0, more: false, tm: 0, pa: 0, c: "", g: "QQ==", cl: 100, pimo: "0,1,8,39,2,3,6,7,4,5", inl: false, _: nowTime, locale }, }; const proxy = process.env.NODE_HTTP_PROXY; if (proxy) { axiosConfig.proxy = false; axiosConfig.httpsAgent = new HttpsProxyAgent(proxy); } return axios(axiosConfig).then(res => res.data.n?.[0]?.[2]?.map(league => { const [leagueId, leagueName, games] = league; return games.map(game => { const [id, teamHomeName, teamAwayName, , timestamp] = game; const startTime = getDateInTimezone('+8', timestamp, true); return { id, leagueId, leagueName, teamHomeName, teamAwayName, timestamp, startTime } }) }).flat().filter(game => { const { teamHomeName, teamAwayName } = game; if (teamHomeName.startsWith('主队')) { return false; } else if (teamAwayName.startsWith('客队')) { return false; } else if (teamHomeName.startsWith('Home Teams')) { return false; } else if (teamAwayName.startsWith('Away Teams')) { return false; } return true; }) ?? []); } export const platformRequest = async (options) => { const { url } = options; if (!url) { throw new Error("url is required"); } const mergedOptions = { ...axiosDefaultOptions, ...options, baseURL: "http://127.0.0.1:9020", }; return axios(mergedOptions).then(res => res.data); } export const platformPost = async (url, data) => { return platformRequest({ url, method: 'POST', headers: { 'Content-Type': 'application/json', }, data, }); } export const platformGet = async (url, params) => { return platformRequest({ url, method: 'GET', params }); } /** * 通知异常 * @param {*} message */ export const notifyException = async (message) => { Logs.out('notify exception', message); }