JavaScript Workflow Examples
Live Update (HugeAPI)
javascript
const headers = {
"x-portal-apikey": "__YOUR_KEY__",
};
const baseUrl = "https://pinnacle-odds-api.hgapi.top";
const sinceItems = {};
const allEvents = {};
async function liveUpdate() {
for (const sportId of [1]) { // endpoint /kit/v1/sports
// is_have_odds = 0 or 1, event_type = prematch, live
const params = new URLSearchParams({
sport_id: sportId,
is_have_odds: true,
...(sinceItems[sportId] && { since: sinceItems[sportId] })
});
console.log('[REQUEST]', Object.fromEntries(params));
const response = await fetch(`${baseUrl}/kit/v1/markets?${params}`, {
headers
});
if (!response.ok) {
throw new Error(`${response.status}: ${await response.text()}`);
}
const result = await response.json();
sinceItems[sportId] = result.last;
for (const event of result.events) {
allEvents[event.event_id] = event;
try {
console.log(` ${event.league_name}: ${event.home} — ${event.away}`,
event.periods?.num_0?.money_line || '');
} catch (e) {
// Skip if data not available
}
}
console.log('Sport:', result.sport_name);
console.log('Number of changes:', result.events.length);
console.log(' ');
}
}
// Run polling loop
(async () => {
while (true) {
await liveUpdate();
await new Promise(resolve => setTimeout(resolve, 3000));
}
})();Live Update (RapidAPI)
javascript
const headers = {
"x-rapidapi-key": "__YOUR_KEY__",
"x-rapidapi-host": "pinbook-odds.p.rapidapi.com"
};
const baseUrl = "https://pinbook-odds.p.rapidapi.com";
const sinceItems = {};
const allEvents = {};
async function liveUpdate() {
for (const sportId of [1]) {
const params = new URLSearchParams({
sport_id: sportId,
is_have_odds: true,
...(sinceItems[sportId] && { since: sinceItems[sportId] })
});
console.log('[REQUEST]', Object.fromEntries(params));
const response = await fetch(`${baseUrl}/kit/v1/markets?${params}`, {
headers
});
if (!response.ok) {
throw new Error(`${response.status}: ${await response.text()}`);
}
const result = await response.json();
sinceItems[sportId] = result.last;
for (const event of result.events) {
allEvents[event.event_id] = event;
}
}
}
// Run polling loop
(async () => {
while (true) {
await liveUpdate();
await new Promise(resolve => setTimeout(resolve, 3000));
}
})();Workflow 1: Complete API Client Class
A reusable class for interacting with the PinBook Odds API.
javascript
/**
* PinBook Odds API Client
*
* Supports both HugeAPI and RapidAPI providers.
*/
class PinBookClient {
/**
* Initialize the client.
* @param {string} apiKey - Your API key
* @param {string} provider - "hugeapi" or "rapidapi"
*/
constructor(apiKey, provider = "hugeapi") {
this.provider = provider;
if (provider === "hugeapi") {
this.baseUrl = "https://pinnacle-odds-api.hgapi.top";
this.headers = {
"x-portal-apikey": apiKey
};
} else { // rapidapi
this.baseUrl = "https://pinbook-odds.p.rapidapi.com";
this.headers = {
"x-rapidapi-key": apiKey,
"x-rapidapi-host": "pinbook-odds.p.rapidapi.com"
};
}
// Store since timestamps for each sport
this._sinceCache = {};
this._specialSinceCache = {};
}
/** Make an API request. */
async _request(endpoint, params = {}) {
const url = new URL(`${this.baseUrl}${endpoint}`);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
const response = await fetch(url, { headers: this.headers });
if (!response.ok) {
throw new Error(`API Error ${response.status}: ${await response.text()}`);
}
return response.json();
}
/** Check Pinnacle server betting status. */
async checkBettingStatus() {
return this._request("/kit/v1/betting-status");
}
/** Get list of all available sports. */
async getSports() {
return this._request("/kit/v1/sports");
}
/** Get period definitions for a sport. */
async getPeriods(sportId) {
return this._request("/kit/v1/meta-periods", { sport_id: sportId });
}
/** Get leagues for a sport. */
async getLeagues(sportId) {
return this._request("/kit/v1/leagues", { sport_id: sportId });
}
/**
* Get markets/odds for a sport.
* @param {number} sportId - Sport ID (required)
* @param {number} since - UTC timestamp for delta updates
* @param {string} leagueIds - Comma-separated league IDs
* @param {string} eventIds - Comma-separated event IDs
* @param {string} eventType - "prematch" or "live"
* @param {boolean} isHaveOdds - Filter events with odds
*/
async getMarkets(sportId, options = {}) {
const params = { sport_id: sportId };
if (options.since !== undefined) params.since = options.since;
if (options.leagueIds) params.league_ids = options.leagueIds;
if (options.eventIds) params.event_ids = options.eventIds;
if (options.eventType) params.event_type = options.eventType;
if (options.isHaveOdds !== undefined) params.is_have_odds = String(options.isHaveOdds).toLowerCase();
const result = await this._request("/kit/v1/markets", params);
// Update since cache
if (result.last !== undefined) {
this._sinceCache[sportId] = result.last;
}
return result;
}
/** Get special markets for a sport. */
async getSpecialMarkets(sportId, options = {}) {
const params = { sport_id: sportId };
if (options.since !== undefined) params.since = options.since;
if (options.eventType) params.event_type = options.eventType;
if (options.isHaveLines !== undefined) params.is_have_lines = String(options.isHaveLines).toLowerCase();
const result = await this._request("/kit/v1/special-markets", params);
if (result.last !== undefined) {
this._specialSinceCache[sportId] = result.last;
}
return result;
}
/** Get detailed event info with historical odds. */
async getEventDetails(eventId) {
return this._request("/kit/v1/details", { event_id: eventId });
}
/**
* Get archived/settled events.
*/
async getArchive(sportId, options = {}) {
const params = {
sport_id: sportId,
page_num: options.pageNum || 1
};
if (options.leagueIds) params.league_ids = options.leagueIds;
if (options.startAfter) params.start_after = options.startAfter;
if (options.startBefore) params.start_before = options.startBefore;
return this._request("/kit/v1/archive", params);
}
/** Get cached since timestamp for a sport. */
getSince(sportId) {
return this._sinceCache[sportId];
}
/** Get cached special since timestamp for a sport. */
getSpecialSince(sportId) {
return this._specialSinceCache[sportId];
}
}
// Usage Example
(async () => {
const client = new PinBookClient("YOUR_API_KEY", "hugeapi");
// Get all sports
const sports = await client.getSports();
console.log(`Available sports: ${sports.length}`);
// Get initial snapshot for Soccer (sport_id=1)
const markets = await client.getMarkets(1, { isHaveOdds: true });
console.log(`Initial events: ${markets.events?.length || 0}`);
// Get delta updates using cached since
await new Promise(resolve => setTimeout(resolve, 5000));
const updates = await client.getMarkets(1, {
since: client.getSince(1),
isHaveOdds: true
});
console.log(`Updated events: ${updates.events?.length || 0}`);
})();Workflow 2: Real-time Odds Monitor
Continuously monitor odds changes for specific leagues.
javascript
/**
* Real-time odds monitoring for specific sports/leagues.
* Tracks odds changes and notifies on significant movements.
*/
class OddsMonitor {
constructor(apiKey, baseUrl) {
this.baseUrl = baseUrl;
this.headers = {
"x-portal-apikey": apiKey
};
// Storage
this.sinceCache = {};
this.eventsStore = {};
this.oddsHistory = {};
}
async _request(endpoint, params) {
const url = new URL(`${this.baseUrl}${endpoint}`);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
const response = await fetch(url, { headers: this.headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response.json();
}
/** Fetch markets with delta updates. */
async fetchMarkets(sportId, leagueIds = null) {
const params = {
sport_id: sportId,
is_have_odds: true,
since: this.sinceCache[sportId]
};
if (leagueIds) {
params.league_ids = leagueIds.join(',');
}
const result = await this._request("/kit/v1/markets", params);
this.sinceCache[sportId] = result.last;
return result;
}
/** Track odds changes for an event. */
trackOddsChanges(event) {
const changes = [];
const eventId = event.event_id;
let currentMl;
try {
currentMl = event.periods.num_0.money_line;
} catch (e) {
return changes;
}
if (this.eventsStore[eventId]) {
const oldEvent = this.eventsStore[eventId];
try {
const oldMl = oldEvent.periods.num_0.money_line;
for (const key of ["home", "draw", "away"]) {
if (currentMl[key] !== undefined && oldMl[key] !== undefined) {
const oldVal = oldMl[key];
const newVal = currentMl[key];
if (oldVal !== newVal) {
changes.push({
field: `money_line.${key}`,
old: oldVal,
new: newVal,
diff: Math.round((newVal - oldVal) * 10000) / 10000
});
}
}
}
} catch (e) {
// Skip
}
}
// Store current state
this.eventsStore[eventId] = event;
// Store in history
if (changes.length > 0) {
if (!this.oddsHistory[eventId]) {
this.oddsHistory[eventId] = [];
}
this.oddsHistory[eventId].push({
timestamp: new Date().toISOString(),
changes
});
}
return changes;
}
/**
* Start monitoring loop.
* @param {number} sportId - Sport to monitor
* @param {number[]} leagueIds - Optional list of league IDs to filter
* @param {number} interval - Polling interval in seconds
*/
async monitor(sportId, leagueIds = null, interval = 5) {
console.log(`Starting monitor for sport_id=${sportId}`);
console.log(`Leagues: ${leagueIds || 'All'}`);
console.log("-".repeat(50));
while (true) {
try {
const result = await this.fetchMarkets(sportId, leagueIds);
const events = result.events || [];
console.log(`\n[${new Date().toISOString()}]`);
console.log(`Events with changes: ${events.length}`);
for (const event of events) {
const changes = this.trackOddsChanges(event);
if (changes.length > 0) {
console.log(`\n Event: ${event.home} vs ${event.away}`);
console.log(` League: ${event.league_name}`);
for (const change of changes) {
const diffStr = change.diff >= 0 ? `+${change.diff.toFixed(4)}` : change.diff.toFixed(4);
console.log(` ${change.field}: ${change.old} -> ${change.new} (${diffStr})`);
}
}
}
await new Promise(resolve => setTimeout(resolve, interval * 1000));
} catch (error) {
console.error(`Request error: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, interval * 2 * 1000));
}
}
}
/** Get odds change history for an event. */
getEventHistory(eventId) {
return this.oddsHistory[eventId] || [];
}
}
// Usage
(async () => {
const monitor = new OddsMonitor(
"YOUR_API_KEY",
"https://pinnacle-odds-api.hgapi.top"
);
// Monitor Soccer (sport_id=1) for specific leagues
// Bundesliga: 1842, Premier League: 1980
await monitor.monitor(1, [1842, 1980], 5);
})();Workflow 3: Value Bet Finder
Find potential value bets by comparing odds movements.
javascript
/**
* Find potential value bets by analyzing odds movements.
*/
class ValueBetFinder {
constructor(apiKey, baseUrl) {
this.baseUrl = baseUrl;
this.headers = {
"x-portal-apikey": apiKey
};
this.sinceCache = {};
}
async _request(endpoint, params) {
const url = new URL(`${this.baseUrl}${endpoint}`);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
const response = await fetch(url, { headers: this.headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response.json();
}
/** Get all events with odds for a sport. */
async getEventsWithOdds(sportId, eventType = "prematch") {
const params = {
sport_id: sportId,
is_have_odds: true,
event_type: eventType
};
const result = await this._request("/kit/v1/markets", params);
this.sinceCache[sportId] = result.last;
return result.events || [];
}
/** Convert decimal odds to implied probability. */
calculateImpliedProbability(odds) {
return odds > 0 ? 1 / odds : 0;
}
/** Calculate bookmaker margin from money line odds. */
calculateMargin(moneyLine) {
const totalProb = Object.values(moneyLine)
.reduce((sum, odds) => sum + this.calculateImpliedProbability(odds), 0);
return totalProb - 1; // Margin as percentage over 100%
}
/**
* Find events with low bookmaker margin (good value).
* @param {number} sportId - Sport ID
* @param {number} maxMargin - Maximum acceptable margin (e.g., 0.02 = 2%)
* @param {string} eventType - "prematch" or "live"
*/
async findLowMarginEvents(sportId, maxMargin = 0.04, eventType = "prematch") {
const events = await this.getEventsWithOdds(sportId, eventType);
const lowMarginEvents = [];
for (const event of events) {
try {
const moneyLine = event.periods?.num_0?.money_line;
if (!moneyLine) continue;
const margin = this.calculateMargin(moneyLine);
if (margin <= maxMargin) {
const impliedProbs = {};
for (const [key, value] of Object.entries(moneyLine)) {
impliedProbs[key] = Math.round(this.calculateImpliedProbability(value) * 10000) / 100;
}
lowMarginEvents.push({
eventId: event.event_id,
home: event.home,
away: event.away,
league: event.league_name,
starts: event.starts,
moneyLine,
margin: Math.round(margin * 10000) / 100,
impliedProbs
});
}
} catch (e) {
continue;
}
}
return lowMarginEvents;
}
/**
* Find events with significantly dropping odds.
*/
async findDroppingOdds(sportId, threshold = 0.05, eventType = "prematch") {
// First get current snapshot
const events = await this.getEventsWithOdds(sportId, eventType);
const droppingOdds = [];
for (const event of events) {
const eventId = event.event_id;
// Get historical data
try {
const details = await this._request("/kit/v1/details", { event_id: eventId });
const eventsDetails = details.events || [];
if (eventsDetails.length > 0) {
const eventDetail = eventsDetails[0];
const periods = eventDetail.periods || [];
for (const period of periods) {
const history = period.history || {};
const moneyLineHistory = history.home || [];
if (moneyLineHistory.length >= 2) {
// history format: [timestamp, price, max_bet]
const current = moneyLineHistory[moneyLineHistory.length - 1];
const previous = moneyLineHistory[0];
if (current.length >= 2 && previous.length >= 2) {
const currentPrice = current[1];
const previousPrice = previous[1];
// Price drop means odds increased (more payout)
const drop = (currentPrice - previousPrice) / previousPrice;
if (drop >= threshold) {
droppingOdds.push({
eventId,
home: event.home,
away: event.away,
league: event.league_name,
previousPrice,
currentPrice,
dropPercent: Math.round(drop * 10000) / 100
});
}
}
}
}
}
} catch (e) {
continue;
}
}
return droppingOdds;
}
/** Analyze spread markets for value opportunities. */
async analyzeSpreads(sportId, eventType = "prematch") {
const events = await this.getEventsWithOdds(sportId, eventType);
const spreadAnalysis = [];
for (const event of events) {
try {
const spreads = event.periods?.num_0?.spreads;
if (!spreads) continue;
for (const [hdpStr, spreadData] of Object.entries(spreads)) {
const handicap = spreadData.hdp;
const homeOdds = spreadData.home;
const awayOdds = spreadData.away;
const maxBet = spreadData.max;
// Calculate fair odds (without margin)
const homeProb = this.calculateImpliedProbability(homeOdds);
const awayProb = this.calculateImpliedProbability(awayOdds);
const totalProb = homeProb + awayProb;
spreadAnalysis.push({
eventId: event.event_id,
home: event.home,
away: event.away,
league: event.league_name,
handicap,
homeOdds,
awayOdds,
maxBet,
margin: Math.round((totalProb - 1) * 10000) / 100
});
}
} catch (e) {
continue;
}
}
return spreadAnalysis;
}
}
// Usage Example
(async () => {
const finder = new ValueBetFinder(
"YOUR_API_KEY",
"https://pinnacle-odds-api.hgapi.top"
);
// Find low margin events (Soccer)
console.log("Finding low margin events...");
const lowMargin = await finder.findLowMarginEvents(1, 0.04);
console.log(`\nFound ${lowMargin.length} events with margin <= 4%:`);
for (const event of lowMargin.slice(0, 10)) {
console.log(`\n ${event.home} vs ${event.away}`);
console.log(` League: ${event.league}`);
console.log(` Margin: ${event.margin}%`);
console.log(` Money Line: ${JSON.stringify(event.moneyLine)}`);
}
// Analyze spreads
console.log("\n" + "=".repeat(50));
console.log("Analyzing spreads...");
let spreads = await finder.analyzeSpreads(1);
// Sort by margin (lowest first)
spreads.sort((a, b) => a.margin - b.margin);
console.log("\nTop 5 lowest margin spreads:");
for (const spread of spreads.slice(0, 5)) {
console.log(`\n ${spread.home} vs ${spread.away}`);
console.log(` Handicap: ${spread.handicap}`);
console.log(` Home: ${spread.homeOdds} | Away: ${spread.awayOdds}`);
console.log(` Margin: ${spread.margin}% | Max: $${spread.maxBet}`);
}
})();Workflow 4: Event Scheduler & Reminder
Track upcoming events and get reminders.
javascript
/**
* Track upcoming events and provide scheduling functionality.
*/
class EventScheduler {
constructor(apiKey, baseUrl) {
this.baseUrl = baseUrl;
this.headers = {
"x-portal-apikey": apiKey
};
this.monitoredEvents = {};
}
async _request(endpoint, params) {
const url = new URL(`${this.baseUrl}${endpoint}`);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
const response = await fetch(url, { headers: this.headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response.json();
}
/**
* Get events starting within specified hours.
* @param {number} sportId - Sport ID
* @param {number} hoursAhead - How many hours ahead to look
* @param {number[]} leagueIds - Optional list of league IDs to filter
*/
async getUpcomingEvents(sportId, hoursAhead = 24, leagueIds = null) {
const params = {
sport_id: sportId,
is_have_odds: true,
event_type: "prematch"
};
if (leagueIds) {
params.league_ids = leagueIds.join(',');
}
const result = await this._request("/kit/v1/markets", params);
const events = result.events || [];
const now = new Date();
const cutoff = new Date(now.getTime() + hoursAhead * 60 * 60 * 1000);
const upcoming = [];
for (const event of events) {
try {
const starts = new Date(event.starts);
if (now <= starts && starts <= cutoff) {
event.timeUntil = starts - now;
event.startsLocal = starts;
upcoming.push(event);
}
} catch (e) {
continue;
}
}
// Sort by start time
upcoming.sort((a, b) => a.startsLocal - b.startsLocal);
return upcoming;
}
/** Get all events starting today. */
async getTodaysEvents(sportId, leagueIds = null) {
return this.getUpcomingEvents(sportId, 24, leagueIds);
}
/** Get events starting in the next hour. */
async getEventsNextHour(sportId) {
return this.getUpcomingEvents(sportId, 1);
}
/** Add an event to the watchlist. */
addToWatchlist(eventId, reminderMinutes = 30, notes = "") {
this.monitoredEvents[eventId] = {
reminderMinutes,
notes,
addedAt: new Date().toISOString()
};
}
/** Check if any monitored events need reminders. */
async checkReminders() {
const reminders = [];
const now = new Date();
for (const [eventId, config] of Object.entries(this.monitoredEvents)) {
try {
// Get event details
const details = await this._request("/kit/v1/details", { event_id: eventId });
const events = details.events || [];
if (events.length > 0) {
const event = events[0];
const starts = new Date(event.starts);
const timeUntil = starts - now;
const reminderMs = config.reminderMinutes * 60 * 1000;
if (timeUntil <= reminderMs && timeUntil > 0) {
reminders.push({
eventId,
home: event.home,
away: event.away,
league: event.league_name,
starts: event.starts,
minutesUntil: Math.floor(timeUntil / 60000),
notes: config.notes
});
}
}
} catch (e) {
continue;
}
}
return reminders;
}
/** Print a formatted daily schedule. */
async printDailySchedule(sportId, leagueIds = null) {
const events = await this.getTodaysEvents(sportId, leagueIds);
console.log("\n" + "=".repeat(60));
console.log(`DAILY SCHEDULE - ${new Date().toISOString().split('T')[0]}`);
console.log("=".repeat(60));
if (events.length === 0) {
console.log("No events scheduled for today.");
return;
}
let currentHour = null;
for (const event of events) {
const hour = event.startsLocal.toTimeString().slice(0, 2) + ":00";
if (hour !== currentHour) {
currentHour = hour;
console.log(`\n[${hour}]`);
}
const timeStr = event.startsLocal.toTimeString().slice(0, 5);
const timeUntil = this._formatDuration(event.timeUntil);
console.log(` ${timeStr} | ${event.home} vs ${event.away}`);
console.log(` | League: ${event.league_name}`);
console.log(` | In: ${timeUntil}`);
// Show money line if available
try {
const ml = event.periods.num_0.money_line;
console.log(` | Odds: H:${ml.home} D:${ml.draw} A:${ml.away}`);
} catch (e) {
// Skip
}
}
console.log("\n" + "=".repeat(60));
console.log(`Total events: ${events.length}`);
}
_formatDuration(ms) {
const hours = Math.floor(ms / 3600000);
const minutes = Math.floor((ms % 3600000) / 60000);
return `${hours}h ${minutes}m`;
}
/** Run the scheduler loop. */
async runScheduler(sportId, checkInterval = 60) {
console.log("Starting event scheduler...");
console.log("Press Ctrl+C to stop.\n");
const intervalId = setInterval(async () => {
try {
// Check reminders
const reminders = await this.checkReminders();
for (const reminder of reminders) {
console.log("\n" + "!".repeat(40));
console.log(`REMINDER: Event starting in ${reminder.minutesUntil} minutes!`);
console.log(` ${reminder.home} vs ${reminder.away}`);
console.log(` League: ${reminder.league}`);
if (reminder.notes) {
console.log(` Notes: ${reminder.notes}`);
}
console.log("!".repeat(40) + "\n");
}
// Show events in next hour
const nextHour = await this.getEventsNextHour(sportId);
if (nextHour.length > 0) {
const time = new Date().toTimeString().slice(0, 8);
console.log(`[${time}] Events in next hour: ${nextHour.length}`);
}
} catch (e) {
console.error("Scheduler error:", e.message);
}
}, checkInterval * 1000);
// Handle graceful shutdown
process.on('SIGINT', () => {
clearInterval(intervalId);
console.log("\nScheduler stopped.");
process.exit(0);
});
}
}
// Usage Example
(async () => {
const scheduler = new EventScheduler(
"YOUR_API_KEY",
"https://pinnacle-odds-api.hgapi.top"
);
// Print today's schedule
await scheduler.printDailySchedule(1);
// Add events to watchlist
const upcoming = await scheduler.getEventsNextHour(1);
for (const event of upcoming.slice(0, 3)) {
scheduler.addToWatchlist(
event.event_id,
15,
`Important match: ${event.home} vs ${event.away}`
);
}
// Run scheduler
await scheduler.runScheduler(1, 60);
})();Workflow 5: Multi-Sport Dashboard
Build a dashboard showing data across multiple sports.
javascript
/**
* Dashboard for monitoring multiple sports simultaneously.
*/
class MultiSportDashboard {
// Common sport IDs
static SPORTS = {
1: "Soccer",
2: "Tennis",
3: "Basketball",
4: "Ice Hockey",
5: "American Football",
6: "Baseball",
7: "MMA",
8: "Handball",
9: "Volleyball",
10: "Cricket"
};
constructor(apiKey, baseUrl) {
this.baseUrl = baseUrl;
this.headers = {
"x-portal-apikey": apiKey
};
this.sinceCache = {};
}
async _request(endpoint, params = null) {
const url = new URL(`${this.baseUrl}${endpoint}`);
if (params) {
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
}
const response = await fetch(url, { headers: this.headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response.json();
}
/** Get list of all available sports. */
async getAllSports() {
return this._request("/kit/v1/sports");
}
/** Get summary statistics for a sport. */
async getSportSummary(sportId) {
const params = {
sport_id: sportId,
is_have_odds: true
};
const result = await this._request("/kit/v1/markets", params);
this.sinceCache[sportId] = result.last;
const events = result.events || [];
const summary = {
sportId,
sportName: result.sport_name || "Unknown",
totalEvents: events.length,
prematchEvents: 0,
liveEvents: 0,
openMarkets: 0,
leagues: new Set(),
nextEvent: null
};
const now = new Date();
for (const event of events) {
// Count by type
if (event.event_type === "prematch") {
summary.prematchEvents++;
} else if (event.event_type === "live") {
summary.liveEvents++;
}
// Count open markets
if (event.is_have_open_markets) {
summary.openMarkets++;
}
// Track leagues
if (event.league_name) {
summary.leagues.add(event.league_name);
}
// Find next event
try {
const starts = new Date(event.starts);
if (starts > now) {
if (!summary.nextEvent || starts < summary.nextEvent.starts) {
summary.nextEvent = {
home: event.home,
away: event.away,
league: event.league_name,
starts
};
}
}
} catch (e) {
// Skip
}
}
summary.leagues = summary.leagues.size;
return summary;
}
/** Get summary for all active sports. */
async getAllSportsSummary() {
const summaries = [];
for (const sportId of Object.keys(MultiSportDashboard.SPORTS)) {
try {
const summary = await this.getSportSummary(parseInt(sportId));
if (summary.totalEvents > 0) {
summaries.push(summary);
}
await new Promise(resolve => setTimeout(resolve, 300)); // Rate limiting
} catch (e) {
continue;
}
}
return summaries;
}
/** Print a formatted dashboard. */
async printDashboard() {
console.log("\n" + "=".repeat(70));
console.log(`PINBOOK ODDS DASHBOARD - ${new Date().toISOString()}`);
console.log("=".repeat(70));
const summaries = await this.getAllSportsSummary();
// Header
console.log(`\n${"Sport".padEnd(20)} ${"Events".padStart(8)} ${"Prematch".padStart(10)} ${"Live".padStart(6)} ${"Leagues".padStart(10)}`);
console.log("-".repeat(70));
let totalEvents = 0;
let totalPrematch = 0;
let totalLive = 0;
for (const summary of summaries) {
console.log(`${summary.sportName.padEnd(20)} ` +
`${String(summary.totalEvents).padStart(8)} ` +
`${String(summary.prematchEvents).padStart(10)} ` +
`${String(summary.liveEvents).padStart(6)} ` +
`${String(summary.leagues).padStart(10)}`);
totalEvents += summary.totalEvents;
totalPrematch += summary.prematchEvents;
totalLive += summary.liveEvents;
}
console.log("-".repeat(70));
console.log(`${"TOTAL".padEnd(20)} ${String(totalEvents).padStart(8)} ${String(totalPrematch).padStart(10)} ${String(totalLive).padStart(6)}`);
// Next events
console.log("\n" + "-".repeat(70));
console.log("UPCOMING EVENTS:");
console.log("-".repeat(70));
const upcoming = [];
for (const summary of summaries) {
if (summary.nextEvent) {
upcoming.push({
sport: summary.sportName,
...summary.nextEvent
});
}
}
upcoming.sort((a, b) => a.starts - b.starts);
for (const event of upcoming.slice(0, 5)) {
const timeUntil = event.starts - new Date();
const minutes = Math.floor(timeUntil / 60000);
console.log(`\n [${event.sport}] ${event.home} vs ${event.away}`);
console.log(` League: ${event.league}`);
console.log(` Starts in: ${minutes} minutes`);
}
console.log("\n" + "=".repeat(70));
}
/** Run live updating dashboard. */
async runLiveDashboard(refreshInterval = 60) {
const intervalId = setInterval(async () => {
try {
// Clear screen (optional - uncomment if desired)
// console.clear();
await this.printDashboard();
console.log(`\nRefreshing in ${refreshInterval} seconds... (Ctrl+C to stop)`);
} catch (e) {
console.error("Dashboard error:", e.message);
}
}, refreshInterval * 1000);
// Initial display
await this.printDashboard();
// Handle graceful shutdown
process.on('SIGINT', () => {
clearInterval(intervalId);
console.log("\n\nDashboard stopped.");
process.exit(0);
});
}
/** Get all currently live events across sports. */
async getLiveEvents() {
const liveEvents = [];
for (const sportId of Object.keys(MultiSportDashboard.SPORTS)) {
try {
const params = {
sport_id: sportId,
event_type: "live",
is_have_odds: true
};
const result = await this._request("/kit/v1/markets", params);
const events = result.events || [];
for (const event of events) {
liveEvents.push({
sport: result.sport_name,
home: event.home,
away: event.away,
league: event.league_name,
periods: event.periods || {}
});
}
await new Promise(resolve => setTimeout(resolve, 300));
} catch (e) {
continue;
}
}
return liveEvents;
}
}
// Usage Example
(async () => {
const dashboard = new MultiSportDashboard(
"YOUR_API_KEY",
"https://pinnacle-odds-api.hgapi.top"
);
// Print one-time dashboard
await dashboard.printDashboard();
// Or run live dashboard
// await dashboard.runLiveDashboard(60);
})();