cito-mcp 0.1.0 → 0.2.2
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 +396 -44
- package/dist/client.js +208 -0
- package/dist/envelope.js +210 -0
- package/dist/index.js +103 -201
- package/dist/instructions.js +80 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/insight.js +1013 -0
- package/dist/tools/live.js +447 -0
- package/dist/tools/match.js +464 -0
- package/dist/tools/meta.js +609 -0
- package/dist/tools/normalize.js +177 -0
- package/dist/tools/player.js +357 -0
- package/dist/tools/resolve.js +518 -0
- package/dist/tools/standings.js +319 -0
- package/dist/tools/team.js +704 -0
- package/dist/tools/types.js +85 -0
- package/package.json +3 -3
- package/dist/executor.js +0 -75
- package/dist/spec.js +0 -193
- package/dist/tools.js +0 -203
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* match_summary + match_details
|
|
3
|
+
*/
|
|
4
|
+
import { extractRows, fetchJson, gameNotIncludedHint, asRecord, pickString, } from '../client.js';
|
|
5
|
+
import { errorEnvelope, mapHttpToCode, newRequestId, partialFromRejection, successEnvelope, } from '../envelope.js';
|
|
6
|
+
import { normalizeMatch } from './normalize.js';
|
|
7
|
+
import { boolSchema, gameSchema, isPrimaryGame, parseGame, stringSchema, } from './types.js';
|
|
8
|
+
async function getSection(ctx, path, query) {
|
|
9
|
+
return fetchJson(ctx, path, { query });
|
|
10
|
+
}
|
|
11
|
+
function matchCore(game, matchId, raw) {
|
|
12
|
+
const m = normalizeMatch(game, raw);
|
|
13
|
+
const r = asRecord(raw) ?? {};
|
|
14
|
+
return {
|
|
15
|
+
matchId: m.matchId !== 'unknown' ? m.matchId : matchId,
|
|
16
|
+
game,
|
|
17
|
+
status: m.status,
|
|
18
|
+
startTime: m.startTime,
|
|
19
|
+
endTime: pickString(r.endTime, r.endedAt, r.finishedAt) ?? null,
|
|
20
|
+
bestOf: r.bestOf ?? r.bo ?? null,
|
|
21
|
+
score: {
|
|
22
|
+
team1: m.team1?.score ?? null,
|
|
23
|
+
team2: m.team2?.score ?? null,
|
|
24
|
+
},
|
|
25
|
+
team1: m.team1,
|
|
26
|
+
team2: m.team2,
|
|
27
|
+
event: m.event,
|
|
28
|
+
league: m.league,
|
|
29
|
+
rawStatus: pickString(r.status, r.state) ?? null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function primaryPath(game, matchId) {
|
|
33
|
+
switch (game) {
|
|
34
|
+
case 'lol':
|
|
35
|
+
return `/lol/matches/${encodeURIComponent(matchId)}`;
|
|
36
|
+
case 'cs2':
|
|
37
|
+
return `/cs2/matches/${encodeURIComponent(matchId)}`;
|
|
38
|
+
case 'dota2':
|
|
39
|
+
return `/dota2/matches/${encodeURIComponent(matchId)}`;
|
|
40
|
+
case 'cod':
|
|
41
|
+
return `/cod/matches/${encodeURIComponent(matchId)}`;
|
|
42
|
+
case 'ufc':
|
|
43
|
+
return `/ufc/bouts/${encodeURIComponent(matchId)}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export const matchSummary = {
|
|
47
|
+
name: 'match_summary',
|
|
48
|
+
description: `COMPOSITE match card: scoreline, key context, player performances, and VOD/demo links when available.
|
|
49
|
+
|
|
50
|
+
When to use:
|
|
51
|
+
- Match recap / default match UI
|
|
52
|
+
- After user selects a live or completed matchId
|
|
53
|
+
|
|
54
|
+
Prefer over match_details for chat answers and default UIs.
|
|
55
|
+
Prefer match_details for timelines, full map trees, live state, advanced packages.
|
|
56
|
+
|
|
57
|
+
Do not use when: no matchId yet (resolve from live/schedule); pure pre-match → match_preview.
|
|
58
|
+
|
|
59
|
+
Parallel-safe: yes. Upstream cost: 2–5.
|
|
60
|
+
Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includePlayerStats": true }`,
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
required: ['game', 'matchId'],
|
|
65
|
+
properties: {
|
|
66
|
+
game: gameSchema({ allowAll: false, required: true }),
|
|
67
|
+
matchId: stringSchema('Game-native match id (UFC: boutId). Example: "cs2-match-123".', 'cs2-match-123'),
|
|
68
|
+
view: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
enum: ['summary', 'full'],
|
|
71
|
+
default: 'summary',
|
|
72
|
+
description: 'Payload weight. Default summary.',
|
|
73
|
+
},
|
|
74
|
+
includePlayerStats: boolSchema('Include player performances when available.', true),
|
|
75
|
+
includeMedia: boolSchema('Include demos/VODs when the API provides them.', true),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
handler: async (args, ctx) => {
|
|
79
|
+
const started = Date.now();
|
|
80
|
+
const requestId = newRequestId();
|
|
81
|
+
const gameParse = parseGame(args.game, { allowAll: false, required: true });
|
|
82
|
+
if (gameParse.error || !gameParse.game || !isPrimaryGame(gameParse.game)) {
|
|
83
|
+
return errorEnvelope({
|
|
84
|
+
code: gameParse.error?.includes('unsupported') ? 'UNSUPPORTED_GAME' : 'VALIDATION',
|
|
85
|
+
message: gameParse.error ?? 'game is required',
|
|
86
|
+
game: null,
|
|
87
|
+
source: 'match_summary',
|
|
88
|
+
requestId,
|
|
89
|
+
tookMs: Date.now() - started,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const game = gameParse.game;
|
|
93
|
+
const matchId = typeof args.matchId === 'string' ? args.matchId.trim() : '';
|
|
94
|
+
if (!matchId) {
|
|
95
|
+
return errorEnvelope({
|
|
96
|
+
code: 'VALIDATION',
|
|
97
|
+
message: 'matchId is required',
|
|
98
|
+
game,
|
|
99
|
+
source: 'match_summary',
|
|
100
|
+
requestId,
|
|
101
|
+
tookMs: Date.now() - started,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const includePlayerStats = args.includePlayerStats !== false;
|
|
105
|
+
const includeMedia = args.includeMedia !== false;
|
|
106
|
+
const view = args.view === 'full' ? 'full' : 'summary';
|
|
107
|
+
const partial = [];
|
|
108
|
+
let upstreamCalls = 0;
|
|
109
|
+
let rateLimit = {};
|
|
110
|
+
const primary = await getSection(ctx, primaryPath(game, matchId));
|
|
111
|
+
upstreamCalls += 1;
|
|
112
|
+
rateLimit = primary.headers;
|
|
113
|
+
if (!primary.ok) {
|
|
114
|
+
return errorEnvelope({
|
|
115
|
+
code: mapHttpToCode(primary.status, { gameNotIncluded: gameNotIncludedHint(primary.data) }),
|
|
116
|
+
message: `Match not found or unavailable: ${matchId}`,
|
|
117
|
+
game,
|
|
118
|
+
source: 'match_summary',
|
|
119
|
+
requestId,
|
|
120
|
+
tookMs: Date.now() - started,
|
|
121
|
+
upstreamCalls,
|
|
122
|
+
rateLimit: primary.headers,
|
|
123
|
+
httpStatus: primary.status,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const match = matchCore(game, matchId, primary.data);
|
|
127
|
+
let playerPerformances = [];
|
|
128
|
+
let media = {
|
|
129
|
+
demos: [],
|
|
130
|
+
vods: [],
|
|
131
|
+
notes: null,
|
|
132
|
+
};
|
|
133
|
+
let gamesOrMaps = [];
|
|
134
|
+
let keyEvents = [];
|
|
135
|
+
const enrich = [];
|
|
136
|
+
if (includePlayerStats) {
|
|
137
|
+
enrich.push((async () => {
|
|
138
|
+
const paths = [];
|
|
139
|
+
if (game === 'lol')
|
|
140
|
+
paths.push(`/lol/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
141
|
+
if (game === 'cs2')
|
|
142
|
+
paths.push(`/cs2/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
143
|
+
if (game === 'dota2')
|
|
144
|
+
paths.push(`/dota2/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
145
|
+
if (game === 'cod')
|
|
146
|
+
paths.push(`/cod/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
147
|
+
if (game === 'ufc')
|
|
148
|
+
paths.push(`/ufc/bouts/${encodeURIComponent(matchId)}/stats`);
|
|
149
|
+
for (const p of paths) {
|
|
150
|
+
const res = await getSection(ctx, p);
|
|
151
|
+
upstreamCalls += 1;
|
|
152
|
+
rateLimit = { ...rateLimit, ...res.headers };
|
|
153
|
+
if (!res.ok) {
|
|
154
|
+
partial.push(partialFromRejection('playerPerformances', {
|
|
155
|
+
code: mapHttpToCode(res.status),
|
|
156
|
+
message: `player stats HTTP ${res.status}`,
|
|
157
|
+
httpStatus: res.status,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
playerPerformances = extractRows(res.data);
|
|
162
|
+
if (playerPerformances.length === 0 && asRecord(res.data)) {
|
|
163
|
+
playerPerformances = [res.data];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})());
|
|
168
|
+
}
|
|
169
|
+
if (includeMedia && game === 'cs2') {
|
|
170
|
+
enrich.push((async () => {
|
|
171
|
+
const res = await getSection(ctx, `/cs2/matches/${encodeURIComponent(matchId)}/demos`);
|
|
172
|
+
upstreamCalls += 1;
|
|
173
|
+
rateLimit = { ...rateLimit, ...res.headers };
|
|
174
|
+
if (res.ok)
|
|
175
|
+
media.demos = extractRows(res.data);
|
|
176
|
+
else {
|
|
177
|
+
partial.push(partialFromRejection('media', {
|
|
178
|
+
code: mapHttpToCode(res.status),
|
|
179
|
+
message: `demos HTTP ${res.status}`,
|
|
180
|
+
httpStatus: res.status,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
})());
|
|
184
|
+
}
|
|
185
|
+
// games/maps
|
|
186
|
+
enrich.push((async () => {
|
|
187
|
+
let path = null;
|
|
188
|
+
if (game === 'lol')
|
|
189
|
+
path = `/lol/matches/${encodeURIComponent(matchId)}/games`;
|
|
190
|
+
if (game === 'cod')
|
|
191
|
+
path = `/cod/matches/${encodeURIComponent(matchId)}/maps`;
|
|
192
|
+
if (game === 'ufc')
|
|
193
|
+
path = `/ufc/bouts/${encodeURIComponent(matchId)}/rounds`;
|
|
194
|
+
if (!path)
|
|
195
|
+
return;
|
|
196
|
+
const res = await getSection(ctx, path);
|
|
197
|
+
upstreamCalls += 1;
|
|
198
|
+
rateLimit = { ...rateLimit, ...res.headers };
|
|
199
|
+
if (res.ok) {
|
|
200
|
+
gamesOrMaps = extractRows(res.data);
|
|
201
|
+
if (view === 'summary')
|
|
202
|
+
gamesOrMaps = gamesOrMaps.slice(0, 5);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
partial.push(partialFromRejection('gamesOrMaps', {
|
|
206
|
+
code: mapHttpToCode(res.status),
|
|
207
|
+
message: `games/maps HTTP ${res.status}`,
|
|
208
|
+
httpStatus: res.status,
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
})());
|
|
212
|
+
await Promise.all(enrich);
|
|
213
|
+
const scoreline = {
|
|
214
|
+
display: `${match.team1?.name ?? '?'} ${match.score.team1 ?? '-'} : ${match.score.team2 ?? '-'} ${match.team2?.name ?? '?'}`,
|
|
215
|
+
team1: match.score.team1,
|
|
216
|
+
team2: match.score.team2,
|
|
217
|
+
};
|
|
218
|
+
if (view === 'summary' && playerPerformances.length > 10) {
|
|
219
|
+
playerPerformances = playerPerformances.slice(0, 10);
|
|
220
|
+
}
|
|
221
|
+
return successEnvelope({
|
|
222
|
+
source: 'match_summary',
|
|
223
|
+
game,
|
|
224
|
+
requestId,
|
|
225
|
+
tookMs: Date.now() - started,
|
|
226
|
+
upstreamCalls,
|
|
227
|
+
rateLimit,
|
|
228
|
+
partial: partial.length ? partial : undefined,
|
|
229
|
+
entities: { games: [game], ids: { matchId: match.matchId } },
|
|
230
|
+
data: {
|
|
231
|
+
match,
|
|
232
|
+
scoreline,
|
|
233
|
+
keyEvents,
|
|
234
|
+
playerPerformances,
|
|
235
|
+
media,
|
|
236
|
+
gamesOrMaps,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
export const matchDetails = {
|
|
242
|
+
name: 'match_details',
|
|
243
|
+
description: `Deep match package: optional timelines, advanced stats, live state/snapshots, full map/game tree, media inventory.
|
|
244
|
+
|
|
245
|
+
When to use:
|
|
246
|
+
- Analyst deep dive
|
|
247
|
+
- Live in-game window (LoL/CS2/UFC)
|
|
248
|
+
- Full demo list
|
|
249
|
+
|
|
250
|
+
Prefer over match_summary only when summary is insufficient.
|
|
251
|
+
Prefer match_summary for short answers and default cards.
|
|
252
|
+
|
|
253
|
+
Do not use when: first-pass live board (use live_matches + match_summary).
|
|
254
|
+
|
|
255
|
+
Section selection: pass includeTimeline / includeLiveState / includeAdvanced booleans, OR an explicit sections[] list.
|
|
256
|
+
If sections[] is non-empty it wins (booleans are ignored). LoL liveState/advanced require gameId.
|
|
257
|
+
|
|
258
|
+
Parallel-safe: yes. Upstream cost: 1–8 (section-gated).
|
|
259
|
+
Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "includeLiveState": false }`,
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
additionalProperties: false,
|
|
263
|
+
required: ['game', 'matchId'],
|
|
264
|
+
properties: {
|
|
265
|
+
game: gameSchema({ allowAll: false, required: true }),
|
|
266
|
+
matchId: stringSchema('Game-native match id (UFC boutId).', 'lol-match-1'),
|
|
267
|
+
gameId: stringSchema('LoL per-game live window target when distinct from matchId.'),
|
|
268
|
+
sections: {
|
|
269
|
+
type: 'array',
|
|
270
|
+
items: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
enum: ['base', 'playerStats', 'gamesOrMaps', 'timeline', 'liveState', 'media', 'advanced'],
|
|
273
|
+
},
|
|
274
|
+
description: 'Explicit section list; defaults to base+playerStats+gamesOrMaps+media.',
|
|
275
|
+
},
|
|
276
|
+
includeTimeline: boolSchema('Include timeline section (heavy).', false),
|
|
277
|
+
includeLiveState: boolSchema('Include live state/snapshots.', false),
|
|
278
|
+
includeAdvanced: boolSchema('Include advanced packages when available (LoL).', false),
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
handler: async (args, ctx) => {
|
|
282
|
+
const started = Date.now();
|
|
283
|
+
const requestId = newRequestId();
|
|
284
|
+
const gameParse = parseGame(args.game, { allowAll: false, required: true });
|
|
285
|
+
if (gameParse.error || !gameParse.game || !isPrimaryGame(gameParse.game)) {
|
|
286
|
+
return errorEnvelope({
|
|
287
|
+
code: gameParse.error?.includes('unsupported') ? 'UNSUPPORTED_GAME' : 'VALIDATION',
|
|
288
|
+
message: gameParse.error ?? 'game is required',
|
|
289
|
+
game: null,
|
|
290
|
+
source: 'match_details',
|
|
291
|
+
requestId,
|
|
292
|
+
tookMs: Date.now() - started,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const game = gameParse.game;
|
|
296
|
+
const matchId = typeof args.matchId === 'string' ? args.matchId.trim() : '';
|
|
297
|
+
if (!matchId) {
|
|
298
|
+
return errorEnvelope({
|
|
299
|
+
code: 'VALIDATION',
|
|
300
|
+
message: 'matchId is required',
|
|
301
|
+
game,
|
|
302
|
+
source: 'match_details',
|
|
303
|
+
requestId,
|
|
304
|
+
tookMs: Date.now() - started,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const gameId = typeof args.gameId === 'string' ? args.gameId : undefined;
|
|
308
|
+
let wanted;
|
|
309
|
+
if (Array.isArray(args.sections) && args.sections.length) {
|
|
310
|
+
wanted = new Set(args.sections.map(String));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
wanted = new Set(['base', 'playerStats', 'gamesOrMaps', 'media']);
|
|
314
|
+
if (args.includeTimeline === true)
|
|
315
|
+
wanted.add('timeline');
|
|
316
|
+
if (args.includeLiveState === true)
|
|
317
|
+
wanted.add('liveState');
|
|
318
|
+
if (args.includeAdvanced === true)
|
|
319
|
+
wanted.add('advanced');
|
|
320
|
+
}
|
|
321
|
+
const partial = [];
|
|
322
|
+
let upstreamCalls = 0;
|
|
323
|
+
let rateLimit = {};
|
|
324
|
+
const sections = {
|
|
325
|
+
base: null,
|
|
326
|
+
playerStats: null,
|
|
327
|
+
gamesOrMaps: null,
|
|
328
|
+
timeline: null,
|
|
329
|
+
liveState: null,
|
|
330
|
+
media: null,
|
|
331
|
+
advanced: null,
|
|
332
|
+
};
|
|
333
|
+
// base is primary
|
|
334
|
+
if (wanted.has('base')) {
|
|
335
|
+
const res = await getSection(ctx, primaryPath(game, matchId));
|
|
336
|
+
upstreamCalls += 1;
|
|
337
|
+
rateLimit = res.headers;
|
|
338
|
+
if (!res.ok) {
|
|
339
|
+
return errorEnvelope({
|
|
340
|
+
code: mapHttpToCode(res.status, { gameNotIncluded: gameNotIncludedHint(res.data) }),
|
|
341
|
+
message: `Match not found or unavailable: ${matchId}`,
|
|
342
|
+
game,
|
|
343
|
+
source: 'match_details',
|
|
344
|
+
requestId,
|
|
345
|
+
tookMs: Date.now() - started,
|
|
346
|
+
upstreamCalls,
|
|
347
|
+
rateLimit: res.headers,
|
|
348
|
+
httpStatus: res.status,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
sections.base = matchCore(game, matchId, res.data);
|
|
352
|
+
}
|
|
353
|
+
const tasks = [];
|
|
354
|
+
const load = (section, path, query) => {
|
|
355
|
+
tasks.push((async () => {
|
|
356
|
+
const res = await getSection(ctx, path, query);
|
|
357
|
+
upstreamCalls += 1;
|
|
358
|
+
rateLimit = { ...rateLimit, ...res.headers };
|
|
359
|
+
if (!res.ok) {
|
|
360
|
+
partial.push(partialFromRejection(section, {
|
|
361
|
+
code: mapHttpToCode(res.status),
|
|
362
|
+
message: `${section} HTTP ${res.status}`,
|
|
363
|
+
httpStatus: res.status,
|
|
364
|
+
}));
|
|
365
|
+
sections[section] = null;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
sections[section] = res.data;
|
|
369
|
+
}
|
|
370
|
+
})());
|
|
371
|
+
};
|
|
372
|
+
if (wanted.has('playerStats')) {
|
|
373
|
+
if (game === 'lol')
|
|
374
|
+
load('playerStats', `/lol/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
375
|
+
if (game === 'cs2')
|
|
376
|
+
load('playerStats', `/cs2/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
377
|
+
if (game === 'dota2')
|
|
378
|
+
load('playerStats', `/dota2/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
379
|
+
if (game === 'cod')
|
|
380
|
+
load('playerStats', `/cod/matches/${encodeURIComponent(matchId)}/player-stats`);
|
|
381
|
+
if (game === 'ufc')
|
|
382
|
+
load('playerStats', `/ufc/bouts/${encodeURIComponent(matchId)}/stats`);
|
|
383
|
+
}
|
|
384
|
+
if (wanted.has('gamesOrMaps')) {
|
|
385
|
+
if (game === 'lol')
|
|
386
|
+
load('gamesOrMaps', `/lol/matches/${encodeURIComponent(matchId)}/games`);
|
|
387
|
+
if (game === 'cod')
|
|
388
|
+
load('gamesOrMaps', `/cod/matches/${encodeURIComponent(matchId)}/maps`);
|
|
389
|
+
if (game === 'ufc')
|
|
390
|
+
load('gamesOrMaps', `/ufc/bouts/${encodeURIComponent(matchId)}/rounds`);
|
|
391
|
+
if (game === 'cs2' || game === 'dota2') {
|
|
392
|
+
// no dedicated maps list — leave null with note via partial only if requested alone
|
|
393
|
+
sections.gamesOrMaps = sections.base;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (wanted.has('timeline')) {
|
|
397
|
+
if (game === 'lol')
|
|
398
|
+
load('timeline', `/lol/matches/${encodeURIComponent(matchId)}/timeline`);
|
|
399
|
+
else {
|
|
400
|
+
partial.push(partialFromRejection('timeline', {
|
|
401
|
+
code: 'NOT_IMPLEMENTED',
|
|
402
|
+
message: `Timeline not available for ${game} via curated paths`,
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (wanted.has('liveState')) {
|
|
407
|
+
if (game === 'lol' && gameId) {
|
|
408
|
+
load('liveState', `/lol/live/${encodeURIComponent(gameId)}/window`);
|
|
409
|
+
}
|
|
410
|
+
else if (game === 'cs2') {
|
|
411
|
+
load('liveState', `/cs2/live/${encodeURIComponent(matchId)}/state`);
|
|
412
|
+
}
|
|
413
|
+
else if (game === 'ufc') {
|
|
414
|
+
load('liveState', `/ufc/live/${encodeURIComponent(matchId)}/state`);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
partial.push(partialFromRejection('liveState', {
|
|
418
|
+
code: 'NOT_IMPLEMENTED',
|
|
419
|
+
message: game === 'lol'
|
|
420
|
+
? 'Pass gameId for LoL live window'
|
|
421
|
+
: `Live state not curated for ${game}`,
|
|
422
|
+
}));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (wanted.has('media')) {
|
|
426
|
+
if (game === 'cs2')
|
|
427
|
+
load('media', `/cs2/matches/${encodeURIComponent(matchId)}/demos`);
|
|
428
|
+
else
|
|
429
|
+
sections.media = { demos: [], vods: [], notes: 'No media endpoint for this game' };
|
|
430
|
+
}
|
|
431
|
+
if (wanted.has('advanced')) {
|
|
432
|
+
if (game === 'lol' && gameId)
|
|
433
|
+
load('advanced', `/lol/games/${encodeURIComponent(gameId)}/advanced`);
|
|
434
|
+
else if (game === 'lol') {
|
|
435
|
+
partial.push(partialFromRejection('advanced', {
|
|
436
|
+
code: 'VALIDATION',
|
|
437
|
+
message: 'gameId required for LoL advanced package',
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
partial.push(partialFromRejection('advanced', {
|
|
442
|
+
code: 'NOT_IMPLEMENTED',
|
|
443
|
+
message: `Advanced package not curated for ${game}`,
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
await Promise.all(tasks);
|
|
448
|
+
return successEnvelope({
|
|
449
|
+
source: 'match_details',
|
|
450
|
+
game,
|
|
451
|
+
requestId,
|
|
452
|
+
tookMs: Date.now() - started,
|
|
453
|
+
upstreamCalls,
|
|
454
|
+
rateLimit,
|
|
455
|
+
partial: partial.length ? partial : undefined,
|
|
456
|
+
entities: { games: [game], ids: { matchId, ...(gameId ? { gameId } : {}) } },
|
|
457
|
+
data: {
|
|
458
|
+
match: sections.base,
|
|
459
|
+
sections,
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
export const matchTools = [matchSummary, matchDetails];
|