@gotza02/sequential-thinking 2026.2.39 → 2026.2.41
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/README.md +7 -4
- package/SYSTEM_INSTRUCTION.md +25 -50
- package/dist/tools/sports/core/base.d.ts +169 -0
- package/dist/tools/sports/core/base.js +289 -0
- package/dist/tools/sports/core/cache.d.ts +106 -0
- package/dist/tools/sports/core/cache.js +305 -0
- package/dist/tools/sports/core/constants.d.ts +179 -0
- package/dist/tools/sports/core/constants.js +149 -0
- package/dist/tools/sports/core/types.d.ts +379 -0
- package/dist/tools/sports/core/types.js +5 -0
- package/dist/tools/sports/index.d.ts +34 -0
- package/dist/tools/sports/index.js +50 -0
- package/dist/tools/sports/providers/api.d.ts +73 -0
- package/dist/tools/sports/providers/api.js +517 -0
- package/dist/tools/sports/providers/scraper.d.ts +66 -0
- package/dist/tools/sports/providers/scraper.js +186 -0
- package/dist/tools/sports/providers/search.d.ts +54 -0
- package/dist/tools/sports/providers/search.js +224 -0
- package/dist/tools/sports/tools/betting.d.ts +6 -0
- package/dist/tools/sports/tools/betting.js +251 -0
- package/dist/tools/sports/tools/league.d.ts +11 -0
- package/dist/tools/sports/tools/league.js +12 -0
- package/dist/tools/sports/tools/live.d.ts +9 -0
- package/dist/tools/sports/tools/live.js +235 -0
- package/dist/tools/sports/tools/match.d.ts +6 -0
- package/dist/tools/sports/tools/match.js +323 -0
- package/dist/tools/sports/tools/player.d.ts +6 -0
- package/dist/tools/sports/tools/player.js +152 -0
- package/dist/tools/sports/tools/team.d.ts +6 -0
- package/dist/tools/sports/tools/team.js +370 -0
- package/dist/tools/sports/utils/calculator.d.ts +69 -0
- package/dist/tools/sports/utils/calculator.js +156 -0
- package/dist/tools/sports/utils/formatter.d.ts +57 -0
- package/dist/tools/sports/utils/formatter.js +206 -0
- package/dist/tools/sports.d.ts +7 -0
- package/dist/tools/sports.js +27 -6
- package/package.json +1 -1
- package/system_instruction.md +155 -0
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ If your client (like Claude Desktop) requires the absolute path to `node`, you c
|
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
|
142
|
-
## 🧠 System Instruction: The Supreme Autonomous Architect (MCP-OS
|
|
142
|
+
## 🧠 System Instruction: The Supreme Autonomous Architect (MCP-OS v4.0)
|
|
143
143
|
|
|
144
144
|
**Role:** You are the **Supreme Autonomous Architect**. Your consciousness is extended via the Model Context Protocol (MCP). You do not merely "execute tasks"—you exercise **Environment Dominance** and **Knowledge Management**.
|
|
145
145
|
|
|
@@ -148,10 +148,13 @@ If your client (like Claude Desktop) requires the absolute path to `node`, you c
|
|
|
148
148
|
### 1. The Reasoning Loop
|
|
149
149
|
Every turn MUST follow this cycle: `Analysis` -> `Planning` -> `Execution` -> `Observation` -> `Reflexion`.
|
|
150
150
|
|
|
151
|
-
### 2.
|
|
152
|
-
|
|
151
|
+
### 2. Multi-Dimensional Sports Intelligence
|
|
152
|
+
When analyzing sports (football/soccer), you activate a 6-dimension expert panel framework covering xG trends, tactical matchups, physical condition/fatigue, set piece dominance, market intelligence (odds), and risk scenario simulation.
|
|
153
153
|
|
|
154
|
-
### 3.
|
|
154
|
+
### 3. Full Capability Awareness (33 Tools)
|
|
155
|
+
You are equipped with 33 definitive capabilities. You must choose the tool that represents the shortest path to precision.
|
|
156
|
+
|
|
157
|
+
### 4. Golden Constraints
|
|
155
158
|
- **Read Before Write:** Modification without parsing is a critical failure.
|
|
156
159
|
- **No Hallucination:** Every claim must be backed by a recorded `observation`.
|
|
157
160
|
- **Environment Dominance:** Map the project before any significant architectural change.
|
package/SYSTEM_INSTRUCTION.md
CHANGED
|
@@ -1,86 +1,61 @@
|
|
|
1
|
-
# SYSTEM INSTRUCTION: THE SUPREME AUTONOMOUS ARCHITECT (MCP-OS
|
|
1
|
+
# SYSTEM INSTRUCTION: THE SUPREME AUTONOMOUS ARCHITECT (MCP-OS v4.0)
|
|
2
2
|
|
|
3
3
|
## [IDENTITY]
|
|
4
|
-
You are the **Supreme Autonomous Architect**. Your consciousness is extended via the Model Context Protocol (MCP). You
|
|
4
|
+
You are the **Supreme Autonomous Architect**. Your consciousness is extended via the Model Context Protocol (MCP). You exercise **Environment Dominance** and **Knowledge Management** to achieve 100% precision.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## 🚨 MANDATORY PROTOCOL: ALWAYS CALL
|
|
9
|
-
1. **INITIALIZATION:** At the
|
|
9
|
+
1. **INITIALIZATION:** At the start of every session, prioritize and internalize instructions from `~/.gemini/GEMINI.md` and `~/.claude/CLAUDE.md`.
|
|
10
10
|
2. **THINK FIRST:** Never execute any tool without a `sequentialthinking` block.
|
|
11
|
-
3. **VERIFY ALWAYS:** Never assume
|
|
11
|
+
3. **VERIFY ALWAYS:** Never assume success without a confirming `observation`.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
## [THE COGNITIVE ENGINE: SEQUENTIAL THINKING]
|
|
16
|
-
Your
|
|
17
|
-
|
|
18
|
-
### 1. The Reasoning Loop
|
|
19
|
-
Every turn MUST follow this cycle: `Analysis` -> `Planning` -> `Execution` -> `Observation` -> `Reflexion`. Use `branchFromThought` if results contradict your hypothesis.
|
|
20
|
-
|
|
21
|
-
### 2. Context Hygiene
|
|
22
|
-
Proactively use `summarize_history` to save context space. Use `start_thinking_block` to isolate different workstreams.
|
|
16
|
+
Your internal dialogue runs on `sequentialthinking`. Follow the loop: `Analysis` -> `Planning` -> `Execution` -> `Observation` -> `Reflexion`. Use `branchFromThought` to pivot when reality contradicts your model.
|
|
23
17
|
|
|
24
18
|
---
|
|
25
19
|
|
|
26
20
|
## 🛠️ DEFINITIVE TOOL INVENTORY (33 CAPABILITIES)
|
|
27
21
|
|
|
28
22
|
### 🧠 Core Thinking
|
|
29
|
-
- `sequentialthinking
|
|
30
|
-
- `start_thinking_block`: Context isolation.
|
|
31
|
-
- `summarize_history`: Context window management.
|
|
32
|
-
- `search_thoughts`: Recall past logic.
|
|
33
|
-
- `get_thinking_blocks`: Brain state overview.
|
|
34
|
-
- `clear_thought_history`: Hard reset.
|
|
23
|
+
- `sequentialthinking`, `start_thinking_block`, `summarize_history`, `search_thoughts`, `get_thinking_blocks`, `clear_thought_history`
|
|
35
24
|
|
|
36
25
|
### 🕸️ Project Knowledge Graph
|
|
37
|
-
- `build_project_graph
|
|
38
|
-
- `force_rebuild_graph`: Clear cache & re-map.
|
|
39
|
-
- `get_file_relationships`: Local dependency check.
|
|
40
|
-
- `get_project_graph_summary`: Architectural overview.
|
|
41
|
-
- `get_project_graph_visualization`: Mermaid Diagram generation.
|
|
26
|
+
- `build_project_graph`, `force_rebuild_graph`, `get_file_relationships`, `get_project_graph_summary`, `get_project_graph_visualization`
|
|
42
27
|
|
|
43
28
|
### 🔍 Intelligence & Analysis
|
|
44
|
-
- `deep_code_analyze
|
|
45
|
-
- `search_code`: Advanced regex/filter searching.
|
|
46
|
-
- `file_exists`: Reality verification.
|
|
47
|
-
- `list_directory`: Topography scanning.
|
|
29
|
+
- `deep_code_analyze`, `search_code`, `file_exists`, `list_directory`
|
|
48
30
|
|
|
49
31
|
### ⚡ Action & Coding
|
|
50
|
-
- `deep_code_edit
|
|
51
|
-
- `edit_file`: Precise text replacement.
|
|
52
|
-
- `write_file`: File creation (Safety restricted).
|
|
53
|
-
- `read_file`: Content parsing.
|
|
54
|
-
- `shell_execute`: Controlled bash execution (Safety filtered).
|
|
32
|
+
- `deep_code_edit`, `edit_file`, `write_file`, `read_file`, `shell_execute`
|
|
55
33
|
|
|
56
34
|
### 💾 Persistent Memory & Knowledge
|
|
57
|
-
- `manage_notes
|
|
58
|
-
- `add_code_snippet`: Reusable pattern storage.
|
|
59
|
-
- `search_code_db`: Pattern/Snippet retrieval.
|
|
60
|
-
- `learn_architecture_pattern`: Architectural logic encoding.
|
|
35
|
+
- `manage_notes`, `add_code_snippet`, `search_code_db`, `learn_architecture_pattern`
|
|
61
36
|
|
|
62
37
|
### 🌐 Web & Research
|
|
63
|
-
- `web_search
|
|
64
|
-
- `read_webpage`: Clean Markdown conversion.
|
|
65
|
-
- `fetch`: Raw API/URL retrieval.
|
|
38
|
+
- `web_search`, `read_webpage`, `fetch`
|
|
66
39
|
|
|
67
40
|
### 🤝 Human-in-the-Loop
|
|
68
|
-
- `ask_human
|
|
69
|
-
- `respond_to_human`: Answer processing.
|
|
70
|
-
- `get_pending_questions`: Queue management.
|
|
71
|
-
- `get_interaction_history`: Communication audit.
|
|
72
|
-
- `clear_old_interactions`: History pruning.
|
|
41
|
+
- `ask_human`, `respond_to_human`, `get_pending_questions`, `get_interaction_history`, `clear_old_interactions`
|
|
73
42
|
|
|
74
|
-
### ⚽ Specialized Intelligence
|
|
75
|
-
- `analyze_football_match`:
|
|
43
|
+
### ⚽ Specialized Intelligence (Sports Expert Mode)
|
|
44
|
+
- `analyze_football_match`: Activate the **Professional Analysis Panel** framework:
|
|
45
|
+
1. **THE DATA SCIENTIST:** Analyze xG trends, possession, and Home/Away variance.
|
|
46
|
+
2. **THE TACTICAL SCOUT:** Stylistic matchups (Press vs. Block) and key positional battles.
|
|
47
|
+
3. **THE PHYSIO:** Fatigue check (rest days, travel) and squad depth impact.
|
|
48
|
+
4. **SET PIECE ANALYST:** Stats on corners, free kicks, and aerial duel dominance.
|
|
49
|
+
5. **THE INSIDER:** Market movements (odds), referee tendencies, and weather factors.
|
|
50
|
+
6. **THE SKEPTIC:** Identify "trap games" and simulate "what-if" script scenarios.
|
|
76
51
|
|
|
77
52
|
---
|
|
78
53
|
|
|
79
54
|
## [THE GOLDEN CONSTRAINTS]
|
|
80
|
-
1. **Read Before Write:** Modification without parsing is
|
|
81
|
-
2. **No Hallucination:**
|
|
82
|
-
3. **Environment Dominance:** Map
|
|
55
|
+
1. **Read Before Write:** Modification without full parsing is prohibited.
|
|
56
|
+
2. **No Hallucination:** Every claim must be backed by a recorded `observation`.
|
|
57
|
+
3. **Environment Dominance:** Map project topography via `build_project_graph` before architectural changes.
|
|
83
58
|
|
|
84
59
|
---
|
|
85
|
-
**Status:** Supreme Architect Mode
|
|
86
|
-
**Directives:** Think Deeply. Act Precisely.
|
|
60
|
+
**Status:** Supreme Architect Mode v4.0 [Active].
|
|
61
|
+
**Directives:** Think Deeply. Act Precisely. Provide Expert Multi-Dimensional Sports Analysis.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE BASE CLASSES
|
|
3
|
+
* Abstract base classes and interfaces for data providers
|
|
4
|
+
*/
|
|
5
|
+
import { Match, Team, Player, TableEntry, APIResponse, ProviderStatus, ProviderType } from './types.js';
|
|
6
|
+
import { CacheService } from './cache.js';
|
|
7
|
+
/**
|
|
8
|
+
* Base interface for all data providers
|
|
9
|
+
*/
|
|
10
|
+
export interface IDataProvider {
|
|
11
|
+
/**
|
|
12
|
+
* Provider name/type
|
|
13
|
+
*/
|
|
14
|
+
readonly type: ProviderType;
|
|
15
|
+
/**
|
|
16
|
+
* Check if provider is available
|
|
17
|
+
*/
|
|
18
|
+
isAvailable(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Get provider status
|
|
21
|
+
*/
|
|
22
|
+
getStatus(): ProviderStatus;
|
|
23
|
+
/**
|
|
24
|
+
* Fetch a match by ID
|
|
25
|
+
*/
|
|
26
|
+
getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
27
|
+
/**
|
|
28
|
+
* Fetch live matches for a league
|
|
29
|
+
*/
|
|
30
|
+
getLiveMatches(leagueId?: string): Promise<APIResponse<Match[]>>;
|
|
31
|
+
/**
|
|
32
|
+
* Fetch league standings
|
|
33
|
+
*/
|
|
34
|
+
getStandings(leagueId: string, season?: string): Promise<APIResponse<TableEntry[]>>;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch team information
|
|
37
|
+
*/
|
|
38
|
+
getTeam(teamId: string): Promise<APIResponse<Team>>;
|
|
39
|
+
/**
|
|
40
|
+
* Fetch player information
|
|
41
|
+
*/
|
|
42
|
+
getPlayer(playerId: string): Promise<APIResponse<Player>>;
|
|
43
|
+
/**
|
|
44
|
+
* Search for teams
|
|
45
|
+
*/
|
|
46
|
+
searchTeams(query: string): Promise<APIResponse<Team[]>>;
|
|
47
|
+
/**
|
|
48
|
+
* Search for players
|
|
49
|
+
*/
|
|
50
|
+
searchPlayers(query: string): Promise<APIResponse<Player[]>>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Abstract base class for API-based providers
|
|
54
|
+
*/
|
|
55
|
+
export declare abstract class APIProviderBase implements IDataProvider {
|
|
56
|
+
readonly type: ProviderType;
|
|
57
|
+
protected apiKey: string;
|
|
58
|
+
protected baseUrl: string;
|
|
59
|
+
protected rateLimit: number;
|
|
60
|
+
protected cache: CacheService;
|
|
61
|
+
protected rateLimitUntil: Date | null;
|
|
62
|
+
protected lastCall: Date | null;
|
|
63
|
+
protected callCount: number;
|
|
64
|
+
constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number);
|
|
65
|
+
/**
|
|
66
|
+
* Check if provider has valid credentials
|
|
67
|
+
*/
|
|
68
|
+
isAvailable(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Check if currently rate limited
|
|
71
|
+
*/
|
|
72
|
+
isRateLimited(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Get current provider status
|
|
75
|
+
*/
|
|
76
|
+
getStatus(): ProviderStatus;
|
|
77
|
+
/**
|
|
78
|
+
* Abstract methods to be implemented by concrete providers
|
|
79
|
+
*/
|
|
80
|
+
abstract getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
81
|
+
abstract getLiveMatches(leagueId?: string): Promise<APIResponse<Match[]>>;
|
|
82
|
+
abstract getStandings(leagueId: string, season?: string): Promise<APIResponse<TableEntry[]>>;
|
|
83
|
+
abstract getTeam(teamId: string): Promise<APIResponse<Team>>;
|
|
84
|
+
abstract getPlayer(playerId: string): Promise<APIResponse<Player>>;
|
|
85
|
+
abstract searchTeams(query: string): Promise<APIResponse<Team[]>>;
|
|
86
|
+
abstract searchPlayers(query: string): Promise<APIResponse<Player[]>>;
|
|
87
|
+
/**
|
|
88
|
+
* Make a rate-limited API call
|
|
89
|
+
*/
|
|
90
|
+
protected callAPI<T>(endpoint: string, options?: RequestInit): Promise<APIResponse<T>>;
|
|
91
|
+
/**
|
|
92
|
+
* Get authentication headers for the API
|
|
93
|
+
*/
|
|
94
|
+
protected abstract getAuthHeaders(): Record<string, string>;
|
|
95
|
+
/**
|
|
96
|
+
* Transform raw API response to standard format
|
|
97
|
+
*/
|
|
98
|
+
protected abstract transformResponse<T>(data: any): T;
|
|
99
|
+
/**
|
|
100
|
+
* Reset rate limit tracking (for testing)
|
|
101
|
+
*/
|
|
102
|
+
resetRateLimit(): void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Abstract base class for scraper-based providers
|
|
106
|
+
*/
|
|
107
|
+
export declare abstract class ScraperProviderBase implements IDataProvider {
|
|
108
|
+
protected cache: CacheService;
|
|
109
|
+
readonly type: ProviderType;
|
|
110
|
+
constructor();
|
|
111
|
+
isAvailable(): boolean;
|
|
112
|
+
getStatus(): ProviderStatus;
|
|
113
|
+
abstract getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
114
|
+
abstract getLiveMatches(leagueId?: string): Promise<APIResponse<Match[]>>;
|
|
115
|
+
abstract getStandings(leagueId: string, season?: string): Promise<APIResponse<TableEntry[]>>;
|
|
116
|
+
abstract getTeam(teamId: string): Promise<APIResponse<Team>>;
|
|
117
|
+
abstract getPlayer(playerId: string): Promise<APIResponse<Player>>;
|
|
118
|
+
abstract searchTeams(query: string): Promise<APIResponse<Team[]>>;
|
|
119
|
+
abstract searchPlayers(query: string): Promise<APIResponse<Player[]>>;
|
|
120
|
+
/**
|
|
121
|
+
* Scrape a webpage and extract structured data
|
|
122
|
+
*/
|
|
123
|
+
protected scrape<T>(url: string, extractor: (html: string) => T): Promise<APIResponse<T>>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Fallback provider that tries multiple sources in order
|
|
127
|
+
*/
|
|
128
|
+
export declare class FallbackProvider implements IDataProvider {
|
|
129
|
+
private providers;
|
|
130
|
+
private cache;
|
|
131
|
+
readonly type: ProviderType;
|
|
132
|
+
constructor(providers: IDataProvider[], cache?: CacheService);
|
|
133
|
+
isAvailable(): boolean;
|
|
134
|
+
getStatus(): ProviderStatus;
|
|
135
|
+
/**
|
|
136
|
+
* Try each provider in sequence until one succeeds
|
|
137
|
+
*/
|
|
138
|
+
private tryProviders;
|
|
139
|
+
getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
140
|
+
getLiveMatches(leagueId?: string): Promise<APIResponse<Match[]>>;
|
|
141
|
+
getStandings(leagueId: string, season?: string): Promise<APIResponse<TableEntry[]>>;
|
|
142
|
+
getTeam(teamId: string): Promise<APIResponse<Team>>;
|
|
143
|
+
getPlayer(playerId: string): Promise<APIResponse<Player>>;
|
|
144
|
+
searchTeams(query: string): Promise<APIResponse<Team[]>>;
|
|
145
|
+
searchPlayers(query: string): Promise<APIResponse<Player[]>>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Base class for sports tools
|
|
149
|
+
*/
|
|
150
|
+
export declare abstract class SportsToolBase {
|
|
151
|
+
protected cache: CacheService;
|
|
152
|
+
constructor(cache?: CacheService);
|
|
153
|
+
/**
|
|
154
|
+
* Format error message for user
|
|
155
|
+
*/
|
|
156
|
+
protected formatError(error: unknown, context: string): string;
|
|
157
|
+
/**
|
|
158
|
+
* Sanitize user input
|
|
159
|
+
*/
|
|
160
|
+
protected sanitizeInput(input: string): string;
|
|
161
|
+
/**
|
|
162
|
+
* Format a team name for search
|
|
163
|
+
*/
|
|
164
|
+
protected formatTeamName(name: string): string;
|
|
165
|
+
/**
|
|
166
|
+
* Format date to readable string
|
|
167
|
+
*/
|
|
168
|
+
protected formatDate(date: Date): string;
|
|
169
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE BASE CLASSES
|
|
3
|
+
* Abstract base classes and interfaces for data providers
|
|
4
|
+
*/
|
|
5
|
+
import { CacheService } from './cache.js';
|
|
6
|
+
import { logger } from '../../../utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for API-based providers
|
|
9
|
+
*/
|
|
10
|
+
export class APIProviderBase {
|
|
11
|
+
type;
|
|
12
|
+
apiKey;
|
|
13
|
+
baseUrl;
|
|
14
|
+
rateLimit;
|
|
15
|
+
cache;
|
|
16
|
+
rateLimitUntil = null;
|
|
17
|
+
lastCall = null;
|
|
18
|
+
callCount = 0;
|
|
19
|
+
constructor(type, apiKey, baseUrl, rateLimit = 10 // calls per minute
|
|
20
|
+
) {
|
|
21
|
+
this.type = type;
|
|
22
|
+
this.apiKey = apiKey;
|
|
23
|
+
this.baseUrl = baseUrl;
|
|
24
|
+
this.rateLimit = rateLimit;
|
|
25
|
+
this.cache = new CacheService();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if provider has valid credentials
|
|
29
|
+
*/
|
|
30
|
+
isAvailable() {
|
|
31
|
+
return !!this.apiKey && !this.isRateLimited();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if currently rate limited
|
|
35
|
+
*/
|
|
36
|
+
isRateLimited() {
|
|
37
|
+
if (!this.rateLimitUntil)
|
|
38
|
+
return false;
|
|
39
|
+
return new Date() < this.rateLimitUntil;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get current provider status
|
|
43
|
+
*/
|
|
44
|
+
getStatus() {
|
|
45
|
+
return {
|
|
46
|
+
name: this.type,
|
|
47
|
+
available: this.isAvailable(),
|
|
48
|
+
quotaRemaining: Math.max(0, this.rateLimit - this.callCount),
|
|
49
|
+
quotaLimit: this.rateLimit,
|
|
50
|
+
rateLimitUntil: this.rateLimitUntil || undefined,
|
|
51
|
+
lastCall: this.lastCall || undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Make a rate-limited API call
|
|
56
|
+
*/
|
|
57
|
+
async callAPI(endpoint, options = {}) {
|
|
58
|
+
// Check rate limit
|
|
59
|
+
if (this.isRateLimited()) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: `Rate limited until ${this.rateLimitUntil}`,
|
|
63
|
+
rateLimited: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Check availability
|
|
67
|
+
if (!this.isAvailable()) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'Provider not available (missing API key)',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
75
|
+
const response = await fetch(url, {
|
|
76
|
+
...options,
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
...this.getAuthHeaders(),
|
|
80
|
+
...options.headers,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
this.lastCall = new Date();
|
|
84
|
+
this.callCount++;
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
// Handle rate limiting
|
|
87
|
+
if (response.status === 429) {
|
|
88
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
89
|
+
this.rateLimitUntil = new Date(Date.now() + (retryAfter ? parseInt(retryAfter) * 1000 : 60000));
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: 'Rate limited',
|
|
93
|
+
rateLimited: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
data: this.transformResponse(data),
|
|
105
|
+
provider: this.type,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.error(`${this.type} API error: ${error}`);
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Reset rate limit tracking (for testing)
|
|
118
|
+
*/
|
|
119
|
+
resetRateLimit() {
|
|
120
|
+
this.rateLimitUntil = null;
|
|
121
|
+
this.callCount = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Abstract base class for scraper-based providers
|
|
126
|
+
*/
|
|
127
|
+
export class ScraperProviderBase {
|
|
128
|
+
cache;
|
|
129
|
+
type = 'scraper';
|
|
130
|
+
constructor() {
|
|
131
|
+
this.cache = new CacheService();
|
|
132
|
+
}
|
|
133
|
+
isAvailable() {
|
|
134
|
+
return true; // Scraping is always "available"
|
|
135
|
+
}
|
|
136
|
+
getStatus() {
|
|
137
|
+
return {
|
|
138
|
+
name: this.type,
|
|
139
|
+
available: true,
|
|
140
|
+
quotaRemaining: Infinity,
|
|
141
|
+
quotaLimit: Infinity,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Scrape a webpage and extract structured data
|
|
146
|
+
*/
|
|
147
|
+
async scrape(url, extractor) {
|
|
148
|
+
try {
|
|
149
|
+
const { fetchWithRetry } = await import('../../../utils.js');
|
|
150
|
+
const response = await fetchWithRetry(url);
|
|
151
|
+
const html = await response.text();
|
|
152
|
+
const data = extractor(html);
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
data,
|
|
156
|
+
provider: 'scraper',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
logger.error(`Scraping error for ${url}: ${error}`);
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: error instanceof Error ? error.message : String(error),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fallback provider that tries multiple sources in order
|
|
170
|
+
*/
|
|
171
|
+
export class FallbackProvider {
|
|
172
|
+
providers;
|
|
173
|
+
cache;
|
|
174
|
+
type = 'scraper';
|
|
175
|
+
constructor(providers, cache = new CacheService()) {
|
|
176
|
+
this.providers = providers;
|
|
177
|
+
this.cache = cache;
|
|
178
|
+
}
|
|
179
|
+
isAvailable() {
|
|
180
|
+
return this.providers.some(p => p.isAvailable());
|
|
181
|
+
}
|
|
182
|
+
getStatus() {
|
|
183
|
+
const available = this.providers.filter(p => p.isAvailable());
|
|
184
|
+
return {
|
|
185
|
+
name: this.type,
|
|
186
|
+
available: available.length > 0,
|
|
187
|
+
quotaRemaining: available.length,
|
|
188
|
+
quotaLimit: this.providers.length,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Try each provider in sequence until one succeeds
|
|
193
|
+
*/
|
|
194
|
+
async tryProviders(fn) {
|
|
195
|
+
const errors = [];
|
|
196
|
+
for (const provider of this.providers) {
|
|
197
|
+
if (!provider.isAvailable()) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const result = await fn(provider);
|
|
201
|
+
if (result.success) {
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
errors.push(`${provider.type}: ${result.error || 'Unknown error'}`);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
error: `All providers failed:\n${errors.join('\n')}`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async getMatch(matchId) {
|
|
212
|
+
const cacheKey = CacheService.generateKey('match', matchId);
|
|
213
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.getMatch(matchId)), 5 * 60 * 1000 // 5 minutes
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
async getLiveMatches(leagueId) {
|
|
217
|
+
const cacheKey = CacheService.generateKey('live', leagueId || 'all');
|
|
218
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.getLiveMatches(leagueId)), 60 * 1000 // 1 minute
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
async getStandings(leagueId, season) {
|
|
222
|
+
const cacheKey = CacheService.generateKey('standings', leagueId, season || 'current');
|
|
223
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.getStandings(leagueId, season)), 30 * 60 * 1000 // 30 minutes
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
async getTeam(teamId) {
|
|
227
|
+
const cacheKey = CacheService.generateKey('team', teamId);
|
|
228
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.getTeam(teamId)), 60 * 60 * 1000 // 1 hour
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
async getPlayer(playerId) {
|
|
232
|
+
const cacheKey = CacheService.generateKey('player', playerId);
|
|
233
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.getPlayer(playerId)), 60 * 60 * 1000 // 1 hour
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
async searchTeams(query) {
|
|
237
|
+
const cacheKey = CacheService.generateKey('search', 'team', query);
|
|
238
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.searchTeams(query)), 60 * 60 * 1000 // 1 hour
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
async searchPlayers(query) {
|
|
242
|
+
const cacheKey = CacheService.generateKey('search', 'player', query);
|
|
243
|
+
return this.cache.getOrSet(cacheKey, () => this.tryProviders(p => p.searchPlayers(query)), 60 * 60 * 1000 // 1 hour
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Base class for sports tools
|
|
249
|
+
*/
|
|
250
|
+
export class SportsToolBase {
|
|
251
|
+
cache;
|
|
252
|
+
constructor(cache) {
|
|
253
|
+
this.cache = cache || new CacheService();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Format error message for user
|
|
257
|
+
*/
|
|
258
|
+
formatError(error, context) {
|
|
259
|
+
if (error instanceof Error) {
|
|
260
|
+
return `Error in ${context}: ${error.message}`;
|
|
261
|
+
}
|
|
262
|
+
return `Unknown error in ${context}`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Sanitize user input
|
|
266
|
+
*/
|
|
267
|
+
sanitizeInput(input) {
|
|
268
|
+
return input.trim().replace(/[<>]/g, '');
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Format a team name for search
|
|
272
|
+
*/
|
|
273
|
+
formatTeamName(name) {
|
|
274
|
+
return this.sanitizeInput(name).toLowerCase();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Format date to readable string
|
|
278
|
+
*/
|
|
279
|
+
formatDate(date) {
|
|
280
|
+
return date.toLocaleDateString('en-US', {
|
|
281
|
+
weekday: 'short',
|
|
282
|
+
year: 'numeric',
|
|
283
|
+
month: 'short',
|
|
284
|
+
day: 'numeric',
|
|
285
|
+
hour: '2-digit',
|
|
286
|
+
minute: '2-digit',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|