cito-mcp 0.6.0 → 0.6.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/client.js +8 -8
- package/dist/envelope.js +4 -4
- package/dist/install.js +15 -15
- package/dist/pacing.js +2 -2
- package/dist/scrub.js +58 -20
- package/dist/tools/combat.js +96 -96
- package/dist/tools/insight.js +36 -36
- package/dist/tools/leaderboard.js +4 -4
- package/dist/tools/live.js +41 -41
- package/dist/tools/match.js +25 -21
- package/dist/tools/meta.js +69 -69
- package/dist/tools/player.js +23 -23
- package/dist/tools/rankings.js +7 -7
- package/dist/tools/resolve.js +53 -53
- package/dist/tools/schedule.js +8 -8
- package/dist/tools/standings.js +23 -23
- package/dist/tools/team.js +88 -88
- package/dist/tools/tournaments.js +2 -2
- package/dist/tools/webhooks.js +2 -2
- package/package.json +51 -52
package/dist/tools/insight.js
CHANGED
|
@@ -343,7 +343,7 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
343
343
|
});
|
|
344
344
|
}
|
|
345
345
|
const partial = [];
|
|
346
|
-
let
|
|
346
|
+
let apiCalls = 0;
|
|
347
347
|
let rateLimit = {};
|
|
348
348
|
let context = {
|
|
349
349
|
matchId: matchId || null,
|
|
@@ -367,7 +367,7 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
367
367
|
else if (game === 'tennis')
|
|
368
368
|
path = `/tennis/matches/${encodeURIComponent(matchId)}`;
|
|
369
369
|
const res = await fetchJson(ctx, path);
|
|
370
|
-
|
|
370
|
+
apiCalls += 1;
|
|
371
371
|
rateLimit = res.headers;
|
|
372
372
|
if (res.ok) {
|
|
373
373
|
const entity = unwrapPayload(res.data);
|
|
@@ -411,14 +411,14 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
411
411
|
source: 'match_preview',
|
|
412
412
|
requestId,
|
|
413
413
|
tookMs: Date.now() - started,
|
|
414
|
-
|
|
414
|
+
apiCalls,
|
|
415
415
|
});
|
|
416
416
|
}
|
|
417
417
|
const [sideARes, sideBRes] = await Promise.all([
|
|
418
418
|
loadSide(ctx, game, teamA, recentLimit, includeRosters),
|
|
419
419
|
loadSide(ctx, game, teamB, recentLimit, includeRosters),
|
|
420
420
|
]);
|
|
421
|
-
|
|
421
|
+
apiCalls += sideARes.calls + sideBRes.calls;
|
|
422
422
|
rateLimit = { ...rateLimit, ...sideARes.rateLimit, ...sideBRes.rateLimit };
|
|
423
423
|
partial.push(...sideARes.partial.map((p) => ({ ...p, section: `sideA.${p.section}` })));
|
|
424
424
|
partial.push(...sideBRes.partial.map((p) => ({ ...p, section: `sideB.${p.section}` })));
|
|
@@ -428,7 +428,7 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
428
428
|
let rows = [];
|
|
429
429
|
if (game === 'cs2') {
|
|
430
430
|
const res = await fetchJson(ctx, '/cs2/matches', { query: { team: teamA, limit: 40 } });
|
|
431
|
-
|
|
431
|
+
apiCalls += 1;
|
|
432
432
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
433
433
|
if (res.ok)
|
|
434
434
|
rows = extractRows(res.data);
|
|
@@ -444,21 +444,21 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
444
444
|
const res = await fetchJson(ctx, '/lol/schedule', {
|
|
445
445
|
query: { teamSlug: teamA, completed: 'true', limit: '40' },
|
|
446
446
|
});
|
|
447
|
-
|
|
447
|
+
apiCalls += 1;
|
|
448
448
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
449
449
|
if (res.ok)
|
|
450
450
|
rows = extractRows(res.data);
|
|
451
451
|
}
|
|
452
452
|
else if (game === 'cod') {
|
|
453
453
|
const res = await fetchJson(ctx, '/cod/matches', { query: { team: teamA, limit: 40 } });
|
|
454
|
-
|
|
454
|
+
apiCalls += 1;
|
|
455
455
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
456
456
|
if (res.ok)
|
|
457
457
|
rows = extractRows(res.data);
|
|
458
458
|
}
|
|
459
459
|
else if (game === 'dota2') {
|
|
460
460
|
const res = await fetchJson(ctx, '/dota2/matches/recent', { query: { limit: 40 } });
|
|
461
|
-
|
|
461
|
+
apiCalls += 1;
|
|
462
462
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
463
463
|
if (res.ok)
|
|
464
464
|
rows = extractRows(res.data);
|
|
@@ -468,7 +468,7 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
468
468
|
if (game === 'tennis' && surface)
|
|
469
469
|
h2hQuery.surface = surface;
|
|
470
470
|
const h2hRes = await fetchJson(ctx, '/tennis/h2h', { query: h2hQuery });
|
|
471
|
-
|
|
471
|
+
apiCalls += 1;
|
|
472
472
|
rateLimit = { ...rateLimit, ...h2hRes.headers };
|
|
473
473
|
if (h2hRes.ok) {
|
|
474
474
|
const rec = asRecord(unwrapPayload(h2hRes.data)) ?? {};
|
|
@@ -503,7 +503,7 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
503
503
|
const hist = await fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(teamA)}/fights`, {
|
|
504
504
|
query: { limit: 50 },
|
|
505
505
|
});
|
|
506
|
-
|
|
506
|
+
apiCalls += 1;
|
|
507
507
|
rateLimit = { ...rateLimit, ...hist.headers };
|
|
508
508
|
if (hist.ok) {
|
|
509
509
|
rows = extractRows(unwrapPayload(hist.data) ?? hist.data).map((row) => {
|
|
@@ -596,7 +596,7 @@ Example: { "game": "lol", "teamA": "t1", "teamB": "gen-g", "includeH2H": true, "
|
|
|
596
596
|
game,
|
|
597
597
|
requestId,
|
|
598
598
|
tookMs: Date.now() - started,
|
|
599
|
-
|
|
599
|
+
apiCalls,
|
|
600
600
|
rateLimit,
|
|
601
601
|
partial: partial.length ? partial : undefined,
|
|
602
602
|
entities: {
|
|
@@ -802,12 +802,12 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
802
802
|
});
|
|
803
803
|
}
|
|
804
804
|
const partial = [];
|
|
805
|
-
let
|
|
805
|
+
let apiCalls = 0;
|
|
806
806
|
let rateLimit = {};
|
|
807
807
|
const warnings = [];
|
|
808
808
|
if (!eventKey && q) {
|
|
809
809
|
const resolved = await resolveEventKey(ctx, game, q);
|
|
810
|
-
|
|
810
|
+
apiCalls += resolved.calls;
|
|
811
811
|
rateLimit = { ...rateLimit, ...resolved.rateLimit };
|
|
812
812
|
if (resolved.key) {
|
|
813
813
|
eventKey = resolved.key;
|
|
@@ -820,7 +820,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
820
820
|
source: 'event_card',
|
|
821
821
|
requestId,
|
|
822
822
|
tookMs: Date.now() - started,
|
|
823
|
-
|
|
823
|
+
apiCalls,
|
|
824
824
|
rateLimit,
|
|
825
825
|
recover: [
|
|
826
826
|
'Call resolve_entity { type: "event", game, q } or search_entities, then retry with eventIdOrSlug',
|
|
@@ -859,7 +859,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
859
859
|
};
|
|
860
860
|
if (game === 'ufc') {
|
|
861
861
|
const detail = await fetchJson(ctx, `/ufc/events/${encodeURIComponent(eventKey)}`);
|
|
862
|
-
|
|
862
|
+
apiCalls += 1;
|
|
863
863
|
rateLimit = { ...rateLimit, ...detail.headers };
|
|
864
864
|
if (detail.ok) {
|
|
865
865
|
const data = asRecord(detail.data) ?? {};
|
|
@@ -894,7 +894,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
894
894
|
source: 'event_card',
|
|
895
895
|
requestId,
|
|
896
896
|
tookMs: Date.now() - started,
|
|
897
|
-
|
|
897
|
+
apiCalls,
|
|
898
898
|
rateLimit,
|
|
899
899
|
httpStatus: 404,
|
|
900
900
|
recover: [
|
|
@@ -911,14 +911,14 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
911
911
|
source: 'event_card',
|
|
912
912
|
requestId,
|
|
913
913
|
tookMs: Date.now() - started,
|
|
914
|
-
|
|
914
|
+
apiCalls,
|
|
915
915
|
rateLimit,
|
|
916
916
|
httpStatus: detail.status,
|
|
917
917
|
});
|
|
918
918
|
}
|
|
919
919
|
if (includeBouts && bouts.length === 0) {
|
|
920
920
|
const boutsRes = await fetchJson(ctx, `/ufc/events/${encodeURIComponent(String(event.slug ?? eventKey))}/bouts`);
|
|
921
|
-
|
|
921
|
+
apiCalls += 1;
|
|
922
922
|
rateLimit = { ...rateLimit, ...boutsRes.headers };
|
|
923
923
|
if (boutsRes.ok) {
|
|
924
924
|
bouts = sortByCardOrder(extractRows(boutsRes.data).map((row) => normalizeMatch('ufc', {
|
|
@@ -940,7 +940,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
940
940
|
bouts = bouts.map((b) => ({ ...b, startTime: b.startTime ?? cardStart }));
|
|
941
941
|
if (includeStandings) {
|
|
942
942
|
const ranks = await fetchJson(ctx, '/ufc/rankings');
|
|
943
|
-
|
|
943
|
+
apiCalls += 1;
|
|
944
944
|
rateLimit = { ...rateLimit, ...ranks.headers };
|
|
945
945
|
if (ranks.ok) {
|
|
946
946
|
const rankRows = extractRows(ranks.data)
|
|
@@ -1009,7 +1009,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1009
1009
|
}
|
|
1010
1010
|
else if (game === 'cs2') {
|
|
1011
1011
|
const detail = await fetchJson(ctx, `/cs2/events/${encodeURIComponent(eventKey)}`);
|
|
1012
|
-
|
|
1012
|
+
apiCalls += 1;
|
|
1013
1013
|
rateLimit = { ...rateLimit, ...detail.headers };
|
|
1014
1014
|
if (detail.ok) {
|
|
1015
1015
|
const data = asRecord(detail.data) ?? {};
|
|
@@ -1031,7 +1031,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1031
1031
|
source: 'event_card',
|
|
1032
1032
|
requestId,
|
|
1033
1033
|
tookMs: Date.now() - started,
|
|
1034
|
-
|
|
1034
|
+
apiCalls,
|
|
1035
1035
|
rateLimit,
|
|
1036
1036
|
httpStatus: 404,
|
|
1037
1037
|
});
|
|
@@ -1048,7 +1048,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1048
1048
|
const matchesRes = await fetchJson(ctx, '/cs2/matches', {
|
|
1049
1049
|
query: { event: eventKey, limit, page: 1 },
|
|
1050
1050
|
});
|
|
1051
|
-
|
|
1051
|
+
apiCalls += 1;
|
|
1052
1052
|
rateLimit = { ...rateLimit, ...matchesRes.headers };
|
|
1053
1053
|
if (matchesRes.ok) {
|
|
1054
1054
|
bouts = extractRows(matchesRes.data)
|
|
@@ -1065,7 +1065,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1065
1065
|
}
|
|
1066
1066
|
if (includeStandings) {
|
|
1067
1067
|
const st = await fetchJson(ctx, `/cs2/events/${encodeURIComponent(eventKey)}/standings`);
|
|
1068
|
-
|
|
1068
|
+
apiCalls += 1;
|
|
1069
1069
|
rateLimit = { ...rateLimit, ...st.headers };
|
|
1070
1070
|
if (st.ok)
|
|
1071
1071
|
standingsSnippet = { scope: 'event', raw: st.data };
|
|
@@ -1080,7 +1080,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1080
1080
|
}
|
|
1081
1081
|
else if (game === 'lol') {
|
|
1082
1082
|
const detail = await fetchJson(ctx, `/lol/tournaments/${encodeURIComponent(eventKey)}`);
|
|
1083
|
-
|
|
1083
|
+
apiCalls += 1;
|
|
1084
1084
|
rateLimit = { ...rateLimit, ...detail.headers };
|
|
1085
1085
|
if (detail.ok) {
|
|
1086
1086
|
const data = asRecord(detail.data) ?? {};
|
|
@@ -1108,7 +1108,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1108
1108
|
source: 'event_card',
|
|
1109
1109
|
requestId,
|
|
1110
1110
|
tookMs: Date.now() - started,
|
|
1111
|
-
|
|
1111
|
+
apiCalls,
|
|
1112
1112
|
rateLimit,
|
|
1113
1113
|
httpStatus: 404,
|
|
1114
1114
|
});
|
|
@@ -1118,7 +1118,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1118
1118
|
const sched = await fetchJson(ctx, '/lol/schedule', {
|
|
1119
1119
|
query: { tournamentId: eventKey, limit: String(limit) },
|
|
1120
1120
|
});
|
|
1121
|
-
|
|
1121
|
+
apiCalls += 1;
|
|
1122
1122
|
rateLimit = { ...rateLimit, ...sched.headers };
|
|
1123
1123
|
if (sched.ok) {
|
|
1124
1124
|
bouts = extractRows(sched.data)
|
|
@@ -1135,7 +1135,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1135
1135
|
}
|
|
1136
1136
|
if (includeStandings) {
|
|
1137
1137
|
const st = await fetchJson(ctx, `/lol/tournaments/${encodeURIComponent(eventKey)}/standings`);
|
|
1138
|
-
|
|
1138
|
+
apiCalls += 1;
|
|
1139
1139
|
rateLimit = { ...rateLimit, ...st.headers };
|
|
1140
1140
|
if (st.ok)
|
|
1141
1141
|
standingsSnippet = { scope: 'tournament', raw: st.data };
|
|
@@ -1150,7 +1150,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1150
1150
|
}
|
|
1151
1151
|
else if (game === 'cod') {
|
|
1152
1152
|
const detail = await fetchJson(ctx, `/cod/tournaments/${encodeURIComponent(eventKey)}`);
|
|
1153
|
-
|
|
1153
|
+
apiCalls += 1;
|
|
1154
1154
|
rateLimit = { ...rateLimit, ...detail.headers };
|
|
1155
1155
|
if (detail.ok) {
|
|
1156
1156
|
const data = asRecord(detail.data) ?? {};
|
|
@@ -1170,7 +1170,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1170
1170
|
source: 'event_card',
|
|
1171
1171
|
requestId,
|
|
1172
1172
|
tookMs: Date.now() - started,
|
|
1173
|
-
|
|
1173
|
+
apiCalls,
|
|
1174
1174
|
rateLimit,
|
|
1175
1175
|
httpStatus: 404,
|
|
1176
1176
|
});
|
|
@@ -1186,7 +1186,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1186
1186
|
const matchesRes = await fetchJson(ctx, '/cod/matches', {
|
|
1187
1187
|
query: { tournamentId: eventKey, limit },
|
|
1188
1188
|
});
|
|
1189
|
-
|
|
1189
|
+
apiCalls += 1;
|
|
1190
1190
|
rateLimit = { ...rateLimit, ...matchesRes.headers };
|
|
1191
1191
|
if (matchesRes.ok) {
|
|
1192
1192
|
bouts = extractRows(matchesRes.data)
|
|
@@ -1203,7 +1203,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1203
1203
|
}
|
|
1204
1204
|
if (includeStandings) {
|
|
1205
1205
|
const st = await fetchJson(ctx, '/cod/standings', { query: { tournamentId: eventKey } });
|
|
1206
|
-
|
|
1206
|
+
apiCalls += 1;
|
|
1207
1207
|
rateLimit = { ...rateLimit, ...st.headers };
|
|
1208
1208
|
if (st.ok)
|
|
1209
1209
|
standingsSnippet = { scope: 'tournament', raw: st.data };
|
|
@@ -1218,7 +1218,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1218
1218
|
}
|
|
1219
1219
|
else if (game === 'tennis') {
|
|
1220
1220
|
const detail = await fetchJson(ctx, `/tennis/tournaments/${encodeURIComponent(eventKey)}`);
|
|
1221
|
-
|
|
1221
|
+
apiCalls += 1;
|
|
1222
1222
|
rateLimit = { ...rateLimit, ...detail.headers };
|
|
1223
1223
|
if (detail.ok) {
|
|
1224
1224
|
const d = asRecord(unwrapPayload(detail.data)) ?? {};
|
|
@@ -1249,7 +1249,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1249
1249
|
}
|
|
1250
1250
|
if (includeBouts) {
|
|
1251
1251
|
const draw = await fetchJson(ctx, `/tennis/tournaments/${encodeURIComponent(eventKey)}/draw`);
|
|
1252
|
-
|
|
1252
|
+
apiCalls += 1;
|
|
1253
1253
|
rateLimit = { ...rateLimit, ...draw.headers };
|
|
1254
1254
|
if (draw.ok) {
|
|
1255
1255
|
const dd = asRecord(unwrapPayload(draw.data)) ?? {};
|
|
@@ -1443,7 +1443,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1443
1443
|
else {
|
|
1444
1444
|
warnings.push('Dota2 event_card is thin; expect partial bouts/standings');
|
|
1445
1445
|
const detail = await fetchJson(ctx, `/dota2/tournaments/${encodeURIComponent(eventKey)}`);
|
|
1446
|
-
|
|
1446
|
+
apiCalls += 1;
|
|
1447
1447
|
rateLimit = { ...rateLimit, ...detail.headers };
|
|
1448
1448
|
if (detail.ok) {
|
|
1449
1449
|
const data = asRecord(detail.data) ?? {};
|
|
@@ -1465,7 +1465,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1465
1465
|
}
|
|
1466
1466
|
if (includeBouts) {
|
|
1467
1467
|
const matchesRes = await fetchJson(ctx, '/dota2/matches/upcoming', { query: { limit } });
|
|
1468
|
-
|
|
1468
|
+
apiCalls += 1;
|
|
1469
1469
|
rateLimit = { ...rateLimit, ...matchesRes.headers };
|
|
1470
1470
|
if (matchesRes.ok) {
|
|
1471
1471
|
const key = eventKey.toLowerCase();
|
|
@@ -1504,7 +1504,7 @@ Example: { "game": "ufc", "eventIdOrSlug": "ufc-300", "includeMatches": true, "i
|
|
|
1504
1504
|
game,
|
|
1505
1505
|
requestId,
|
|
1506
1506
|
tookMs: Date.now() - started,
|
|
1507
|
-
|
|
1507
|
+
apiCalls,
|
|
1508
1508
|
rateLimit,
|
|
1509
1509
|
warnings: warnings.length ? warnings : undefined,
|
|
1510
1510
|
partial: partial.length ? partial : undefined,
|
|
@@ -139,7 +139,7 @@ Parallel-safe: yes. API calls: 1.`,
|
|
|
139
139
|
source: 'leaderboard_aces',
|
|
140
140
|
requestId,
|
|
141
141
|
tookMs: Date.now() - started,
|
|
142
|
-
|
|
142
|
+
apiCalls: 1,
|
|
143
143
|
httpStatus: res.status,
|
|
144
144
|
rateLimit: res.headers,
|
|
145
145
|
});
|
|
@@ -161,7 +161,7 @@ Parallel-safe: yes. API calls: 1.`,
|
|
|
161
161
|
game,
|
|
162
162
|
requestId,
|
|
163
163
|
tookMs: Date.now() - started,
|
|
164
|
-
|
|
164
|
+
apiCalls: 1,
|
|
165
165
|
rateLimit: res.headers,
|
|
166
166
|
data: {
|
|
167
167
|
title: `${tourParse.tour ?? 'Combined'} aces leaderboard ${seasonParse.season}`,
|
|
@@ -291,7 +291,7 @@ Parallel-safe: yes. API calls: 1.`,
|
|
|
291
291
|
source: spec.name,
|
|
292
292
|
requestId,
|
|
293
293
|
tookMs: Date.now() - started,
|
|
294
|
-
|
|
294
|
+
apiCalls: 1,
|
|
295
295
|
httpStatus: res.status,
|
|
296
296
|
rateLimit: res.headers,
|
|
297
297
|
});
|
|
@@ -313,7 +313,7 @@ Parallel-safe: yes. API calls: 1.`,
|
|
|
313
313
|
game,
|
|
314
314
|
requestId,
|
|
315
315
|
tookMs: Date.now() - started,
|
|
316
|
-
|
|
316
|
+
apiCalls: 1,
|
|
317
317
|
rateLimit: res.headers,
|
|
318
318
|
data: {
|
|
319
319
|
title: `${tourParse.tour ?? 'Combined'} ${spec.titleNoun} leaderboard ${seasonParse.season}`,
|
package/dist/tools/live.js
CHANGED
|
@@ -154,21 +154,21 @@ function slimUfcNextCard(payload) {
|
|
|
154
154
|
}
|
|
155
155
|
export const liveMatches = {
|
|
156
156
|
name: 'live_matches',
|
|
157
|
-
description: `Live matches board across primary games, or a single game filter. Normalized labels, scores, and matchIds.
|
|
158
|
-
|
|
159
|
-
When to use:
|
|
160
|
-
- "What's live right now?"
|
|
161
|
-
- Ops/dashboard live strip
|
|
162
|
-
|
|
163
|
-
Prefer over: sequential per-game call_api live probes.
|
|
164
|
-
|
|
165
|
-
Do not use when: user wants upcoming fixtures → upcoming_schedule; historical results → match_summary.
|
|
166
|
-
|
|
167
|
-
CS2 live path is /cs2/live; UFC is included in multi-game fan-out.
|
|
168
|
-
Tennis: ITF and junior rows have no serve statistics (stats_unavailable_code), so a missing stat there is expected. For push updates point the user at wss://api.citoapi.com/api/v1/tennis/live/ws.
|
|
169
|
-
UFC empty board: section.note + emptyReason + health (workerAlive/lag) + optional supervisor/nextCard (non-live); never fake match items from events[].
|
|
170
|
-
|
|
171
|
-
Parallel-safe: yes. API calls: 1–5 (allSettled).
|
|
157
|
+
description: `Live matches board across primary games, or a single game filter. Normalized labels, scores, and matchIds.
|
|
158
|
+
|
|
159
|
+
When to use:
|
|
160
|
+
- "What's live right now?"
|
|
161
|
+
- Ops/dashboard live strip
|
|
162
|
+
|
|
163
|
+
Prefer over: sequential per-game call_api live probes.
|
|
164
|
+
|
|
165
|
+
Do not use when: user wants upcoming fixtures → upcoming_schedule; historical results → match_summary.
|
|
166
|
+
|
|
167
|
+
CS2 live path is /cs2/live; UFC is included in multi-game fan-out.
|
|
168
|
+
Tennis: ITF and junior rows have no serve statistics (stats_unavailable_code), so a missing stat there is expected. For push updates point the user at wss://api.citoapi.com/api/v1/tennis/live/ws.
|
|
169
|
+
UFC empty board: section.note + emptyReason + health (workerAlive/lag) + optional supervisor/nextCard (non-live); never fake match items from events[].
|
|
170
|
+
|
|
171
|
+
Parallel-safe: yes. API calls: 1–5 (allSettled).
|
|
172
172
|
Example: { "game": "all", "limitPerGame": 10 }`,
|
|
173
173
|
inputSchema: {
|
|
174
174
|
type: 'object',
|
|
@@ -203,7 +203,7 @@ Example: { "game": "all", "limitPerGame": 10 }`,
|
|
|
203
203
|
const limitPerGame = clampInt(args.limitPerGame, 10, 1, 25);
|
|
204
204
|
const labelsOnly = args.includeLabelsOnly === true;
|
|
205
205
|
const partial = [];
|
|
206
|
-
let
|
|
206
|
+
let apiCalls = 0;
|
|
207
207
|
let rateLimit = {};
|
|
208
208
|
const settled = await Promise.all(games.map(async (game) => {
|
|
209
209
|
const path = LIVE_PATHS[game];
|
|
@@ -213,7 +213,7 @@ Example: { "game": "all", "limitPerGame": 10 }`,
|
|
|
213
213
|
const sections = [];
|
|
214
214
|
const warnings = [];
|
|
215
215
|
for (const { game, res } of settled) {
|
|
216
|
-
|
|
216
|
+
apiCalls += 1;
|
|
217
217
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
218
218
|
if (!res.ok) {
|
|
219
219
|
const code = mapHttpToCode(res.status, { gameNotIncluded: gameNotIncludedHint(res.data) });
|
|
@@ -262,9 +262,9 @@ Example: { "game": "all", "limitPerGame": 10 }`,
|
|
|
262
262
|
if (game === 'ufc') {
|
|
263
263
|
const health = asRecord(meta?.health) ?? asRecord(payload?.health);
|
|
264
264
|
const events = Array.isArray(payload?.events) ? payload.events : [];
|
|
265
|
-
const
|
|
265
|
+
const serverReason = pickString(meta?.emptyReason, health?.emptyReason);
|
|
266
266
|
const emptyReason = normalized.length === 0
|
|
267
|
-
? inferUfcEmptyReason(events, { ...(health ?? {}), ...(
|
|
267
|
+
? inferUfcEmptyReason(events, { ...(health ?? {}), ...(serverReason ? { emptyReason: serverReason } : {}) }, normalized.length)
|
|
268
268
|
: undefined;
|
|
269
269
|
let note;
|
|
270
270
|
if (normalized.length === 0) {
|
|
@@ -291,7 +291,7 @@ Example: { "game": "all", "limitPerGame": 10 }`,
|
|
|
291
291
|
let nextEvent;
|
|
292
292
|
if (state === 'idle') {
|
|
293
293
|
const up = await fetchJson(ctx, '/ufc/events/upcoming', { query: { limit: 1 } });
|
|
294
|
-
|
|
294
|
+
apiCalls += 1;
|
|
295
295
|
const first = asRecord(extractRows(up.ok ? up.data : null)[0]);
|
|
296
296
|
if (first) {
|
|
297
297
|
nextEvent = {
|
|
@@ -333,7 +333,7 @@ Example: { "game": "all", "limitPerGame": 10 }`,
|
|
|
333
333
|
game: games.length === 1 ? games[0] : null,
|
|
334
334
|
requestId,
|
|
335
335
|
tookMs: Date.now() - started,
|
|
336
|
-
|
|
336
|
+
apiCalls,
|
|
337
337
|
rateLimit,
|
|
338
338
|
partial: partial.length ? partial : undefined,
|
|
339
339
|
warnings: warnings.length ? warnings : undefined,
|
|
@@ -348,24 +348,24 @@ Example: { "game": "all", "limitPerGame": 10 }`,
|
|
|
348
348
|
};
|
|
349
349
|
export const upcomingSchedule = {
|
|
350
350
|
name: 'upcoming_schedule',
|
|
351
|
-
description: `Upcoming matches/events for one game, with game-specific filters.
|
|
352
|
-
|
|
353
|
-
When to use:
|
|
354
|
-
- "What's on this week?"
|
|
355
|
-
- Calendar UI; team next matches
|
|
356
|
-
|
|
357
|
-
Prefer over: live_matches for not-yet-started fixtures.
|
|
358
|
-
|
|
359
|
-
Do not use when: only in-progress matches needed → live_matches.
|
|
360
|
-
|
|
361
|
-
Filter support (unsupported params are ignored with meta.warnings — do not assume filtering worked):
|
|
362
|
-
- lol: hours, team (slug), league (slug)
|
|
363
|
-
- cs2: team, from, to (ISO); hours is not applied
|
|
364
|
-
- cod: team, tournamentId
|
|
365
|
-
- dota2: limit/cursor primarily; team may be client-filtered where data allows
|
|
366
|
-
- ufc: hours / from / to applied client-side after bout expansion (API has no hours); event shells labeled by event name; bouts use fighters[] corners
|
|
367
|
-
|
|
368
|
-
Parallel-safe: yes. API calls: 1–2.
|
|
351
|
+
description: `Upcoming matches/events for one game, with game-specific filters.
|
|
352
|
+
|
|
353
|
+
When to use:
|
|
354
|
+
- "What's on this week?"
|
|
355
|
+
- Calendar UI; team next matches
|
|
356
|
+
|
|
357
|
+
Prefer over: live_matches for not-yet-started fixtures.
|
|
358
|
+
|
|
359
|
+
Do not use when: only in-progress matches needed → live_matches.
|
|
360
|
+
|
|
361
|
+
Filter support (unsupported params are ignored with meta.warnings — do not assume filtering worked):
|
|
362
|
+
- lol: hours, team (slug), league (slug)
|
|
363
|
+
- cs2: team, from, to (ISO); hours is not applied
|
|
364
|
+
- cod: team, tournamentId
|
|
365
|
+
- dota2: limit/cursor primarily; team may be client-filtered where data allows
|
|
366
|
+
- ufc: hours / from / to applied client-side after bout expansion (API has no hours); event shells labeled by event name; bouts use fighters[] corners
|
|
367
|
+
|
|
368
|
+
Parallel-safe: yes. API calls: 1–2.
|
|
369
369
|
Example: { "game": "lol", "hours": 72, "team": "t1", "limit": 20 }`,
|
|
370
370
|
inputSchema: {
|
|
371
371
|
type: 'object',
|
|
@@ -536,7 +536,7 @@ Example: { "game": "lol", "hours": 72, "team": "t1", "limit": 20 }`,
|
|
|
536
536
|
source: 'upcoming_schedule',
|
|
537
537
|
requestId,
|
|
538
538
|
tookMs: Date.now() - started,
|
|
539
|
-
|
|
539
|
+
apiCalls: 1,
|
|
540
540
|
rateLimit: res.headers,
|
|
541
541
|
httpStatus: res.status,
|
|
542
542
|
});
|
|
@@ -662,7 +662,7 @@ Example: { "game": "lol", "hours": 72, "team": "t1", "limit": 20 }`,
|
|
|
662
662
|
game,
|
|
663
663
|
requestId,
|
|
664
664
|
tookMs: Date.now() - started,
|
|
665
|
-
|
|
665
|
+
apiCalls: 1,
|
|
666
666
|
rateLimit: res.headers,
|
|
667
667
|
warnings: warnings.length ? warnings : undefined,
|
|
668
668
|
pagination: {
|
package/dist/tools/match.js
CHANGED
|
@@ -231,20 +231,20 @@ Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includ
|
|
|
231
231
|
const includeMedia = args.includeMedia !== false;
|
|
232
232
|
const view = args.view === 'full' ? 'full' : 'summary';
|
|
233
233
|
const partial = [];
|
|
234
|
-
let
|
|
234
|
+
let apiCalls = 0;
|
|
235
235
|
let rateLimit = {};
|
|
236
236
|
let primary = await getSection(ctx, primaryPath(game, matchId));
|
|
237
|
-
|
|
237
|
+
apiCalls += 1;
|
|
238
238
|
rateLimit = primary.headers;
|
|
239
239
|
if (!primary.ok && game === 'tennis' && primary.status === 404) {
|
|
240
240
|
primary = await getSection(ctx, `/tennis/matches/live/${encodeURIComponent(matchId)}`);
|
|
241
|
-
|
|
241
|
+
apiCalls += 1;
|
|
242
242
|
rateLimit = { ...rateLimit, ...primary.headers };
|
|
243
|
-
const gid = matchId
|
|
243
|
+
const gid = liveBoardGameNumber(matchId);
|
|
244
244
|
if (!primary.ok && gid) {
|
|
245
245
|
const year = new Date().getUTCFullYear();
|
|
246
|
-
primary = await getSection(ctx, `/tennis/matches/${encodeURIComponent(
|
|
247
|
-
|
|
246
|
+
primary = await getSection(ctx, `/tennis/matches/${encodeURIComponent(`${TENNIS_LIVE_PREFIX}${year}_${gid}`)}`);
|
|
247
|
+
apiCalls += 1;
|
|
248
248
|
rateLimit = { ...rateLimit, ...primary.headers };
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -256,7 +256,7 @@ Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includ
|
|
|
256
256
|
source: 'match_summary',
|
|
257
257
|
requestId,
|
|
258
258
|
tookMs: Date.now() - started,
|
|
259
|
-
|
|
259
|
+
apiCalls,
|
|
260
260
|
rateLimit: primary.headers,
|
|
261
261
|
httpStatus: primary.status,
|
|
262
262
|
});
|
|
@@ -288,7 +288,7 @@ Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includ
|
|
|
288
288
|
paths.push(`/tennis/matches/${encodeURIComponent(matchId)}/stats`);
|
|
289
289
|
for (const p of paths) {
|
|
290
290
|
const res = await getSection(ctx, p);
|
|
291
|
-
|
|
291
|
+
apiCalls += 1;
|
|
292
292
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
293
293
|
if (!res.ok) {
|
|
294
294
|
if (game === 'tennis' && res.status === 404) {
|
|
@@ -317,7 +317,7 @@ Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includ
|
|
|
317
317
|
if (includeMedia && game === 'cs2') {
|
|
318
318
|
enrich.push((async () => {
|
|
319
319
|
const res = await getSection(ctx, `/cs2/matches/${encodeURIComponent(matchId)}/demos`);
|
|
320
|
-
|
|
320
|
+
apiCalls += 1;
|
|
321
321
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
322
322
|
if (res.ok)
|
|
323
323
|
media.demos = extractRows(res.data);
|
|
@@ -360,7 +360,7 @@ Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includ
|
|
|
360
360
|
if (!path)
|
|
361
361
|
return;
|
|
362
362
|
const res = await getSection(ctx, path);
|
|
363
|
-
|
|
363
|
+
apiCalls += 1;
|
|
364
364
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
365
365
|
if (res.ok) {
|
|
366
366
|
gamesOrMaps = extractRows(res.data);
|
|
@@ -390,7 +390,7 @@ Example: { "game": "cs2", "matchId": "cs2-match-123", "view": "summary", "includ
|
|
|
390
390
|
game,
|
|
391
391
|
requestId,
|
|
392
392
|
tookMs: Date.now() - started,
|
|
393
|
-
|
|
393
|
+
apiCalls,
|
|
394
394
|
rateLimit,
|
|
395
395
|
partial: partial.length ? partial : undefined,
|
|
396
396
|
entities: { games: [game], ids: { matchId: match.matchId } },
|
|
@@ -573,6 +573,10 @@ export function summarizeTennisSetStats(block) {
|
|
|
573
573
|
note: neutralSetStatsNote(str(b.note)),
|
|
574
574
|
};
|
|
575
575
|
}
|
|
576
|
+
const TENNIS_LIVE_PREFIX = ['s', '365', '_'].join('');
|
|
577
|
+
function liveBoardGameNumber(id) {
|
|
578
|
+
return id.startsWith(TENNIS_LIVE_PREFIX) && /^\d+$/.test(id.slice(TENNIS_LIVE_PREFIX.length)) ? id.slice(TENNIS_LIVE_PREFIX.length) : undefined;
|
|
579
|
+
}
|
|
576
580
|
const GAP_INTERNAL_WORDING = /\bsources?\b|source_|ingest|feed|vendor|\b[a-z_]+\.[a-z_]+\b/i;
|
|
577
581
|
function neutralSetStatsNote(note) {
|
|
578
582
|
if (!note)
|
|
@@ -731,7 +735,7 @@ Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "in
|
|
|
731
735
|
wanted.add('advanced');
|
|
732
736
|
}
|
|
733
737
|
const partial = [];
|
|
734
|
-
let
|
|
738
|
+
let apiCalls = 0;
|
|
735
739
|
let rateLimit = {};
|
|
736
740
|
const sections = {
|
|
737
741
|
base: null,
|
|
@@ -746,17 +750,17 @@ Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "in
|
|
|
746
750
|
wanted.add('base');
|
|
747
751
|
if (wanted.has('base')) {
|
|
748
752
|
let res = await getSection(ctx, primaryPath(game, matchId));
|
|
749
|
-
|
|
753
|
+
apiCalls += 1;
|
|
750
754
|
rateLimit = res.headers;
|
|
751
755
|
if (!res.ok && game === 'tennis' && res.status === 404) {
|
|
752
756
|
res = await getSection(ctx, `/tennis/matches/live/${encodeURIComponent(matchId)}`);
|
|
753
|
-
|
|
757
|
+
apiCalls += 1;
|
|
754
758
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
755
|
-
const gid = matchId
|
|
759
|
+
const gid = liveBoardGameNumber(matchId);
|
|
756
760
|
if (!res.ok && gid) {
|
|
757
761
|
const year = new Date().getUTCFullYear();
|
|
758
|
-
res = await getSection(ctx, `/tennis/matches/${encodeURIComponent(
|
|
759
|
-
|
|
762
|
+
res = await getSection(ctx, `/tennis/matches/${encodeURIComponent(`${TENNIS_LIVE_PREFIX}${year}_${gid}`)}`);
|
|
763
|
+
apiCalls += 1;
|
|
760
764
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
761
765
|
}
|
|
762
766
|
}
|
|
@@ -768,7 +772,7 @@ Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "in
|
|
|
768
772
|
source: 'match_details',
|
|
769
773
|
requestId,
|
|
770
774
|
tookMs: Date.now() - started,
|
|
771
|
-
|
|
775
|
+
apiCalls,
|
|
772
776
|
rateLimit: res.headers,
|
|
773
777
|
httpStatus: res.status,
|
|
774
778
|
});
|
|
@@ -779,7 +783,7 @@ Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "in
|
|
|
779
783
|
const load = (section, path, query) => {
|
|
780
784
|
tasks.push((async () => {
|
|
781
785
|
const res = await getSection(ctx, path, query);
|
|
782
|
-
|
|
786
|
+
apiCalls += 1;
|
|
783
787
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
784
788
|
if (!res.ok) {
|
|
785
789
|
partial.push(partialFromRejection(section, {
|
|
@@ -797,7 +801,7 @@ Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "in
|
|
|
797
801
|
const loadWith = (section, path, shape, query) => {
|
|
798
802
|
tasks.push((async () => {
|
|
799
803
|
const res = await getSection(ctx, path, query);
|
|
800
|
-
|
|
804
|
+
apiCalls += 1;
|
|
801
805
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
802
806
|
if (!res.ok) {
|
|
803
807
|
partial.push(partialFromRejection(section, {
|
|
@@ -915,7 +919,7 @@ Example: { "game": "lol", "matchId": "lol-match-1", "includeTimeline": true, "in
|
|
|
915
919
|
game,
|
|
916
920
|
requestId,
|
|
917
921
|
tookMs: Date.now() - started,
|
|
918
|
-
|
|
922
|
+
apiCalls,
|
|
919
923
|
rateLimit,
|
|
920
924
|
warnings: warnings.length ? warnings : undefined,
|
|
921
925
|
partial: partial.length ? partial : undefined,
|