@semanticintent/semantic-chirp-intelligence-mcp 3.1.0 → 4.0.0

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.
@@ -1,309 +0,0 @@
1
- /**
2
- * 🏒 Yahoo Fantasy API Client Service
3
- *
4
- * Handles all Yahoo Fantasy Sports API interactions including:
5
- * - OAuth token management (load, save, refresh)
6
- * - Authenticated API requests
7
- * - Token expiration handling
8
- * - Error handling and retries
9
- */
10
- import fs from 'fs';
11
- import https from 'https';
12
- import path from 'path';
13
- import { fileURLToPath } from 'url';
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
16
- export class YahooApiClient {
17
- cachedToken = null;
18
- tokenFile;
19
- apiBase;
20
- clientId;
21
- clientSecret;
22
- constructor(clientId, clientSecret, apiBase = "https://fantasysports.yahooapis.com/fantasy/v2") {
23
- this.clientId = clientId;
24
- this.clientSecret = clientSecret;
25
- this.apiBase = apiBase;
26
- this.tokenFile = path.join(__dirname, "..", "..", ".yahoo-oauth.json");
27
- }
28
- /**
29
- * Load OAuth token from file system
30
- */
31
- loadToken() {
32
- try {
33
- console.error(`[DEBUG] Looking for token at: ${this.tokenFile}`);
34
- if (fs.existsSync(this.tokenFile)) {
35
- const tokenData = fs.readFileSync(this.tokenFile, "utf8");
36
- const token = JSON.parse(tokenData);
37
- console.error(`[DEBUG] Token loaded successfully`);
38
- this.cachedToken = token;
39
- return token;
40
- }
41
- else {
42
- console.error(`[DEBUG] Token file not found`);
43
- }
44
- }
45
- catch (error) {
46
- console.error("[ERROR] Error loading token:", error);
47
- }
48
- return null;
49
- }
50
- /**
51
- * Save OAuth token to file system
52
- */
53
- saveToken(token) {
54
- token.expires_at = Date.now() + (token.expires_in * 1000);
55
- fs.writeFileSync(this.tokenFile, JSON.stringify(token, null, 2));
56
- this.cachedToken = token;
57
- console.error("[DEBUG] Token saved successfully");
58
- }
59
- /**
60
- * Refresh expired OAuth token
61
- */
62
- async refreshAccessToken() {
63
- const token = this.cachedToken || this.loadToken();
64
- if (!token) {
65
- throw new Error("No refresh token available. Please re-authenticate.");
66
- }
67
- console.error("[DEBUG] Refreshing access token...");
68
- const tokenData = new URLSearchParams({
69
- client_id: this.clientId,
70
- client_secret: this.clientSecret,
71
- redirect_uri: "oob",
72
- refresh_token: token.refresh_token,
73
- grant_type: "refresh_token",
74
- });
75
- const options = {
76
- hostname: "api.login.yahoo.com",
77
- port: 443,
78
- path: "/oauth2/get_token",
79
- method: "POST",
80
- headers: {
81
- "Content-Type": "application/x-www-form-urlencoded",
82
- "Content-Length": tokenData.toString().length,
83
- },
84
- };
85
- return new Promise((resolve, reject) => {
86
- const req = https.request(options, (res) => {
87
- let data = "";
88
- res.on("data", (chunk) => {
89
- data += chunk;
90
- });
91
- res.on("end", () => {
92
- try {
93
- const newToken = JSON.parse(data);
94
- this.saveToken(newToken);
95
- console.error("[DEBUG] Token refreshed successfully");
96
- resolve(newToken.access_token);
97
- }
98
- catch (error) {
99
- reject(new Error(`Failed to parse token response: ${error}`));
100
- }
101
- });
102
- });
103
- req.on("error", (error) => {
104
- reject(error);
105
- });
106
- req.write(tokenData.toString());
107
- req.end();
108
- });
109
- }
110
- /**
111
- * Get valid access token, refreshing if necessary
112
- */
113
- async getValidAccessToken() {
114
- const token = this.cachedToken || this.loadToken();
115
- if (!token) {
116
- throw new Error("No authentication token found! Run: node authenticate.js");
117
- }
118
- // Check if token is expired (with 5 minute buffer)
119
- if (token.expires_at && token.expires_at < Date.now() + 300000) {
120
- console.error("[DEBUG] Token expired or expiring soon, refreshing...");
121
- return await this.refreshAccessToken();
122
- }
123
- return token.access_token;
124
- }
125
- /**
126
- * Make authenticated request to Yahoo Fantasy API
127
- *
128
- * @param endpoint - API endpoint path (e.g., "/team/nhl.l.12345.t.1/roster")
129
- * @param format - Response format (default: "json")
130
- * @returns Parsed API response
131
- */
132
- async request(endpoint, format = "json") {
133
- const accessToken = await this.getValidAccessToken();
134
- const url = `${this.apiBase}${endpoint}${endpoint.includes('?') ? '&' : '?'}format=${format}`;
135
- console.error(`[DEBUG] API Request: ${endpoint}`);
136
- return new Promise((resolve, reject) => {
137
- const urlObj = new URL(url);
138
- const options = {
139
- hostname: urlObj.hostname,
140
- port: 443,
141
- path: urlObj.pathname + urlObj.search,
142
- method: "GET",
143
- headers: {
144
- Authorization: `Bearer ${accessToken}`,
145
- Accept: "application/json",
146
- },
147
- };
148
- const req = https.request(options, (res) => {
149
- let data = "";
150
- res.on("data", (chunk) => {
151
- data += chunk;
152
- });
153
- res.on("end", () => {
154
- if (res.statusCode === 401) {
155
- // Token expired, try to refresh and retry
156
- this.refreshAccessToken()
157
- .then(() => this.request(endpoint, format))
158
- .then(resolve)
159
- .catch(reject);
160
- return;
161
- }
162
- if (res.statusCode !== 200) {
163
- reject(new Error(`API returned status ${res.statusCode}: ${data}`));
164
- return;
165
- }
166
- try {
167
- const parsed = JSON.parse(data);
168
- resolve(parsed);
169
- }
170
- catch (error) {
171
- reject(new Error(`Failed to parse API response: ${error}`));
172
- }
173
- });
174
- });
175
- req.on("error", (error) => {
176
- reject(error);
177
- });
178
- req.end();
179
- });
180
- }
181
- /**
182
- * Helper: Strip nhl.l. prefix from league ID if present
183
- */
184
- stripLeaguePrefix(leagueId) {
185
- return leagueId.replace(/^nhl\.l\./, '');
186
- }
187
- /**
188
- * Helper: Strip team number from full team ID (nhl.l.12345.t.6 -> 6)
189
- */
190
- extractTeamNumber(teamId) {
191
- const match = teamId.match(/\.t\.(\d+)$/);
192
- return match ? match[1] : teamId.replace(/^.*\.t\./, '');
193
- }
194
- /**
195
- * Convenience method: Get team roster
196
- */
197
- async getTeamRoster(leagueId, teamId) {
198
- const cleanLeagueId = this.stripLeaguePrefix(leagueId);
199
- const cleanTeamId = this.extractTeamNumber(teamId);
200
- return this.request(`/team/nhl.l.${cleanLeagueId}.t.${cleanTeamId}/roster`);
201
- }
202
- /**
203
- * Convenience method: Get league standings
204
- */
205
- async getLeagueStandings(leagueId) {
206
- const cleanLeagueId = this.stripLeaguePrefix(leagueId);
207
- return this.request(`/league/nhl.l.${cleanLeagueId}/standings`);
208
- }
209
- /**
210
- * Convenience method: Get team matchup
211
- */
212
- async getTeamMatchup(leagueId, teamId, week) {
213
- const cleanLeagueId = this.stripLeaguePrefix(leagueId);
214
- const cleanTeamId = this.extractTeamNumber(teamId);
215
- const weekParam = week ? `;week=${week}` : '';
216
- return this.request(`/team/nhl.l.${cleanLeagueId}.t.${cleanTeamId}/matchups${weekParam}`);
217
- }
218
- /**
219
- * Convenience method: Get league scoreboard
220
- */
221
- async getLeagueScoreboard(leagueId, week) {
222
- const cleanLeagueId = this.stripLeaguePrefix(leagueId);
223
- const weekParam = week ? `;week=${week}` : '';
224
- return this.request(`/league/nhl.l.${cleanLeagueId}/scoreboard${weekParam}`);
225
- }
226
- /**
227
- * Convenience method: Get team stats
228
- */
229
- async getTeamStats(leagueId, teamId) {
230
- const cleanLeagueId = this.stripLeaguePrefix(leagueId);
231
- const cleanTeamId = this.extractTeamNumber(teamId);
232
- return this.request(`/team/nhl.l.${cleanLeagueId}.t.${cleanTeamId}/stats`);
233
- }
234
- /**
235
- * Convenience method: Get league settings
236
- */
237
- async getLeagueSettings(leagueId) {
238
- return this.request(`/league/nhl.l.${leagueId}/settings`);
239
- }
240
- /**
241
- * Convenience method: Get players (for search, trending, etc.)
242
- */
243
- async getPlayers(leagueId, queryParams = '') {
244
- return this.request(`/league/nhl.l.${leagueId}/players${queryParams}`);
245
- }
246
- /**
247
- * Convenience method: Search players by position
248
- */
249
- async searchPlayers(position, count = 25, leagueId) {
250
- const league = leagueId || process.env.YAHOO_LEAGUE_ID;
251
- const cleanLeagueId = this.stripLeaguePrefix(league);
252
- let queryParams = `;status=A;count=${count}`;
253
- if (position) {
254
- queryParams += `;position=${position}`;
255
- }
256
- const data = await this.request(`/league/nhl.l.${cleanLeagueId}/players${queryParams}`);
257
- // Parse player data
258
- const playersData = data.fantasy_content.league[1].players;
259
- const players = Object.keys(playersData)
260
- .filter(key => key !== 'count')
261
- .map(key => {
262
- const playerData = playersData[key].player[0];
263
- const playerId = playerData.find((item) => item.player_id)?.player_id;
264
- const name = playerData.find((item) => item.name)?.name?.full;
265
- const displayPosition = playerData.find((item) => item.display_position)?.display_position;
266
- const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
267
- const percentOwned = playerData.find((item) => item.percent_owned)?.percent_owned?.value || "0";
268
- return {
269
- player_id: playerId,
270
- name: name,
271
- position: displayPosition,
272
- team: team,
273
- percent_owned: parseFloat(percentOwned),
274
- selected_position: []
275
- };
276
- });
277
- return { players };
278
- }
279
- /**
280
- * Convenience method: Get trending players
281
- */
282
- async getTrendingPlayers(trendType = 'add', count = 25, leagueId) {
283
- const league = leagueId || process.env.YAHOO_LEAGUE_ID;
284
- const cleanLeagueId = this.stripLeaguePrefix(league);
285
- const sortParam = trendType === 'add' ? 'AR' : 'OR';
286
- const data = await this.request(`/league/nhl.l.${cleanLeagueId}/players;status=A;sort=${sortParam};count=${count}`);
287
- const playersData = data.fantasy_content.league[1].players;
288
- const players = Object.keys(playersData)
289
- .filter(key => key !== 'count')
290
- .map(key => {
291
- const playerData = playersData[key].player[0];
292
- const playerId = playerData.find((item) => item.player_id)?.player_id;
293
- const name = playerData.find((item) => item.name)?.name?.full;
294
- const displayPosition = playerData.find((item) => item.display_position)?.display_position;
295
- const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
296
- const percentOwned = playerData.find((item) => item.percent_owned)?.percent_owned?.value || "0";
297
- return {
298
- player_id: playerId,
299
- name: name,
300
- position: displayPosition,
301
- team: team,
302
- percent_owned: parseFloat(percentOwned),
303
- trending: trendType,
304
- selected_position: []
305
- };
306
- });
307
- return { players, trend_type: trendType };
308
- }
309
- }