@gotza02/sequential-thinking 10000.2.0 → 10000.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/sports/core/base.d.ts +3 -2
- package/dist/tools/sports/core/base.js +12 -10
- package/dist/tools/sports/core/cache.d.ts +9 -0
- package/dist/tools/sports/core/cache.js +25 -3
- package/dist/tools/sports/core/types.d.ts +6 -2
- package/dist/tools/sports/providers/api.d.ts +4 -0
- package/dist/tools/sports/providers/api.js +110 -27
- package/dist/tools/sports/tools/betting.js +16 -16
- package/dist/tools/sports/tools/league.d.ts +2 -7
- package/dist/tools/sports/tools/league.js +198 -8
- package/dist/tools/sports/tools/live.js +80 -38
- package/dist/tools/sports/tools/match.js +239 -34
- package/package.json +1 -1
- package/CLAUDE.md +0 -231
|
@@ -73,7 +73,8 @@ export declare abstract class APIProviderBase implements IDataProvider {
|
|
|
73
73
|
protected rateLimitUntil: Date | null;
|
|
74
74
|
protected lastCall: Date | null;
|
|
75
75
|
protected callCount: number;
|
|
76
|
-
constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number
|
|
76
|
+
constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number, // calls per minute
|
|
77
|
+
cache?: CacheService);
|
|
77
78
|
/**
|
|
78
79
|
* Check if provider has valid credentials
|
|
79
80
|
*/
|
|
@@ -131,7 +132,7 @@ export declare abstract class APIProviderBase implements IDataProvider {
|
|
|
131
132
|
export declare abstract class ScraperProviderBase implements IDataProvider {
|
|
132
133
|
protected cache: CacheService;
|
|
133
134
|
readonly type: ProviderType;
|
|
134
|
-
constructor();
|
|
135
|
+
constructor(cache?: CacheService);
|
|
135
136
|
isAvailable(): boolean;
|
|
136
137
|
getStatus(): ProviderStatus;
|
|
137
138
|
abstract getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SPORTS MODULE BASE CLASSES
|
|
3
3
|
* Abstract base classes and interfaces for data providers
|
|
4
4
|
*/
|
|
5
|
-
import { CacheService } from './cache.js';
|
|
5
|
+
import { CacheService, getGlobalCache } from './cache.js';
|
|
6
6
|
import { logger } from '../../../utils.js';
|
|
7
7
|
/**
|
|
8
8
|
* Abstract base class for API-based providers
|
|
@@ -16,13 +16,13 @@ export class APIProviderBase {
|
|
|
16
16
|
rateLimitUntil = null;
|
|
17
17
|
lastCall = null;
|
|
18
18
|
callCount = 0;
|
|
19
|
-
constructor(type, apiKey, baseUrl, rateLimit = 10 // calls per minute
|
|
20
|
-
) {
|
|
19
|
+
constructor(type, apiKey, baseUrl, rateLimit = 10, // calls per minute
|
|
20
|
+
cache) {
|
|
21
21
|
this.type = type;
|
|
22
22
|
this.apiKey = apiKey;
|
|
23
23
|
this.baseUrl = baseUrl;
|
|
24
24
|
this.rateLimit = rateLimit;
|
|
25
|
-
this.cache =
|
|
25
|
+
this.cache = cache || getGlobalCache();
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Check if provider has valid credentials
|
|
@@ -86,7 +86,9 @@ export class APIProviderBase {
|
|
|
86
86
|
// Handle rate limiting
|
|
87
87
|
if (response.status === 429) {
|
|
88
88
|
const retryAfter = response.headers.get('Retry-After');
|
|
89
|
-
|
|
89
|
+
const retrySeconds = retryAfter ? parseInt(retryAfter, 10) : NaN;
|
|
90
|
+
const delayMs = isNaN(retrySeconds) ? 60000 : retrySeconds * 1000;
|
|
91
|
+
this.rateLimitUntil = new Date(Date.now() + delayMs);
|
|
90
92
|
return {
|
|
91
93
|
success: false,
|
|
92
94
|
error: 'Rate limited',
|
|
@@ -127,8 +129,8 @@ export class APIProviderBase {
|
|
|
127
129
|
export class ScraperProviderBase {
|
|
128
130
|
cache;
|
|
129
131
|
type = 'scraper';
|
|
130
|
-
constructor() {
|
|
131
|
-
this.cache =
|
|
132
|
+
constructor(cache) {
|
|
133
|
+
this.cache = cache || getGlobalCache();
|
|
132
134
|
}
|
|
133
135
|
isAvailable() {
|
|
134
136
|
return true; // Scraping is always "available"
|
|
@@ -181,8 +183,8 @@ export class ScraperProviderBase {
|
|
|
181
183
|
export class FallbackProvider {
|
|
182
184
|
providers;
|
|
183
185
|
cache;
|
|
184
|
-
type = '
|
|
185
|
-
constructor(providers, cache =
|
|
186
|
+
type = 'api-football'; // Most common API type
|
|
187
|
+
constructor(providers, cache = getGlobalCache()) {
|
|
186
188
|
this.providers = providers;
|
|
187
189
|
this.cache = cache;
|
|
188
190
|
}
|
|
@@ -275,7 +277,7 @@ export class FallbackProvider {
|
|
|
275
277
|
export class SportsToolBase {
|
|
276
278
|
cache;
|
|
277
279
|
constructor(cache) {
|
|
278
|
-
this.cache = cache ||
|
|
280
|
+
this.cache = cache || getGlobalCache();
|
|
279
281
|
}
|
|
280
282
|
/**
|
|
281
283
|
* Format error message for user
|
|
@@ -12,7 +12,12 @@ export declare class CacheService {
|
|
|
12
12
|
private maxSize;
|
|
13
13
|
private savePending;
|
|
14
14
|
private saveTimer;
|
|
15
|
+
cleanupInterval: NodeJS.Timeout | null;
|
|
15
16
|
constructor(cachePath?: string, maxAge?: number, maxSize?: number);
|
|
17
|
+
/**
|
|
18
|
+
* Initialize cache - must be called after construction
|
|
19
|
+
*/
|
|
20
|
+
initialize(): Promise<void>;
|
|
16
21
|
/**
|
|
17
22
|
* Generate a cache key from components
|
|
18
23
|
*/
|
|
@@ -37,6 +42,10 @@ export declare class CacheService {
|
|
|
37
42
|
* Clear all cache entries
|
|
38
43
|
*/
|
|
39
44
|
clear(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Stop cleanup interval and clear all resources
|
|
47
|
+
*/
|
|
48
|
+
dispose(): void;
|
|
40
49
|
/**
|
|
41
50
|
* Get cache statistics
|
|
42
51
|
*/
|
|
@@ -16,11 +16,17 @@ export class CacheService {
|
|
|
16
16
|
maxSize;
|
|
17
17
|
savePending = false;
|
|
18
18
|
saveTimer = null;
|
|
19
|
+
cleanupInterval = null;
|
|
19
20
|
constructor(cachePath = CACHE_CONFIG.CACHE_PATH, maxAge = CACHE_CONFIG.DEFAULT_TTL, maxSize = CACHE_CONFIG.MAX_SIZE) {
|
|
20
21
|
this.cachePath = path.resolve(cachePath);
|
|
21
22
|
this.maxAge = maxAge;
|
|
22
23
|
this.maxSize = maxSize;
|
|
23
|
-
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Initialize cache - must be called after construction
|
|
27
|
+
*/
|
|
28
|
+
async initialize() {
|
|
29
|
+
await this.loadFromFile();
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* Generate a cache key from components
|
|
@@ -95,6 +101,20 @@ export class CacheService {
|
|
|
95
101
|
logger.info('Cache cleared');
|
|
96
102
|
this.scheduleSave();
|
|
97
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Stop cleanup interval and clear all resources
|
|
106
|
+
*/
|
|
107
|
+
dispose() {
|
|
108
|
+
if (this.cleanupInterval) {
|
|
109
|
+
clearInterval(this.cleanupInterval);
|
|
110
|
+
this.cleanupInterval = null;
|
|
111
|
+
}
|
|
112
|
+
if (this.saveTimer) {
|
|
113
|
+
clearTimeout(this.saveTimer);
|
|
114
|
+
this.saveTimer = null;
|
|
115
|
+
}
|
|
116
|
+
this.cache.clear();
|
|
117
|
+
}
|
|
98
118
|
/**
|
|
99
119
|
* Get cache statistics
|
|
100
120
|
*/
|
|
@@ -288,8 +308,10 @@ let globalCache = null;
|
|
|
288
308
|
export function getGlobalCache() {
|
|
289
309
|
if (!globalCache) {
|
|
290
310
|
globalCache = new CacheService();
|
|
311
|
+
// Initialize async (fire and forget - will be ready on next tick)
|
|
312
|
+
globalCache.initialize().catch(err => logger.error('Cache init failed:', err));
|
|
291
313
|
// Cleanup expired entries every 5 minutes
|
|
292
|
-
setInterval(() => {
|
|
314
|
+
globalCache.cleanupInterval = setInterval(() => {
|
|
293
315
|
globalCache?.cleanup();
|
|
294
316
|
}, 5 * 60 * 1000);
|
|
295
317
|
}
|
|
@@ -300,7 +322,7 @@ export function getGlobalCache() {
|
|
|
300
322
|
*/
|
|
301
323
|
export function resetGlobalCache() {
|
|
302
324
|
if (globalCache) {
|
|
303
|
-
globalCache.
|
|
325
|
+
globalCache.dispose();
|
|
304
326
|
}
|
|
305
327
|
globalCache = null;
|
|
306
328
|
}
|
|
@@ -138,7 +138,9 @@ export interface TableEntry {
|
|
|
138
138
|
drawn: number;
|
|
139
139
|
lost: number;
|
|
140
140
|
goalsFor: number;
|
|
141
|
-
|
|
141
|
+
goalsAgainst: number;
|
|
142
|
+
/** @deprecated Use goalsAgainst instead */
|
|
143
|
+
against?: number;
|
|
142
144
|
};
|
|
143
145
|
awayRecord?: {
|
|
144
146
|
played: number;
|
|
@@ -146,7 +148,9 @@ export interface TableEntry {
|
|
|
146
148
|
drawn: number;
|
|
147
149
|
lost: number;
|
|
148
150
|
goalsFor: number;
|
|
149
|
-
|
|
151
|
+
goalsAgainst: number;
|
|
152
|
+
/** @deprecated Use goalsAgainst instead */
|
|
153
|
+
against?: number;
|
|
150
154
|
};
|
|
151
155
|
lastFive?: {
|
|
152
156
|
result: 'W' | 'D' | 'L';
|
|
@@ -61,6 +61,10 @@ export declare class FootballDataProvider extends APIProviderBase {
|
|
|
61
61
|
export declare class SportsDBProvider extends APIProviderBase {
|
|
62
62
|
constructor();
|
|
63
63
|
protected getAuthHeaders(): Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Override callAPI to inject API key in URL for TheSportsDB
|
|
66
|
+
*/
|
|
67
|
+
protected callAPI<T>(endpoint: string, options?: RequestInit): Promise<APIResponse<T>>;
|
|
64
68
|
protected transformResponse<T>(data: any): T;
|
|
65
69
|
getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
66
70
|
getLiveMatches(): Promise<APIResponse<Match[]>>;
|
|
@@ -65,7 +65,8 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
65
65
|
return response;
|
|
66
66
|
}
|
|
67
67
|
async getPlayer(playerId) {
|
|
68
|
-
const
|
|
68
|
+
const currentYear = new Date().getFullYear();
|
|
69
|
+
const response = await this.callAPI(`/players?id=${playerId}&season=${currentYear}`);
|
|
69
70
|
if (response.success && response.data?.response?.[0]) {
|
|
70
71
|
const player = this.transformPlayer(response.data.response[0]);
|
|
71
72
|
return { success: true, data: player, provider: 'api-football' };
|
|
@@ -81,7 +82,8 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
81
82
|
return response;
|
|
82
83
|
}
|
|
83
84
|
async searchPlayers(query) {
|
|
84
|
-
const
|
|
85
|
+
const currentYear = new Date().getFullYear();
|
|
86
|
+
const response = await this.callAPI(`/players?search=${encodeURIComponent(query)}&season=${currentYear}`);
|
|
85
87
|
if (response.success && response.data?.response) {
|
|
86
88
|
const players = response.data.response.map((p) => this.transformPlayer(p));
|
|
87
89
|
return { success: true, data: players, provider: 'api-football' };
|
|
@@ -156,11 +158,11 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
156
158
|
status: this.mapStatus(data.fixture.status.long),
|
|
157
159
|
venue: data.fixture.venue?.name,
|
|
158
160
|
score: data.goals ? {
|
|
159
|
-
home: data.goals.home,
|
|
160
|
-
away: data.goals.away,
|
|
161
|
-
halfTime: data.score
|
|
162
|
-
home: data.score.halftime.home,
|
|
163
|
-
away: data.score.halftime.away,
|
|
161
|
+
home: data.goals.home ?? 0,
|
|
162
|
+
away: data.goals.away ?? 0,
|
|
163
|
+
halfTime: data.score?.halftime ? {
|
|
164
|
+
home: data.score.halftime.home ?? 0,
|
|
165
|
+
away: data.score.halftime.away ?? 0,
|
|
164
166
|
} : undefined,
|
|
165
167
|
} : undefined,
|
|
166
168
|
minute: data.fixture.status?.elapsed,
|
|
@@ -208,7 +210,7 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
208
210
|
drawn: data.home.draw,
|
|
209
211
|
lost: data.home.lose,
|
|
210
212
|
goalsFor: data.home.goals.for,
|
|
211
|
-
|
|
213
|
+
goalsAgainst: data.home.goals.against,
|
|
212
214
|
} : undefined,
|
|
213
215
|
awayRecord: data.away ? {
|
|
214
216
|
played: data.away.played,
|
|
@@ -216,7 +218,7 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
216
218
|
drawn: data.away.draw,
|
|
217
219
|
lost: data.away.lose,
|
|
218
220
|
goalsFor: data.away.goals.for,
|
|
219
|
-
|
|
221
|
+
goalsAgainst: data.away.goals.against,
|
|
220
222
|
} : undefined,
|
|
221
223
|
};
|
|
222
224
|
}
|
|
@@ -230,6 +232,8 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
230
232
|
return 'postponed';
|
|
231
233
|
if (s.includes('cancelled'))
|
|
232
234
|
return 'cancelled';
|
|
235
|
+
if (s.includes('abandoned'))
|
|
236
|
+
return 'abandoned';
|
|
233
237
|
return 'scheduled';
|
|
234
238
|
}
|
|
235
239
|
mapPosition(pos) {
|
|
@@ -276,9 +280,17 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
276
280
|
homeGoals += isHomeTeam ? match.score.home : match.score.away;
|
|
277
281
|
awayGoals += isHomeTeam ? match.score.away : match.score.home;
|
|
278
282
|
}
|
|
283
|
+
// Determine teams - use first match data if available
|
|
284
|
+
const firstMatch = matches[0];
|
|
285
|
+
const homeTeamObj = firstMatch
|
|
286
|
+
? (firstMatch.homeTeam.id === homeId ? firstMatch.homeTeam : firstMatch.awayTeam)
|
|
287
|
+
: { id: homeId, name: 'Unknown', country: '' };
|
|
288
|
+
const awayTeamObj = firstMatch
|
|
289
|
+
? (firstMatch.homeTeam.id === awayId ? firstMatch.homeTeam : firstMatch.awayTeam)
|
|
290
|
+
: { id: awayId, name: 'Unknown', country: '' };
|
|
279
291
|
return {
|
|
280
|
-
homeTeam:
|
|
281
|
-
awayTeam:
|
|
292
|
+
homeTeam: homeTeamObj,
|
|
293
|
+
awayTeam: awayTeamObj,
|
|
282
294
|
totalMatches: matches.length,
|
|
283
295
|
homeWins,
|
|
284
296
|
draws,
|
|
@@ -306,23 +318,27 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
306
318
|
// Try to find Asian Handicap odds
|
|
307
319
|
const ahBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('asian') ||
|
|
308
320
|
b.name?.toLowerCase().includes('handicap'));
|
|
309
|
-
if (ahBet?.values?.
|
|
310
|
-
const
|
|
321
|
+
if (ahBet?.values?.length >= 2) {
|
|
322
|
+
const homeValue = ahBet.values.find((v) => v.value?.toLowerCase().includes('home') || v.value === 'Home');
|
|
323
|
+
const awayValue = ahBet.values.find((v) => v.value?.toLowerCase().includes('away') || v.value === 'Away');
|
|
324
|
+
const lineMatch = ahBet.values[0].value?.match(/[-+]?\d+(?:\.\d+)?/);
|
|
311
325
|
result.asianHandicap = {
|
|
312
|
-
home: parseFloat(
|
|
313
|
-
away: parseFloat(
|
|
314
|
-
line: parseFloat(
|
|
326
|
+
home: parseFloat(homeValue?.odd) || parseFloat(ahBet.values[0]?.odd) || 2.0,
|
|
327
|
+
away: parseFloat(awayValue?.odd) || parseFloat(ahBet.values[1]?.odd) || 2.0,
|
|
328
|
+
line: lineMatch ? parseFloat(lineMatch[0]) : 0,
|
|
315
329
|
};
|
|
316
330
|
}
|
|
317
331
|
// Try to find Over/Under odds
|
|
318
|
-
const ouBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('over/under')
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
332
|
+
const ouBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('over/under') ||
|
|
333
|
+
b.name?.toLowerCase().includes('goals over/under'));
|
|
334
|
+
if (ouBet?.values?.length >= 2) {
|
|
335
|
+
const overValue = ouBet.values.find((v) => v.value?.toLowerCase().includes('over'));
|
|
336
|
+
const underValue = ouBet.values.find((v) => v.value?.toLowerCase().includes('under'));
|
|
337
|
+
const lineMatch = overValue?.value?.match(/\d+(?:\.\d+)?/);
|
|
322
338
|
result.overUnder = {
|
|
323
|
-
line,
|
|
324
|
-
over: parseFloat(
|
|
325
|
-
under: parseFloat(
|
|
339
|
+
line: lineMatch ? parseFloat(lineMatch[0]) : 2.5,
|
|
340
|
+
over: parseFloat(overValue?.odd) || 2.0,
|
|
341
|
+
under: parseFloat(underValue?.odd) || 2.0,
|
|
326
342
|
};
|
|
327
343
|
}
|
|
328
344
|
return result;
|
|
@@ -353,7 +369,10 @@ export class FootballDataProvider extends APIProviderBase {
|
|
|
353
369
|
return response;
|
|
354
370
|
}
|
|
355
371
|
async getLiveMatches(leagueId) {
|
|
356
|
-
const
|
|
372
|
+
const endpoint = leagueId
|
|
373
|
+
? `/competitions/${leagueId}/matches?status=IN_PLAY`
|
|
374
|
+
: '/matches?status=IN_PLAY';
|
|
375
|
+
const response = await this.callAPI(endpoint);
|
|
357
376
|
if (response.success && response.data?.matches) {
|
|
358
377
|
const matches = response.data.matches.map((m) => this.transformMatchFD(m));
|
|
359
378
|
return { success: true, data: matches, provider: 'football-data' };
|
|
@@ -467,7 +486,7 @@ export class FootballDataProvider extends APIProviderBase {
|
|
|
467
486
|
drawn: data.homeGames.draw,
|
|
468
487
|
lost: data.homeGames.lost,
|
|
469
488
|
goalsFor: data.homeGames.goalsFor,
|
|
470
|
-
|
|
489
|
+
goalsAgainst: data.homeGames.goalsAgainst,
|
|
471
490
|
} : undefined,
|
|
472
491
|
awayRecord: data.awayGames ? {
|
|
473
492
|
played: data.awayGames.playedGames,
|
|
@@ -475,7 +494,7 @@ export class FootballDataProvider extends APIProviderBase {
|
|
|
475
494
|
drawn: data.awayGames.draw,
|
|
476
495
|
lost: data.awayGames.lost,
|
|
477
496
|
goalsFor: data.awayGames.goalsFor,
|
|
478
|
-
|
|
497
|
+
goalsAgainst: data.awayGames.goalsAgainst,
|
|
479
498
|
} : undefined,
|
|
480
499
|
};
|
|
481
500
|
}
|
|
@@ -502,7 +521,71 @@ export class SportsDBProvider extends APIProviderBase {
|
|
|
502
521
|
}
|
|
503
522
|
getAuthHeaders() {
|
|
504
523
|
// TheSportsDB uses API key in URL, not headers
|
|
505
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
'Accept': 'application/json',
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Override callAPI to inject API key in URL for TheSportsDB
|
|
530
|
+
*/
|
|
531
|
+
async callAPI(endpoint, options = {}) {
|
|
532
|
+
// Inject API key into URL for TheSportsDB
|
|
533
|
+
const separator = endpoint.includes('?') ? '&' : '?';
|
|
534
|
+
const urlWithKey = `${this.baseUrl}${endpoint}${separator}api_key=${encodeURIComponent(this.apiKey)}`;
|
|
535
|
+
// Check rate limit
|
|
536
|
+
if (this.isRateLimited()) {
|
|
537
|
+
return {
|
|
538
|
+
success: false,
|
|
539
|
+
error: `Rate limited until ${this.rateLimitUntil}`,
|
|
540
|
+
rateLimited: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
// Check availability
|
|
544
|
+
if (!this.isAvailable()) {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
error: 'Provider not available (missing API key)',
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
const response = await fetch(urlWithKey, {
|
|
552
|
+
...options,
|
|
553
|
+
headers: {
|
|
554
|
+
'Accept': 'application/json',
|
|
555
|
+
...options.headers,
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
this.lastCall = new Date();
|
|
559
|
+
this.callCount++;
|
|
560
|
+
if (!response.ok) {
|
|
561
|
+
if (response.status === 429) {
|
|
562
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
563
|
+
this.rateLimitUntil = new Date(Date.now() + (retryAfter ? parseInt(retryAfter) * 1000 : 60000));
|
|
564
|
+
return {
|
|
565
|
+
success: false,
|
|
566
|
+
error: 'Rate limited',
|
|
567
|
+
rateLimited: true,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const data = await response.json();
|
|
576
|
+
return {
|
|
577
|
+
success: true,
|
|
578
|
+
data: data,
|
|
579
|
+
provider: this.type,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
logger.error(`${this.type} API error: ${error}`);
|
|
584
|
+
return {
|
|
585
|
+
success: false,
|
|
586
|
+
error: error instanceof Error ? error.message : String(error),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
506
589
|
}
|
|
507
590
|
transformResponse(data) {
|
|
508
591
|
return data;
|
|
@@ -8,19 +8,6 @@ import { calculateValue, calculateKelly, calculateRecommendedStake, oddsToProbab
|
|
|
8
8
|
import { formatOdds } from '../utils/formatter.js';
|
|
9
9
|
import { BETTING } from '../core/constants.js';
|
|
10
10
|
// ============= Helper Functions =============
|
|
11
|
-
/**
|
|
12
|
-
* Get league ID from league name
|
|
13
|
-
*/
|
|
14
|
-
function getLeagueId(leagueName) {
|
|
15
|
-
const normalized = leagueName.toLowerCase();
|
|
16
|
-
for (const [key, value] of Object.entries({ ...BETTING })) {
|
|
17
|
-
// Skip non-league entries
|
|
18
|
-
if (typeof value !== 'object' || value === null || !('name' in value)) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
11
|
/**
|
|
25
12
|
* Search for odds by match
|
|
26
13
|
*/
|
|
@@ -159,13 +146,26 @@ Shows: Fair odds, market odds, value %, confidence, Kelly stake.`, {
|
|
|
159
146
|
break;
|
|
160
147
|
case 'fractional':
|
|
161
148
|
// Parse fractional odds like "5/2" or "5-2"
|
|
162
|
-
const oddsStr = odds.toString();
|
|
149
|
+
const oddsStr = odds.toString().trim();
|
|
163
150
|
const parts = oddsStr.split(/[-/]/);
|
|
164
151
|
if (parts.length === 2) {
|
|
165
|
-
|
|
152
|
+
const numerator = parseFloat(parts[0]);
|
|
153
|
+
const denominator = parseFloat(parts[1]);
|
|
154
|
+
if (!isNaN(numerator) && !isNaN(denominator) && denominator !== 0) {
|
|
155
|
+
decimalOdds = (numerator / denominator) + 1;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: 'text', text: `Error: Invalid fractional odds format "${odds}". Use format like "5/2" or "3/1".` }],
|
|
160
|
+
isError: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
166
163
|
}
|
|
167
164
|
else {
|
|
168
|
-
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: 'text', text: `Error: Invalid fractional odds format "${odds}". Use format like "5/2" or "3/1".` }],
|
|
167
|
+
isError: true,
|
|
168
|
+
};
|
|
169
169
|
}
|
|
170
170
|
break;
|
|
171
171
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPORTS MODULE TOOLS - League Tools
|
|
3
3
|
* Tools for league standings and statistics
|
|
4
|
-
*
|
|
5
|
-
* This file will contain:
|
|
6
|
-
* - get_league_standings
|
|
7
|
-
* - get_top_scorers
|
|
8
|
-
*
|
|
9
|
-
* Status: Placeholder for Phase 2 implementation
|
|
10
4
|
*/
|
|
11
|
-
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
export declare function registerLeagueTools(server: McpServer): void;
|