@claudinho/cli 0.9.3 → 0.9.4
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 +11 -3
- package/dist/index.js +1443 -513
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -154,10 +154,11 @@ var ALIASES = [
|
|
|
154
154
|
["Burma", "MM"],
|
|
155
155
|
["Cape Verde", "CV"]
|
|
156
156
|
];
|
|
157
|
-
var BY_NATION = Object.
|
|
158
|
-
|
|
157
|
+
var BY_NATION = Object.assign(
|
|
158
|
+
/* @__PURE__ */ Object.create(null),
|
|
159
|
+
Object.fromEntries([...NATIONS, ...ALIASES].map(([name, code]) => [norm(name), code]))
|
|
159
160
|
);
|
|
160
|
-
var BY_CODE = {
|
|
161
|
+
var BY_CODE = Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
161
162
|
MEX: "MX",
|
|
162
163
|
RSA: "ZA",
|
|
163
164
|
KOR: "KR",
|
|
@@ -218,7 +219,7 @@ var BY_CODE = {
|
|
|
218
219
|
HON: "HN",
|
|
219
220
|
COD: "CD",
|
|
220
221
|
MLI: "ML"
|
|
221
|
-
};
|
|
222
|
+
});
|
|
222
223
|
function nationToFlag(nameOrCode) {
|
|
223
224
|
const region = nationToRegion(nameOrCode);
|
|
224
225
|
return region ? flagEmoji(region) : NEUTRAL;
|
|
@@ -226,7 +227,7 @@ function nationToFlag(nameOrCode) {
|
|
|
226
227
|
var INTL_BY_NAME;
|
|
227
228
|
function intlNameMap() {
|
|
228
229
|
if (INTL_BY_NAME) return INTL_BY_NAME;
|
|
229
|
-
const map =
|
|
230
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
230
231
|
try {
|
|
231
232
|
const dn = new Intl.DisplayNames(["en"], { type: "region" });
|
|
232
233
|
for (let a = 65; a <= 90; a++) {
|
|
@@ -251,11 +252,8 @@ function intlNameMap() {
|
|
|
251
252
|
}
|
|
252
253
|
function nationToRegion(nameOrCode) {
|
|
253
254
|
if (!nameOrCode) return void 0;
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
const byCode = BY_CODE[nameOrCode.trim().toUpperCase()];
|
|
257
|
-
if (byCode) return byCode;
|
|
258
|
-
return intlNameMap()[norm(nameOrCode)];
|
|
255
|
+
const str = (v) => typeof v === "string" && v ? v : void 0;
|
|
256
|
+
return str(BY_NATION[norm(nameOrCode)]) ?? str(BY_CODE[nameOrCode.trim().toUpperCase()]) ?? str(intlNameMap()[norm(nameOrCode)]);
|
|
259
257
|
}
|
|
260
258
|
var EN = {
|
|
261
259
|
"bracket.title": "Knockout bracket",
|
|
@@ -275,6 +273,7 @@ var EN = {
|
|
|
275
273
|
"bracket.slot.loser": "{stage} {n} loser",
|
|
276
274
|
"bracket.slot.tbd": "TBD",
|
|
277
275
|
"live.data": "Live data: {source}",
|
|
276
|
+
"standings.unavailable": "Live standings unavailable.",
|
|
278
277
|
"share.tryIt": "Try it: {line}",
|
|
279
278
|
"stage.group": "Group {group}",
|
|
280
279
|
"stage.groupStage": "Group stage",
|
|
@@ -304,6 +303,7 @@ var ES = {
|
|
|
304
303
|
"bracket.slot.loser": "Perdedor {stage} {n}",
|
|
305
304
|
"bracket.slot.tbd": "Por definir",
|
|
306
305
|
"live.data": "Datos en vivo: {source}",
|
|
306
|
+
"standings.unavailable": "Tabla en vivo no disponible.",
|
|
307
307
|
"share.tryIt": "Pru\xE9balo: {line}",
|
|
308
308
|
"stage.group": "Grupo {group}",
|
|
309
309
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -333,6 +333,7 @@ var PT = {
|
|
|
333
333
|
"bracket.slot.loser": "Perdedor {stage} {n}",
|
|
334
334
|
"bracket.slot.tbd": "A definir",
|
|
335
335
|
"live.data": "Dados ao vivo: {source}",
|
|
336
|
+
"standings.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
|
|
336
337
|
"share.tryIt": "Experimente: {line}",
|
|
337
338
|
"stage.group": "Grupo {group}",
|
|
338
339
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -362,6 +363,7 @@ var FR = {
|
|
|
362
363
|
"bracket.slot.loser": "Perdant {stage} {n}",
|
|
363
364
|
"bracket.slot.tbd": "\xC0 d\xE9finir",
|
|
364
365
|
"live.data": "Donn\xE9es en direct : {source}",
|
|
366
|
+
"standings.unavailable": "Classement en direct indisponible.",
|
|
365
367
|
"share.tryIt": "Essayez : {line}",
|
|
366
368
|
"stage.group": "Groupe {group}",
|
|
367
369
|
"stage.groupStage": "Phase de groupes",
|
|
@@ -446,7 +448,14 @@ function safeLocale(locale) {
|
|
|
446
448
|
return "en";
|
|
447
449
|
}
|
|
448
450
|
}
|
|
451
|
+
function parsedDate(iso) {
|
|
452
|
+
const t2 = Date.parse(iso);
|
|
453
|
+
return Number.isFinite(t2) ? new Date(t2) : void 0;
|
|
454
|
+
}
|
|
455
|
+
var UNKNOWN_TIME = "\u2014";
|
|
449
456
|
function formatKickoff(iso, opts = {}) {
|
|
457
|
+
const when = parsedDate(iso);
|
|
458
|
+
if (!when) return UNKNOWN_TIME;
|
|
450
459
|
const tz = resolveTz(opts.tz);
|
|
451
460
|
const locale = safeLocale(opts.locale);
|
|
452
461
|
return new Intl.DateTimeFormat(locale, {
|
|
@@ -456,18 +465,22 @@ function formatKickoff(iso, opts = {}) {
|
|
|
456
465
|
minute: "2-digit",
|
|
457
466
|
hour12: false,
|
|
458
467
|
timeZone: tz
|
|
459
|
-
}).format(
|
|
468
|
+
}).format(when);
|
|
460
469
|
}
|
|
461
470
|
function formatDate(iso, opts = {}) {
|
|
471
|
+
const when = parsedDate(iso);
|
|
472
|
+
if (!when) return UNKNOWN_TIME;
|
|
462
473
|
const tz = resolveTz(opts.tz);
|
|
463
474
|
const locale = safeLocale(opts.locale);
|
|
464
475
|
return new Intl.DateTimeFormat(locale, {
|
|
465
476
|
month: "short",
|
|
466
477
|
day: "numeric",
|
|
467
478
|
timeZone: tz
|
|
468
|
-
}).format(
|
|
479
|
+
}).format(when);
|
|
469
480
|
}
|
|
470
481
|
function formatTime(iso, opts = {}) {
|
|
482
|
+
const when = parsedDate(iso);
|
|
483
|
+
if (!when) return UNKNOWN_TIME;
|
|
471
484
|
const tz = resolveTz(opts.tz);
|
|
472
485
|
const locale = safeLocale(opts.locale);
|
|
473
486
|
return new Intl.DateTimeFormat(locale, {
|
|
@@ -475,10 +488,12 @@ function formatTime(iso, opts = {}) {
|
|
|
475
488
|
minute: "2-digit",
|
|
476
489
|
hour12: false,
|
|
477
490
|
timeZone: tz
|
|
478
|
-
}).format(
|
|
491
|
+
}).format(when);
|
|
479
492
|
}
|
|
480
493
|
function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
481
|
-
const
|
|
494
|
+
const when = parsedDate(iso);
|
|
495
|
+
if (!when) return UNKNOWN_TIME;
|
|
496
|
+
const ms = when.getTime() - from.getTime();
|
|
482
497
|
if (ms <= 0) return "now";
|
|
483
498
|
const totalMin = Math.floor(ms / 6e4);
|
|
484
499
|
const days = Math.floor(totalMin / 1440);
|
|
@@ -489,71 +504,56 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
|
489
504
|
return `${mins}m`;
|
|
490
505
|
}
|
|
491
506
|
function localDate(iso, tz) {
|
|
507
|
+
const when = parsedDate(iso);
|
|
508
|
+
if (!when) return "";
|
|
492
509
|
const zone = resolveTz(tz);
|
|
493
510
|
return new Intl.DateTimeFormat("en-CA", {
|
|
494
511
|
year: "numeric",
|
|
495
512
|
month: "2-digit",
|
|
496
513
|
day: "2-digit",
|
|
497
514
|
timeZone: zone
|
|
498
|
-
}).format(
|
|
515
|
+
}).format(when);
|
|
499
516
|
}
|
|
500
517
|
function shiftUtcDate(dateISO, days) {
|
|
501
518
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
502
519
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
503
520
|
}
|
|
504
|
-
var FEED_TEXT_MAX = 100;
|
|
505
|
-
function sanitizeFeedText(value, max = FEED_TEXT_MAX) {
|
|
506
|
-
let out2 = "";
|
|
507
|
-
let count = 0;
|
|
508
|
-
for (const ch of String(value)) {
|
|
509
|
-
const cp = ch.codePointAt(0) ?? 0;
|
|
510
|
-
const isWhitespaceControl = cp === 9 || cp === 10 || cp === 13;
|
|
511
|
-
if ((cp <= 31 || cp >= 127 && cp <= 159) && !isWhitespaceControl) continue;
|
|
512
|
-
if (count >= max) break;
|
|
513
|
-
out2 += isWhitespaceControl ? " " : ch;
|
|
514
|
-
count++;
|
|
515
|
-
}
|
|
516
|
-
return out2;
|
|
517
|
-
}
|
|
518
|
-
function sanitizeTeam(t2) {
|
|
519
|
-
return {
|
|
520
|
-
...t2 ?? {},
|
|
521
|
-
code: sanitizeFeedText(t2?.code ?? ""),
|
|
522
|
-
name: sanitizeFeedText(t2?.name ?? ""),
|
|
523
|
-
flag: sanitizeFeedText(t2?.flag ?? "")
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
function finiteOrUndefined(v) {
|
|
527
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
528
|
-
}
|
|
529
|
-
function sanitizeScorePair(v) {
|
|
530
|
-
const home = finiteOrUndefined(v?.home);
|
|
531
|
-
const away = finiteOrUndefined(v?.away);
|
|
532
|
-
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
533
|
-
}
|
|
534
|
-
function sanitizeMatchStrings(m) {
|
|
535
|
-
const score = sanitizeScorePair(m.score);
|
|
536
|
-
return {
|
|
537
|
-
...m,
|
|
538
|
-
venue: sanitizeFeedText(m.venue ?? ""),
|
|
539
|
-
city: m.city == null ? m.city : sanitizeFeedText(m.city),
|
|
540
|
-
country: m.country == null ? m.country : sanitizeFeedText(m.country),
|
|
541
|
-
home: sanitizeTeam(m.home),
|
|
542
|
-
away: sanitizeTeam(m.away),
|
|
543
|
-
score,
|
|
544
|
-
shootout: score ? sanitizeScorePair(m.shootout) : void 0,
|
|
545
|
-
minute: finiteOrUndefined(m.minute)
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
521
|
var segmenter = new Intl.Segmenter();
|
|
549
522
|
var WIDE_CLUSTER = new RegExp("^(?:\\p{Regional_Indicator}|\\p{Extended_Pictographic})", "u");
|
|
523
|
+
var WIDE_BASE = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-ꥠ-가-힣豈-︐-︙︰--⦆¢-₩\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}]/u;
|
|
524
|
+
var KEYCAP = /\u{20E3}/u;
|
|
525
|
+
var ZERO_WIDTH_BASE = /[\p{Mn}\p{Me}\p{Cf}\p{Cc}]/u;
|
|
526
|
+
function clusterWidth(segment) {
|
|
527
|
+
if (WIDE_CLUSTER.test(segment) || KEYCAP.test(segment)) return 2;
|
|
528
|
+
const first = segment.codePointAt(0);
|
|
529
|
+
if (first === void 0) return 0;
|
|
530
|
+
const base = String.fromCodePoint(first);
|
|
531
|
+
if (ZERO_WIDTH_BASE.test(base)) return 0;
|
|
532
|
+
return WIDE_BASE.test(base) ? 2 : 1;
|
|
533
|
+
}
|
|
550
534
|
function displayWidth(s) {
|
|
551
535
|
let w = 0;
|
|
552
536
|
for (const { segment } of segmenter.segment(s)) {
|
|
553
|
-
w +=
|
|
537
|
+
w += clusterWidth(segment);
|
|
554
538
|
}
|
|
555
539
|
return w;
|
|
556
540
|
}
|
|
541
|
+
function truncateVisible(s, maxColumns, marker = "\u2026") {
|
|
542
|
+
if (displayWidth(s) <= maxColumns) return s;
|
|
543
|
+
const budget = Math.max(0, maxColumns - displayWidth(marker));
|
|
544
|
+
let out2 = "";
|
|
545
|
+
let w = 0;
|
|
546
|
+
for (const { segment } of segmenter.segment(s)) {
|
|
547
|
+
const cw = clusterWidth(segment);
|
|
548
|
+
if (w + cw > budget) break;
|
|
549
|
+
out2 += segment;
|
|
550
|
+
w += cw;
|
|
551
|
+
}
|
|
552
|
+
return out2 + marker;
|
|
553
|
+
}
|
|
554
|
+
function* graphemes(s) {
|
|
555
|
+
for (const { segment } of segmenter.segment(s)) yield segment;
|
|
556
|
+
}
|
|
557
557
|
function padVisible(s, width) {
|
|
558
558
|
const w = displayWidth(s);
|
|
559
559
|
return w >= width ? s : s + " ".repeat(width - w);
|
|
@@ -2953,47 +2953,285 @@ function rosterAtZero(matches) {
|
|
|
2953
2953
|
}
|
|
2954
2954
|
return [...teams.values()].sort((a, b) => a.name.localeCompare(b.name)).map(blankRow);
|
|
2955
2955
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
var USER_AGENT = `claudinho/${"0.9.3"} (+https://github.com/arturogarrido/claudinho)`;
|
|
2960
|
-
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
2961
|
-
function competitionBase(slug) {
|
|
2962
|
-
return `${ESPN_SOCCER}/${slug}`;
|
|
2956
|
+
function takeBounded(value, max) {
|
|
2957
|
+
if (!Array.isArray(value)) return [];
|
|
2958
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
2963
2959
|
}
|
|
2964
|
-
var
|
|
2965
|
-
var
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2960
|
+
var valid = (value) => ({ kind: "valid", value });
|
|
2961
|
+
var definitiveNone = (reason) => ({
|
|
2962
|
+
kind: "definitive-none",
|
|
2963
|
+
reason
|
|
2964
|
+
});
|
|
2965
|
+
var malformed = (reason) => ({ kind: "malformed", reason });
|
|
2966
|
+
var ambiguous = (reason) => ({ kind: "ambiguous", reason });
|
|
2967
|
+
var unresolved = (reason) => ({ kind: "unresolved", reason });
|
|
2968
|
+
function parsedValue(r) {
|
|
2969
|
+
return r.kind === "valid" ? r.value : void 0;
|
|
2970
|
+
}
|
|
2971
|
+
function isCacheable(r) {
|
|
2972
|
+
return r.kind === "valid" || r.kind === "definitive-none" || r.kind === "ambiguous";
|
|
2973
|
+
}
|
|
2974
|
+
var FORBIDDEN_IN_LABEL = new RegExp("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Cs}\\p{Co}\\p{Cn}]|\\p{Default_Ignorable_Code_Point}", "u");
|
|
2975
|
+
var EMOJI_IN_LABEL = new RegExp("\\p{Extended_Pictographic}|\\p{Regional_Indicator}|\\p{Emoji_Modifier}|\\u{20E3}", "u");
|
|
2976
|
+
var MAX_LABEL_INPUT_UNITS = 4096;
|
|
2977
|
+
var MAX_LABEL_COLUMNS = 100;
|
|
2978
|
+
function humanLabel(value, maxColumns = MAX_LABEL_COLUMNS) {
|
|
2979
|
+
if (typeof value !== "string" || value === "") return "";
|
|
2980
|
+
const capped = value.length > MAX_LABEL_INPUT_UNITS ? value.slice(0, MAX_LABEL_INPUT_UNITS) : value;
|
|
2981
|
+
return visible(runToFixedPoint(capped, maxColumns));
|
|
2982
|
+
}
|
|
2983
|
+
function visible(label) {
|
|
2984
|
+
return label !== "" && displayWidth(label) === 0 ? "" : label;
|
|
2985
|
+
}
|
|
2986
|
+
function runToFixedPoint(capped, maxColumns) {
|
|
2987
|
+
const first = sealLabelOnce(capped, maxColumns);
|
|
2988
|
+
if (!lastPassDropped) return first;
|
|
2989
|
+
let out2 = first;
|
|
2990
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
2991
|
+
const again = sealLabelOnce(out2, maxColumns);
|
|
2992
|
+
if (again === out2) return out2;
|
|
2993
|
+
out2 = again;
|
|
2994
|
+
}
|
|
2995
|
+
return sealLabelOnce(out2, maxColumns) === out2 ? out2 : "";
|
|
2996
|
+
}
|
|
2997
|
+
var lastPassDropped = false;
|
|
2998
|
+
function sealLabelOnce(value, maxColumns) {
|
|
2999
|
+
lastPassDropped = false;
|
|
3000
|
+
let normalized;
|
|
3001
|
+
try {
|
|
3002
|
+
normalized = value.normalize("NFC");
|
|
3003
|
+
} catch {
|
|
3004
|
+
return "";
|
|
2974
3005
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3006
|
+
const maxCodePoints = Math.max(16, maxColumns * 4);
|
|
3007
|
+
let out2 = "";
|
|
3008
|
+
let width = 0;
|
|
3009
|
+
let points = 0;
|
|
3010
|
+
for (const cluster of graphemes(normalized)) {
|
|
3011
|
+
if ([...cluster].length > 8) {
|
|
3012
|
+
lastPassDropped = true;
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
3015
|
+
if (EMOJI_IN_LABEL.test(cluster)) {
|
|
3016
|
+
lastPassDropped = true;
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
let piece = "";
|
|
3020
|
+
for (const ch of cluster) {
|
|
3021
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
3022
|
+
if (cp === 9 || cp === 10 || cp === 13) {
|
|
3023
|
+
piece += " ";
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
if (FORBIDDEN_IN_LABEL.test(ch)) {
|
|
3027
|
+
lastPassDropped = true;
|
|
3028
|
+
continue;
|
|
3029
|
+
}
|
|
3030
|
+
piece += ch;
|
|
3031
|
+
}
|
|
3032
|
+
if (!piece) {
|
|
3033
|
+
lastPassDropped = true;
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
const w = displayWidth(piece);
|
|
3037
|
+
const cps = [...piece].length;
|
|
3038
|
+
if (width + w > maxColumns || points + cps > maxCodePoints) break;
|
|
3039
|
+
out2 += piece;
|
|
3040
|
+
width += w;
|
|
3041
|
+
points += cps;
|
|
2978
3042
|
}
|
|
2979
|
-
|
|
3043
|
+
try {
|
|
3044
|
+
return out2.normalize("NFC").trim();
|
|
3045
|
+
} catch {
|
|
3046
|
+
return out2.trim();
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
function opaqueId(value, grammar) {
|
|
3050
|
+
return typeof value === "string" && grammar.test(value) ? value : void 0;
|
|
3051
|
+
}
|
|
3052
|
+
var ESPN_ID = /^[0-9]{1,20}$/;
|
|
3053
|
+
var ISO_INSTANT = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:[Zz]|[+-]\d{2}(?::?\d{2})?)$/;
|
|
3054
|
+
var ISO_CANONICAL = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
3055
|
+
function calendarValid(iso) {
|
|
3056
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
|
|
3057
|
+
if (!m) return false;
|
|
3058
|
+
const year = Number(m[1]);
|
|
3059
|
+
const month = Number(m[2]);
|
|
3060
|
+
const day = Number(m[3]);
|
|
3061
|
+
if (month < 1 || month > 12 || day < 1) return false;
|
|
3062
|
+
return day <= new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
3063
|
+
}
|
|
3064
|
+
function canonicalTimestamp(value) {
|
|
3065
|
+
if (typeof value !== "string" || !ISO_INSTANT.test(value)) return void 0;
|
|
3066
|
+
if (!calendarValid(value)) return void 0;
|
|
3067
|
+
const t2 = Date.parse(value);
|
|
3068
|
+
if (!Number.isFinite(t2)) return void 0;
|
|
3069
|
+
const out2 = new Date(t2).toISOString();
|
|
3070
|
+
return ISO_CANONICAL.test(out2) ? out2 : void 0;
|
|
3071
|
+
}
|
|
3072
|
+
function productFlag(nameOrCode) {
|
|
3073
|
+
return nationToFlag(nameOrCode);
|
|
3074
|
+
}
|
|
3075
|
+
function count(value, max) {
|
|
3076
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max ? value : void 0;
|
|
3077
|
+
}
|
|
3078
|
+
function quantity(value) {
|
|
3079
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
3080
|
+
}
|
|
3081
|
+
function probability(value) {
|
|
3082
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1 ? value : void 0;
|
|
3083
|
+
}
|
|
3084
|
+
function member(value, allowed) {
|
|
3085
|
+
return typeof value === "string" && allowed.has(value) ? value : void 0;
|
|
3086
|
+
}
|
|
3087
|
+
function flag(value) {
|
|
3088
|
+
return typeof value === "boolean" ? value : void 0;
|
|
3089
|
+
}
|
|
3090
|
+
var MAX_GOALS = 99;
|
|
3091
|
+
var MAX_MINUTE = 200;
|
|
3092
|
+
var MAX_MATCH_EVENTS = 128;
|
|
3093
|
+
var TEAM_CODE_COLUMNS = 8;
|
|
3094
|
+
var STAGES = /* @__PURE__ */ new Set(["GROUP", "R32", "R16", "QF", "SF", "3P", "F", "FRIENDLY"]);
|
|
3095
|
+
var STATUSES = /* @__PURE__ */ new Set(["SCHEDULED", "LIVE", "HT", "FT", "POSTPONED", "CANCELLED"]);
|
|
3096
|
+
var EVENT_TYPES = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN", "YELLOW", "RED", "SUB"]);
|
|
3097
|
+
function teamCode(raw, fallbackName) {
|
|
3098
|
+
const upper = typeof raw === "string" ? raw.toUpperCase() : raw;
|
|
3099
|
+
const stated = humanLabel(upper, TEAM_CODE_COLUMNS);
|
|
3100
|
+
if (stated) return stated;
|
|
3101
|
+
return humanLabel([...fallbackName].slice(0, 3).join("").toUpperCase(), TEAM_CODE_COLUMNS);
|
|
3102
|
+
}
|
|
3103
|
+
function sealTeam(raw) {
|
|
3104
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3105
|
+
const t2 = raw;
|
|
3106
|
+
const name = humanLabel(t2.name);
|
|
3107
|
+
if (!name) return void 0;
|
|
3108
|
+
const code = teamCode(t2.code, name);
|
|
3109
|
+
return { code, name, flag: productFlag(name) };
|
|
3110
|
+
}
|
|
3111
|
+
function sealScorePair(raw) {
|
|
3112
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3113
|
+
const v = raw;
|
|
3114
|
+
const home = count(v.home, MAX_GOALS);
|
|
3115
|
+
const away = count(v.away, MAX_GOALS);
|
|
3116
|
+
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
3117
|
+
}
|
|
3118
|
+
function sealEvent(raw) {
|
|
3119
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3120
|
+
const e = raw;
|
|
3121
|
+
const type = member(e.type, EVENT_TYPES);
|
|
3122
|
+
if (!type) return void 0;
|
|
3123
|
+
const minute = count(e.minute, MAX_MINUTE);
|
|
3124
|
+
if (minute === void 0) return void 0;
|
|
3125
|
+
const out2 = { type, minute, teamCode: humanLabel(e.teamCode, TEAM_CODE_COLUMNS) };
|
|
3126
|
+
const player = humanLabel(e.player);
|
|
3127
|
+
if (player) out2.player = player;
|
|
3128
|
+
return out2;
|
|
3129
|
+
}
|
|
3130
|
+
function sealMatch(parts, opts = {}) {
|
|
3131
|
+
if (!parts || typeof parts !== "object") return malformed("match is not an object");
|
|
3132
|
+
const id = opaqueId(parts.id, ESPN_ID);
|
|
3133
|
+
if (!id) return malformed("match id is absent or not an identifier");
|
|
3134
|
+
const kickoff = canonicalTimestamp(parts.kickoff);
|
|
3135
|
+
if (!kickoff) return malformed("match kickoff is absent or not one instant");
|
|
3136
|
+
const stage = member(parts.stage, STAGES);
|
|
3137
|
+
if (!stage) return malformed("match stage is not a known stage");
|
|
3138
|
+
const status = member(parts.status, STATUSES);
|
|
3139
|
+
if (!status) return malformed("match status is not a known status");
|
|
3140
|
+
const home = sealTeam(parts.home);
|
|
3141
|
+
const away = sealTeam(parts.away);
|
|
3142
|
+
if (!home || !away) return malformed("match does not name both teams");
|
|
3143
|
+
if (home.code === away.code && home.name === away.name) {
|
|
3144
|
+
return definitiveNone("both competitors are the same team");
|
|
3145
|
+
}
|
|
3146
|
+
const group = humanLabel(parts.group) || void 0;
|
|
3147
|
+
const city = humanLabel(parts.city) || void 0;
|
|
3148
|
+
const country = humanLabel(parts.country) || void 0;
|
|
3149
|
+
const score = status === "SCHEDULED" ? void 0 : sealScorePair(parts.score);
|
|
3150
|
+
if ((status === "LIVE" || status === "HT" || status === "FT") && !score) {
|
|
3151
|
+
return malformed("match claims an unreadable score");
|
|
3152
|
+
}
|
|
3153
|
+
const finished = status === "FT";
|
|
3154
|
+
const canGoToPenalties = stage !== "GROUP";
|
|
3155
|
+
const shootoutPresent = parts.shootout !== void 0 && parts.shootout !== null;
|
|
3156
|
+
const parsedShootout = shootoutPresent ? sealScorePair(parts.shootout) : void 0;
|
|
3157
|
+
const shootoutStatus = status === "LIVE" || status === "FT";
|
|
3158
|
+
const shootout = parsedShootout && score && canGoToPenalties && shootoutStatus && !(finished && parsedShootout.home === parsedShootout.away) ? parsedShootout : void 0;
|
|
3159
|
+
const claimedWinner = teamCode(parts.winnerCode, "");
|
|
3160
|
+
const claimedSide = claimedWinner === home.code ? "home" : claimedWinner === away.code ? "away" : void 0;
|
|
3161
|
+
let winnerCode;
|
|
3162
|
+
const unusableShootoutCouldDecide = canGoToPenalties && shootoutPresent && !shootout;
|
|
3163
|
+
if (claimedSide && score && finished && !unusableShootoutCouldDecide) {
|
|
3164
|
+
const level = score.home === score.away;
|
|
3165
|
+
const decider = shootout ?? score;
|
|
3166
|
+
if (decider && decider.home !== decider.away) {
|
|
3167
|
+
if ((decider.home > decider.away ? "home" : "away") === claimedSide) winnerCode = claimedWinner;
|
|
3168
|
+
} else if (level && !shootoutPresent && canGoToPenalties) {
|
|
3169
|
+
winnerCode = claimedWinner;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
const events = opts.events === false ? [] : takeBounded(parts.events, MAX_MATCH_EVENTS).map(sealEvent).filter((e) => !!e);
|
|
3173
|
+
const out2 = { id, stage };
|
|
3174
|
+
if (group) out2.group = group;
|
|
3175
|
+
out2.kickoff = kickoff;
|
|
3176
|
+
out2.venue = humanLabel(parts.venue);
|
|
3177
|
+
if (city) out2.city = city;
|
|
3178
|
+
if (country) out2.country = country;
|
|
3179
|
+
out2.home = home;
|
|
3180
|
+
out2.away = away;
|
|
3181
|
+
if (score) out2.score = score;
|
|
3182
|
+
if (shootout) out2.shootout = shootout;
|
|
3183
|
+
const minute = count(parts.minute, MAX_MINUTE);
|
|
3184
|
+
if (minute !== void 0) out2.minute = minute;
|
|
3185
|
+
out2.status = status;
|
|
3186
|
+
if (events.length) out2.events = events;
|
|
3187
|
+
if (winnerCode) out2.winnerCode = winnerCode;
|
|
3188
|
+
out2.updatedAt = canonicalTimestamp(parts.updatedAt) ?? "";
|
|
3189
|
+
return valid(out2);
|
|
3190
|
+
}
|
|
3191
|
+
function parseCachedMatch(raw, opts = {}) {
|
|
3192
|
+
if (!raw || typeof raw !== "object") return definitiveNone("cache entry is not an object");
|
|
3193
|
+
return sealMatch(raw, opts);
|
|
3194
|
+
}
|
|
3195
|
+
var MAX_EVENTS = 300;
|
|
3196
|
+
var MAX_GROUPS = 16;
|
|
3197
|
+
var MAX_GROUP_ROWS = 32;
|
|
3198
|
+
var STAGES2 = /* @__PURE__ */ new Set(["GROUP", "R32", "R16", "QF", "SF", "3P", "F", "FRIENDLY"]);
|
|
3199
|
+
function teamNames(t2) {
|
|
3200
|
+
return [t2?.displayName, t2?.name, t2?.location, t2?.shortDisplayName, t2?.abbreviation].map((v) => humanLabel(v)).filter((v) => v !== "");
|
|
3201
|
+
}
|
|
3202
|
+
function toParticipant(raw) {
|
|
3203
|
+
if (!raw || typeof raw !== "object") return malformed("competitor is not an object");
|
|
3204
|
+
const names = teamNames(raw.team);
|
|
3205
|
+
if (names.length === 0) return malformed("competitor names no team");
|
|
3206
|
+
const name = names[0];
|
|
3207
|
+
const code = teamCode(raw.team?.abbreviation, name);
|
|
3208
|
+
const team = { code, name, flag: productFlag(name) };
|
|
3209
|
+
const providerId = opaqueId(raw.team?.id, ESPN_ID);
|
|
3210
|
+
const known = productFlag(name) !== nationToFlag("");
|
|
3211
|
+
return valid(
|
|
3212
|
+
providerId && known ? { kind: "team", providerId, team } : { kind: "slot", team }
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
2980
3215
|
function mapStatus(st) {
|
|
2981
|
-
const
|
|
2982
|
-
const
|
|
3216
|
+
const type = st?.type;
|
|
3217
|
+
const name = typeof type?.name === "string" ? type.name.toUpperCase() : "";
|
|
3218
|
+
const state = typeof type?.state === "string" ? type.state : "";
|
|
2983
3219
|
if (name.includes("HALFTIME")) return "HT";
|
|
2984
3220
|
if (name.includes("POSTPONED")) return "POSTPONED";
|
|
2985
3221
|
if (name.includes("CANCEL")) return "CANCELLED";
|
|
2986
3222
|
if (state === "pre") return "SCHEDULED";
|
|
2987
3223
|
if (state === "post") return "FT";
|
|
2988
3224
|
if (state === "in") return "LIVE";
|
|
2989
|
-
return
|
|
3225
|
+
return void 0;
|
|
2990
3226
|
}
|
|
2991
3227
|
function parseMinute(st) {
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
if (
|
|
2996
|
-
|
|
3228
|
+
const s = st;
|
|
3229
|
+
if (s?.type?.state !== "in") return void 0;
|
|
3230
|
+
const dc = typeof s.displayClock === "string" ? s.displayClock.match(/(\d+)/) : null;
|
|
3231
|
+
if (dc) return count(Number.parseInt(dc[1], 10), MAX_MINUTE);
|
|
3232
|
+
if (typeof s.clock === "number" && s.clock > 0) {
|
|
3233
|
+
const n = Math.floor(s.clock / 60);
|
|
3234
|
+
return n > 0 ? count(n, MAX_MINUTE) : void 0;
|
|
2997
3235
|
}
|
|
2998
3236
|
return void 0;
|
|
2999
3237
|
}
|
|
@@ -3007,58 +3245,74 @@ var SLUG_TO_STAGE = {
|
|
|
3007
3245
|
final: "F"
|
|
3008
3246
|
};
|
|
3009
3247
|
function stageFromSlug(slug) {
|
|
3010
|
-
if (slug
|
|
3011
|
-
if (
|
|
3248
|
+
if (slug == null || slug === "") return "GROUP";
|
|
3249
|
+
if (typeof slug === "string" && Object.hasOwn(SLUG_TO_STAGE, slug)) {
|
|
3250
|
+
const mapped = SLUG_TO_STAGE[slug];
|
|
3251
|
+
if (mapped) return mapped;
|
|
3252
|
+
}
|
|
3012
3253
|
return "FRIENDLY";
|
|
3013
3254
|
}
|
|
3014
|
-
function
|
|
3015
|
-
if (
|
|
3016
|
-
|
|
3017
|
-
return
|
|
3018
|
-
}
|
|
3019
|
-
function
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
);
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
}
|
|
3030
|
-
|
|
3031
|
-
const
|
|
3032
|
-
const
|
|
3033
|
-
|
|
3034
|
-
const
|
|
3255
|
+
function toGoals(v) {
|
|
3256
|
+
if (typeof v === "number") return count(v, MAX_GOALS);
|
|
3257
|
+
if (typeof v !== "string" || v.trim() === "") return void 0;
|
|
3258
|
+
return /^-?\d{1,9}$/.test(v.trim()) ? count(Number(v.trim()), MAX_GOALS) : void 0;
|
|
3259
|
+
}
|
|
3260
|
+
function parseEspnEvent(raw, ctx = {}) {
|
|
3261
|
+
if (!raw || typeof raw !== "object") return malformed("event is not an object");
|
|
3262
|
+
const ev = raw;
|
|
3263
|
+
const id = opaqueId(ev.id, ESPN_ID);
|
|
3264
|
+
if (!id) return malformed("event id is absent or not an identifier");
|
|
3265
|
+
const kickoff = canonicalTimestamp(ev.date);
|
|
3266
|
+
if (!kickoff) return malformed("event date is absent or not one instant");
|
|
3267
|
+
const comp = Array.isArray(ev.competitions) ? ev.competitions[0] : void 0;
|
|
3268
|
+
const rawCompetitors = takeBounded(comp?.competitors, 4);
|
|
3269
|
+
if (rawCompetitors.length !== 2) {
|
|
3270
|
+
return definitiveNone(`expected 2 competitors, found ${rawCompetitors.length}`);
|
|
3271
|
+
}
|
|
3272
|
+
const homeRaw = rawCompetitors.find((c) => c?.homeAway === "home");
|
|
3273
|
+
const awayRaw = rawCompetitors.find((c) => c?.homeAway === "away");
|
|
3274
|
+
if (!homeRaw || !awayRaw) return definitiveNone("competitors do not state home and away");
|
|
3275
|
+
const homeP = toParticipant(homeRaw);
|
|
3276
|
+
const awayP = toParticipant(awayRaw);
|
|
3277
|
+
if (homeP.kind !== "valid") return homeP;
|
|
3278
|
+
if (awayP.kind !== "valid") return awayP;
|
|
3279
|
+
const h = homeP.value;
|
|
3280
|
+
const a = awayP.value;
|
|
3281
|
+
if (h.kind === "team" && a.kind === "team" && h.providerId === a.providerId) {
|
|
3282
|
+
return definitiveNone("both competitors are the same team");
|
|
3283
|
+
}
|
|
3284
|
+
const home = homeP.value.team;
|
|
3285
|
+
const away = awayP.value.team;
|
|
3035
3286
|
const status = mapStatus(ev.status ?? comp?.status);
|
|
3036
|
-
|
|
3037
|
-
const
|
|
3038
|
-
const away = toTeam(awayC?.team);
|
|
3287
|
+
if (!status) return malformed("event status is not a status we recognize");
|
|
3288
|
+
const stage = member(stageFromSlug(ev.season?.slug), STAGES2) ?? "FRIENDLY";
|
|
3039
3289
|
let group;
|
|
3040
3290
|
if (stage === "GROUP" && ctx.groupByTeam) {
|
|
3041
3291
|
group = ctx.groupByTeam[home.code] ?? ctx.groupByTeam[away.code];
|
|
3042
3292
|
}
|
|
3043
|
-
const hs =
|
|
3044
|
-
const as =
|
|
3045
|
-
const
|
|
3293
|
+
const hs = toGoals(homeRaw.score);
|
|
3294
|
+
const as = toGoals(awayRaw.score);
|
|
3295
|
+
const scoreExpected = status === "LIVE" || status === "HT" || status === "FT";
|
|
3296
|
+
const hasScore = scoreExpected && hs !== void 0 && as !== void 0;
|
|
3297
|
+
if (scoreExpected && !hasScore) return malformed("event score is absent or unreadable");
|
|
3298
|
+
const hShoot = toGoals(homeRaw.shootoutScore);
|
|
3299
|
+
const aShoot = toGoals(awayRaw.shootoutScore);
|
|
3300
|
+
const shootoutPresent = homeRaw.shootoutScore !== void 0 || awayRaw.shootoutScore !== void 0;
|
|
3301
|
+
const shootout = shootoutPresent ? { home: hShoot, away: aShoot } : void 0;
|
|
3046
3302
|
let winnerCode;
|
|
3047
3303
|
if (isFinished(status)) {
|
|
3048
|
-
|
|
3049
|
-
|
|
3304
|
+
const winners = [homeRaw, awayRaw].filter((c) => flag(c.winner) === true);
|
|
3305
|
+
if (winners.length === 1) winnerCode = winners[0] === homeRaw ? home.code : away.code;
|
|
3050
3306
|
}
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
return {
|
|
3055
|
-
id: ev.id,
|
|
3307
|
+
const venue = comp?.venue;
|
|
3308
|
+
return sealMatch({
|
|
3309
|
+
id,
|
|
3056
3310
|
stage,
|
|
3057
3311
|
group,
|
|
3058
|
-
kickoff
|
|
3059
|
-
venue:
|
|
3060
|
-
city:
|
|
3061
|
-
country:
|
|
3312
|
+
kickoff,
|
|
3313
|
+
venue: venue?.fullName,
|
|
3314
|
+
city: venue?.address?.city,
|
|
3315
|
+
country: venue?.address?.country,
|
|
3062
3316
|
home,
|
|
3063
3317
|
away,
|
|
3064
3318
|
score: hasScore ? { home: hs, away: as } : void 0,
|
|
@@ -3066,57 +3320,221 @@ function mapEspnEvent(ev, ctx = {}) {
|
|
|
3066
3320
|
minute: parseMinute(ev.status ?? comp?.status),
|
|
3067
3321
|
status,
|
|
3068
3322
|
winnerCode,
|
|
3069
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3070
|
-
|
|
3323
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3324
|
+
events: ev.events
|
|
3325
|
+
});
|
|
3071
3326
|
}
|
|
3072
|
-
function
|
|
3073
|
-
|
|
3327
|
+
function parseEspnEvents(raw, ctx = {}) {
|
|
3328
|
+
const all = raw?.events;
|
|
3329
|
+
const readable = Array.isArray(all);
|
|
3330
|
+
const total = readable ? all.length : 0;
|
|
3331
|
+
const considered = takeBounded(all, MAX_EVENTS);
|
|
3332
|
+
const items = [];
|
|
3333
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
3334
|
+
let complete = readable && considered.length === total;
|
|
3335
|
+
for (const event of considered) {
|
|
3336
|
+
const parsed = parseEspnEvent(event, ctx);
|
|
3337
|
+
if (parsed.kind !== "valid") {
|
|
3338
|
+
if (parsed.kind !== "definitive-none") complete = false;
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
if (seenIds.has(parsed.value.id)) {
|
|
3342
|
+
complete = false;
|
|
3343
|
+
continue;
|
|
3344
|
+
}
|
|
3345
|
+
seenIds.add(parsed.value.id);
|
|
3346
|
+
items.push(parsed.value);
|
|
3347
|
+
}
|
|
3348
|
+
return {
|
|
3349
|
+
items,
|
|
3350
|
+
total,
|
|
3351
|
+
shown: items.length,
|
|
3352
|
+
truncated: total > considered.length,
|
|
3353
|
+
// Some record was unreadable, or the window did not cover the payload, or
|
|
3354
|
+
// the envelope itself was not a list — none of those is a complete account
|
|
3355
|
+
// of what the provider sent.
|
|
3356
|
+
complete
|
|
3357
|
+
};
|
|
3074
3358
|
}
|
|
3075
|
-
function statVal(stats, name) {
|
|
3076
|
-
|
|
3077
|
-
|
|
3359
|
+
function statVal(stats, name, signed = false) {
|
|
3360
|
+
if (!Array.isArray(stats) || stats.length > 64) return void 0;
|
|
3361
|
+
const matches = takeBounded(stats, 64).filter(
|
|
3362
|
+
(s) => s?.name === name
|
|
3363
|
+
);
|
|
3364
|
+
if (matches.length !== 1) return void 0;
|
|
3365
|
+
const v = matches[0]?.value;
|
|
3366
|
+
if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) return void 0;
|
|
3367
|
+
const limit = 1e3;
|
|
3368
|
+
return v > limit || v < (signed ? -limit : 0) ? void 0 : v;
|
|
3369
|
+
}
|
|
3370
|
+
function optionalStatVal(stats, name) {
|
|
3371
|
+
if (!Array.isArray(stats) || stats.length > 64) return void 0;
|
|
3372
|
+
const matches = takeBounded(stats, 64).filter(
|
|
3373
|
+
(s) => s?.name === name
|
|
3374
|
+
);
|
|
3375
|
+
if (matches.length === 0) return 0;
|
|
3376
|
+
if (matches.length !== 1) return void 0;
|
|
3377
|
+
const v = matches[0]?.value;
|
|
3378
|
+
return typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e3 ? v : void 0;
|
|
3078
3379
|
}
|
|
3079
3380
|
function entryToRow(e) {
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3381
|
+
const names = teamNames(e?.team);
|
|
3382
|
+
if (names.length === 0) return definitiveNone("standings entry names no team");
|
|
3383
|
+
const name = names[0];
|
|
3384
|
+
const code = teamCode(e?.team?.abbreviation, name);
|
|
3385
|
+
const played = statVal(e.stats, "gamesPlayed");
|
|
3386
|
+
const won = statVal(e.stats, "wins");
|
|
3387
|
+
const drawn = statVal(e.stats, "ties");
|
|
3388
|
+
const lost = statVal(e.stats, "losses");
|
|
3389
|
+
const goalsFor = statVal(e.stats, "pointsFor");
|
|
3390
|
+
const goalsAgainst = statVal(e.stats, "pointsAgainst");
|
|
3391
|
+
const goalDiff = statVal(e.stats, "pointDifferential", true);
|
|
3392
|
+
const points = statVal(e.stats, "points", true);
|
|
3393
|
+
const deductions = optionalStatVal(e.stats, "deductions");
|
|
3394
|
+
const providerRank = statVal(e.stats, "rank");
|
|
3395
|
+
if (played === void 0 || won === void 0 || drawn === void 0 || lost === void 0 || goalsFor === void 0 || goalsAgainst === void 0 || goalDiff === void 0 || points === void 0 || deductions === void 0 || providerRank === void 0 || providerRank < 1) {
|
|
3396
|
+
return malformed("standings entry has missing or invalid statistics");
|
|
3397
|
+
}
|
|
3398
|
+
if (played !== won + drawn + lost) {
|
|
3399
|
+
return malformed("standings entry games do not add up");
|
|
3400
|
+
}
|
|
3401
|
+
if (goalDiff !== goalsFor - goalsAgainst) {
|
|
3402
|
+
return malformed("standings entry goal difference does not add up");
|
|
3403
|
+
}
|
|
3404
|
+
if (points !== won * 3 + drawn - deductions) {
|
|
3405
|
+
return malformed("standings entry points do not add up");
|
|
3406
|
+
}
|
|
3407
|
+
return valid({
|
|
3408
|
+
team: { code, name, flag: productFlag(name) },
|
|
3409
|
+
played,
|
|
3410
|
+
won,
|
|
3411
|
+
drawn,
|
|
3412
|
+
lost,
|
|
3413
|
+
goalsFor,
|
|
3414
|
+
goalsAgainst,
|
|
3415
|
+
goalDiff,
|
|
3416
|
+
points,
|
|
3417
|
+
providerId: opaqueId(e?.team?.id, ESPN_ID),
|
|
3418
|
+
providerRank
|
|
3419
|
+
});
|
|
3091
3420
|
}
|
|
3092
|
-
function
|
|
3421
|
+
function parseEspnStandings(raw) {
|
|
3422
|
+
const rawChildren = raw?.children;
|
|
3423
|
+
const readable = Array.isArray(rawChildren);
|
|
3424
|
+
const rawCount = readable ? rawChildren.length : 0;
|
|
3425
|
+
const children = takeBounded(rawChildren, MAX_GROUPS * 4);
|
|
3426
|
+
const sawAllChildren = rawCount === children.length;
|
|
3427
|
+
let rowsTruncated = false;
|
|
3428
|
+
let complete = readable && sawAllChildren;
|
|
3093
3429
|
const out2 = [];
|
|
3094
|
-
|
|
3095
|
-
|
|
3430
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
3431
|
+
const seenProviderIds = /* @__PURE__ */ new Set();
|
|
3432
|
+
for (const child of children) {
|
|
3433
|
+
const label = humanLabel(child?.name ?? child?.abbreviation);
|
|
3434
|
+
const letter = label.match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
|
|
3096
3435
|
if (!letter) continue;
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
}
|
|
3436
|
+
if (seenGroups.has(letter)) {
|
|
3437
|
+
complete = false;
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
seenGroups.add(letter);
|
|
3441
|
+
const rawEntries = child?.standings?.entries;
|
|
3442
|
+
if (!Array.isArray(rawEntries) || rawEntries.length === 0) {
|
|
3443
|
+
complete = false;
|
|
3444
|
+
continue;
|
|
3445
|
+
}
|
|
3446
|
+
if (rawEntries.length > MAX_GROUP_ROWS) {
|
|
3447
|
+
rowsTruncated = true;
|
|
3448
|
+
complete = false;
|
|
3449
|
+
}
|
|
3450
|
+
const entries = takeBounded(rawEntries, MAX_GROUP_ROWS);
|
|
3451
|
+
const seenTeams = /* @__PURE__ */ new Set();
|
|
3452
|
+
const seenRanks = /* @__PURE__ */ new Set();
|
|
3453
|
+
const ranked = [];
|
|
3454
|
+
for (const e of entries) {
|
|
3455
|
+
const r = entryToRow(e);
|
|
3456
|
+
if (r.kind !== "valid") {
|
|
3457
|
+
if (r.kind !== "definitive-none") complete = false;
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
const key = r.value.providerId ?? r.value.team.code;
|
|
3461
|
+
if (seenTeams.has(key) || seenRanks.has(r.value.providerRank) || r.value.providerId !== void 0 && seenProviderIds.has(r.value.providerId)) {
|
|
3462
|
+
complete = false;
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
seenTeams.add(key);
|
|
3466
|
+
seenRanks.add(r.value.providerRank);
|
|
3467
|
+
if (r.value.providerId !== void 0) seenProviderIds.add(r.value.providerId);
|
|
3468
|
+
const { providerId: _dropId, providerRank: rank, ...row } = r.value;
|
|
3469
|
+
ranked.push({ row, rank });
|
|
3470
|
+
}
|
|
3101
3471
|
ranked.sort((a, b) => {
|
|
3102
3472
|
if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
return
|
|
3473
|
+
if (b.row.points !== a.row.points) return b.row.points - a.row.points;
|
|
3474
|
+
if (b.row.goalDiff !== a.row.goalDiff) return b.row.goalDiff - a.row.goalDiff;
|
|
3475
|
+
return b.row.goalsFor - a.row.goalsFor;
|
|
3106
3476
|
});
|
|
3477
|
+
if (ranked.length === 0) {
|
|
3478
|
+
complete = false;
|
|
3479
|
+
continue;
|
|
3480
|
+
}
|
|
3107
3481
|
out2.push({ group: letter, rows: ranked.map((x) => x.row) });
|
|
3108
3482
|
}
|
|
3109
|
-
|
|
3110
|
-
|
|
3483
|
+
return {
|
|
3484
|
+
items: out2,
|
|
3485
|
+
total: seenGroups.size,
|
|
3486
|
+
shown: out2.length,
|
|
3487
|
+
// We stopped early if the child list or any single group's rows were cut.
|
|
3488
|
+
truncated: !sawAllChildren || rowsTruncated,
|
|
3489
|
+
complete: complete && !rowsTruncated
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
3493
|
+
var DEFAULT_COMPETITION = "fifa.world";
|
|
3494
|
+
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
3495
|
+
var USER_AGENT = `claudinho/${"0.9.4"} (+https://github.com/arturogarrido/claudinho)`;
|
|
3496
|
+
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
3497
|
+
function competitionBase(slug) {
|
|
3498
|
+
return `${ESPN_SOCCER}/${slug}`;
|
|
3499
|
+
}
|
|
3500
|
+
var DEFAULT_TIMEOUT_MS = 6e3;
|
|
3501
|
+
var STANDINGS_SHARE_MS = 3e4;
|
|
3502
|
+
var ProviderError = class extends Error {
|
|
3503
|
+
kind;
|
|
3504
|
+
status;
|
|
3505
|
+
constructor(message, kind, status) {
|
|
3506
|
+
super(message);
|
|
3507
|
+
this.name = "ProviderError";
|
|
3508
|
+
this.kind = kind;
|
|
3509
|
+
this.status = status;
|
|
3510
|
+
}
|
|
3511
|
+
/** 429/403 — the upstream is refusing us; retrying at the live cadence makes it worse. */
|
|
3512
|
+
get throttled() {
|
|
3513
|
+
return this.kind === "http" && (this.status === 429 || this.status === 403);
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
function toEspnDate(d) {
|
|
3517
|
+
return d.replace(/\D/g, "").slice(0, 8);
|
|
3518
|
+
}
|
|
3519
|
+
function usableProviderItems(kind, parsed, hasUsableRecord = parsed.items.length > 0) {
|
|
3520
|
+
if (!hasUsableRecord && (!parsed.complete || parsed.total > 0)) {
|
|
3521
|
+
throw new ProviderError(`ESPN ${kind} payload had no readable records`, "parse");
|
|
3522
|
+
}
|
|
3523
|
+
return [...parsed.items];
|
|
3111
3524
|
}
|
|
3112
3525
|
var EspnAdapter = class {
|
|
3113
3526
|
constructor(opts = {}) {
|
|
3114
3527
|
this.opts = opts;
|
|
3528
|
+
const expected = opts.expectedStandingsGroups ?? (opts.baseUrl === void 0 ? groups() : void 0);
|
|
3529
|
+
this.expectedStandingsGroups = expected ? [...expected] : void 0;
|
|
3530
|
+
this.standingsFallbackGroups = opts.baseUrl === void 0 && expected ? [...expected] : void 0;
|
|
3115
3531
|
}
|
|
3116
3532
|
opts;
|
|
3117
3533
|
name = "espn";
|
|
3118
3534
|
capabilities = { push: false, latencyHintSec: 45 };
|
|
3119
|
-
|
|
3535
|
+
expectedStandingsGroups;
|
|
3536
|
+
standingsFallbackGroups;
|
|
3537
|
+
/** Short-lived team-code -> group-letter map (built lazily from standings). */
|
|
3120
3538
|
groupMap;
|
|
3121
3539
|
/**
|
|
3122
3540
|
* One in-flight/recent standings fetch shared by fetchStandings and
|
|
@@ -3159,38 +3577,48 @@ var EspnAdapter = class {
|
|
|
3159
3577
|
if (this.standingsShared && now - this.standingsShared.at < STANDINGS_SHARE_MS) {
|
|
3160
3578
|
return this.standingsShared.promise;
|
|
3161
3579
|
}
|
|
3162
|
-
const promise = this.get(this.standingsUrl()).then(
|
|
3163
|
-
|
|
3164
|
-
|
|
3580
|
+
const promise = this.get(this.standingsUrl()).then((d) => {
|
|
3581
|
+
const parsed = parseEspnStandings(d);
|
|
3582
|
+
return usableProviderItems(
|
|
3583
|
+
"standings",
|
|
3584
|
+
parsed,
|
|
3585
|
+
parsed.items.some((table) => table.rows.length > 0)
|
|
3586
|
+
);
|
|
3587
|
+
});
|
|
3165
3588
|
this.standingsShared = { at: now, promise };
|
|
3166
|
-
promise.catch(() => {
|
|
3589
|
+
void promise.catch(() => {
|
|
3167
3590
|
if (this.standingsShared?.promise === promise) this.standingsShared = void 0;
|
|
3168
3591
|
});
|
|
3169
3592
|
return promise;
|
|
3170
3593
|
}
|
|
3171
3594
|
/**
|
|
3172
3595
|
* Authoritative, cumulative group tables from the standings endpoint. Throws
|
|
3173
|
-
* on fetch
|
|
3174
|
-
*
|
|
3596
|
+
* on fetch failure. Group-stage only: non-group `children` are filtered out
|
|
3597
|
+
* by {@link parseStandings}; malformed rows are omitted without hiding their
|
|
3598
|
+
* readable siblings.
|
|
3175
3599
|
*/
|
|
3176
3600
|
async fetchStandings() {
|
|
3177
3601
|
return this.sharedStandings();
|
|
3178
3602
|
}
|
|
3179
3603
|
/**
|
|
3180
|
-
* Build (and cache) a team-code -> group-letter map from the standings
|
|
3604
|
+
* Build (and briefly cache) a team-code -> group-letter map from the standings
|
|
3181
3605
|
* endpoint. Best-effort: returns {} if standings are unavailable — but a
|
|
3182
|
-
* transient failure is NOT cached
|
|
3183
|
-
*
|
|
3606
|
+
* transient failure is NOT cached, and a partial successful parse expires at
|
|
3607
|
+
* the standings TTL, so neither can silently drop group letters for the
|
|
3608
|
+
* adapter's lifetime.
|
|
3184
3609
|
* Reuses the same parse/fetch as {@link fetchStandings}, so the two never
|
|
3185
3610
|
* drift and one command never fetches standings twice.
|
|
3186
3611
|
*/
|
|
3187
3612
|
async fetchGroupMap(force = false) {
|
|
3188
|
-
|
|
3613
|
+
const now = Date.now();
|
|
3614
|
+
if (!force && this.groupMap && now - this.groupMap.at < STANDINGS_SHARE_MS) {
|
|
3615
|
+
return this.groupMap.value;
|
|
3616
|
+
}
|
|
3189
3617
|
try {
|
|
3190
3618
|
const tables = await this.sharedStandings();
|
|
3191
3619
|
const map = {};
|
|
3192
3620
|
for (const t2 of tables) for (const r of t2.rows) map[r.team.code] = t2.group;
|
|
3193
|
-
this.groupMap = map;
|
|
3621
|
+
this.groupMap = { at: Date.now(), value: map };
|
|
3194
3622
|
return map;
|
|
3195
3623
|
} catch {
|
|
3196
3624
|
return {};
|
|
@@ -3205,7 +3633,8 @@ var EspnAdapter = class {
|
|
|
3205
3633
|
this.opts.enrichGroups === false ? Promise.resolve({}) : this.fetchGroupMap(),
|
|
3206
3634
|
this.get(url.toString())
|
|
3207
3635
|
]);
|
|
3208
|
-
|
|
3636
|
+
const parsed = parseEspnEvents(data, { groupByTeam });
|
|
3637
|
+
return usableProviderItems("scoreboard", parsed);
|
|
3209
3638
|
}
|
|
3210
3639
|
async get(url) {
|
|
3211
3640
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
@@ -4015,17 +4444,26 @@ async function getMatchesForDate(adapter, dateISO) {
|
|
|
4015
4444
|
}
|
|
4016
4445
|
async function getStandings(adapter, group) {
|
|
4017
4446
|
const want = group?.toUpperCase();
|
|
4447
|
+
const expected = adapter.expectedStandingsGroups;
|
|
4448
|
+
if (want && expected && !expected.includes(want)) {
|
|
4449
|
+
return { tables: [], degraded: false };
|
|
4450
|
+
}
|
|
4018
4451
|
if (adapter.fetchStandings) {
|
|
4019
4452
|
try {
|
|
4020
4453
|
const all = await adapter.fetchStandings();
|
|
4021
4454
|
const tables2 = (want ? all.filter((t2) => t2.group === want) : all).sort(
|
|
4022
4455
|
(a, b) => a.group.localeCompare(b.group)
|
|
4023
4456
|
);
|
|
4024
|
-
|
|
4457
|
+
const availableGroups = new Set(tables2.map((table) => table.group));
|
|
4458
|
+
const expectedGroupWasOmitted = want ? (expected?.includes(want) ?? false) && tables2.length === 0 : expected?.some((group2) => !availableGroups.has(group2)) ?? false;
|
|
4459
|
+
if (!expectedGroupWasOmitted) {
|
|
4460
|
+
return { tables: tables2, degraded: false, source: adapter.name };
|
|
4461
|
+
}
|
|
4025
4462
|
} catch {
|
|
4026
4463
|
}
|
|
4027
4464
|
}
|
|
4028
|
-
const
|
|
4465
|
+
const fallbackGroups = adapter.standingsFallbackGroups;
|
|
4466
|
+
const letters = fallbackGroups ? want ? fallbackGroups.includes(want) ? [want] : [] : [...new Set(fallbackGroups)].sort((a, b) => a.localeCompare(b)) : [];
|
|
4029
4467
|
const tables = letters.map((g) => ({ group: g, rows: rosterAtZero(fixturesByGroup(g)) })).filter((t2) => t2.rows.length > 0);
|
|
4030
4468
|
return { tables, degraded: true };
|
|
4031
4469
|
}
|
|
@@ -4082,7 +4520,10 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
4082
4520
|
try {
|
|
4083
4521
|
const win = knockoutWindow();
|
|
4084
4522
|
if (adapter.fetchWindow && win) {
|
|
4085
|
-
fixtures = mergeLive(
|
|
4523
|
+
fixtures = mergeLive(
|
|
4524
|
+
fixtures,
|
|
4525
|
+
await adapter.fetchWindow(win.start, win.end)
|
|
4526
|
+
);
|
|
4086
4527
|
}
|
|
4087
4528
|
} catch {
|
|
4088
4529
|
overlayFailed = true;
|
|
@@ -4146,14 +4587,144 @@ async function getMatchById(adapter, id) {
|
|
|
4146
4587
|
async function getLiveMatches(adapter, now = /* @__PURE__ */ new Date()) {
|
|
4147
4588
|
try {
|
|
4148
4589
|
const day = now.toISOString().slice(0, 10);
|
|
4149
|
-
const matches = adapter.fetchWindow ?
|
|
4150
|
-
(m) => isLive(m.status)
|
|
4151
|
-
) : await adapter.fetchLive();
|
|
4590
|
+
const matches = (adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchLive()).filter((m) => isLive(m.status));
|
|
4152
4591
|
return { matches, degraded: false, source: adapter.name };
|
|
4153
4592
|
} catch {
|
|
4154
4593
|
return { matches: [], degraded: true };
|
|
4155
4594
|
}
|
|
4156
4595
|
}
|
|
4596
|
+
function pct(p) {
|
|
4597
|
+
return Math.round(p * 100);
|
|
4598
|
+
}
|
|
4599
|
+
var KNOWN_MARKET_SOURCES = ["polymarket", "fake"];
|
|
4600
|
+
function marketSourceLabel(source) {
|
|
4601
|
+
if (source === "polymarket") return "Polymarket";
|
|
4602
|
+
if (source === "fake") return "demo data";
|
|
4603
|
+
return source.charAt(0).toUpperCase() + source.slice(1);
|
|
4604
|
+
}
|
|
4605
|
+
function outcomeLabel(o, match) {
|
|
4606
|
+
if (o.kind === "home") return match.home.name;
|
|
4607
|
+
if (o.kind === "away") return match.away.name;
|
|
4608
|
+
if (o.kind === "draw") return "Draw";
|
|
4609
|
+
return o.label;
|
|
4610
|
+
}
|
|
4611
|
+
function utcHhmm(iso) {
|
|
4612
|
+
const t2 = Date.parse(iso);
|
|
4613
|
+
if (!Number.isFinite(t2)) return "";
|
|
4614
|
+
return `${new Date(t2).toISOString().slice(11, 16)} UTC`;
|
|
4615
|
+
}
|
|
4616
|
+
function marketFavoriteText(signal, match) {
|
|
4617
|
+
const fav = signal.favorite;
|
|
4618
|
+
if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
|
|
4619
|
+
if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
|
|
4620
|
+
const name = fav.kind === "home" ? match.home.name : match.away.name;
|
|
4621
|
+
return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
|
|
4622
|
+
}
|
|
4623
|
+
function marketProbabilityText(signal, match) {
|
|
4624
|
+
const order = ["home", "draw", "away"];
|
|
4625
|
+
const parts = [];
|
|
4626
|
+
for (const kind of order) {
|
|
4627
|
+
const o = signal.outcomes.find((x) => x.kind === kind);
|
|
4628
|
+
if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4629
|
+
}
|
|
4630
|
+
for (const o of signal.outcomes) {
|
|
4631
|
+
if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4632
|
+
}
|
|
4633
|
+
return parts.join(" \xB7 ");
|
|
4634
|
+
}
|
|
4635
|
+
function marketAttributionText(signal) {
|
|
4636
|
+
const time = utcHhmm(signal.asOf);
|
|
4637
|
+
const src = `Source: ${marketSourceLabel(signal.source)}`;
|
|
4638
|
+
return time ? `${src} \xB7 updated ${time}` : src;
|
|
4639
|
+
}
|
|
4640
|
+
function marketLine(signal, match) {
|
|
4641
|
+
return `Market: ${marketProbabilityText(signal, match)} \xB7 ${marketSourceLabel(
|
|
4642
|
+
signal.source
|
|
4643
|
+
)} \xB7 informational only`;
|
|
4644
|
+
}
|
|
4645
|
+
function marketBlock(signal, match) {
|
|
4646
|
+
const lines = [];
|
|
4647
|
+
if (signal.stale) lines.push("Market signal is stale; the reading may be out of date.");
|
|
4648
|
+
lines.push(marketFavoriteText(signal, match));
|
|
4649
|
+
lines.push(marketProbabilityText(signal, match));
|
|
4650
|
+
lines.push(`${marketAttributionText(signal)} \xB7 informational only`);
|
|
4651
|
+
return lines;
|
|
4652
|
+
}
|
|
4653
|
+
var MAX_OUTCOMES = 128;
|
|
4654
|
+
var MATCH_ID = /^[0-9]{1,20}$/;
|
|
4655
|
+
var MARKET_ID = /^(?:[0-9]{1,32}|fifwc-[a-z]{2,3}-[a-z]{2,3}-\d{4}-\d{2}-\d{2})$/;
|
|
4656
|
+
var OUTCOME_KINDS = /* @__PURE__ */ new Set(["home", "draw", "away", "other"]);
|
|
4657
|
+
var TEAM_CODE_COLUMNS2 = 8;
|
|
4658
|
+
function sealOutcome(raw) {
|
|
4659
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
4660
|
+
const o = raw;
|
|
4661
|
+
const kind = member(o.kind, OUTCOME_KINDS);
|
|
4662
|
+
const p = probability(o.probability);
|
|
4663
|
+
if (!kind || p === void 0) return void 0;
|
|
4664
|
+
const out2 = { kind };
|
|
4665
|
+
if (o.teamCode !== void 0) {
|
|
4666
|
+
if (typeof o.teamCode !== "string") return void 0;
|
|
4667
|
+
out2.teamCode = humanLabel(o.teamCode, TEAM_CODE_COLUMNS2);
|
|
4668
|
+
}
|
|
4669
|
+
out2.label = humanLabel(o.label);
|
|
4670
|
+
out2.probability = p;
|
|
4671
|
+
if ((out2.kind === "home" || out2.kind === "away") && !out2.teamCode) return void 0;
|
|
4672
|
+
return out2;
|
|
4673
|
+
}
|
|
4674
|
+
function hasDuplicateKind(outcomes) {
|
|
4675
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4676
|
+
for (const o of outcomes) {
|
|
4677
|
+
if (o.kind === "other") continue;
|
|
4678
|
+
if (seen.has(o.kind)) return true;
|
|
4679
|
+
seen.add(o.kind);
|
|
4680
|
+
}
|
|
4681
|
+
return false;
|
|
4682
|
+
}
|
|
4683
|
+
function sealMarketSignal(raw, options = {}) {
|
|
4684
|
+
if (!raw || typeof raw !== "object") return malformed("signal is not an object");
|
|
4685
|
+
const s = raw;
|
|
4686
|
+
const matchId = opaqueId(s.matchId, MATCH_ID);
|
|
4687
|
+
if (!matchId) return malformed("signal names no fixture");
|
|
4688
|
+
if (!Array.isArray(s.outcomes) || s.outcomes.length > MAX_OUTCOMES) {
|
|
4689
|
+
return malformed("signal outcomes are absent or exceed the cap");
|
|
4690
|
+
}
|
|
4691
|
+
const outcomes = [];
|
|
4692
|
+
for (const rawOutcome of takeBounded(s.outcomes, MAX_OUTCOMES)) {
|
|
4693
|
+
const outcome = sealOutcome(rawOutcome);
|
|
4694
|
+
if (!outcome) return malformed("signal carries an unreadable outcome");
|
|
4695
|
+
outcomes.push(outcome);
|
|
4696
|
+
}
|
|
4697
|
+
if (hasDuplicateKind(outcomes)) {
|
|
4698
|
+
return ambiguous("two outcomes claim the same result");
|
|
4699
|
+
}
|
|
4700
|
+
const sourceMarketId = opaqueId(s.sourceMarketId, MARKET_ID);
|
|
4701
|
+
const liquidity = quantity(s.liquidity);
|
|
4702
|
+
const volume24h = quantity(s.volume24h);
|
|
4703
|
+
const out2 = {
|
|
4704
|
+
matchId,
|
|
4705
|
+
// Allow-listed, not merely stripped: this lands in the provider-attribution
|
|
4706
|
+
// slot, where `marketSourceLabel` falls through to the raw string for an
|
|
4707
|
+
// unrecognized provider — attacker prose where the reader expects
|
|
4708
|
+
// "Polymarket".
|
|
4709
|
+
source: member(s.source, new Set(KNOWN_MARKET_SOURCES)) ?? ""
|
|
4710
|
+
};
|
|
4711
|
+
if (sourceMarketId) out2.sourceMarketId = sourceMarketId;
|
|
4712
|
+
out2.asOf = canonicalTimestamp(s.asOf) ?? "";
|
|
4713
|
+
out2.fetchedAt = canonicalTimestamp(s.fetchedAt) ?? "";
|
|
4714
|
+
out2.outcomes = outcomes;
|
|
4715
|
+
const isAmbiguous = s.ambiguous !== false;
|
|
4716
|
+
const favorite = isAmbiguous ? void 0 : deriveFavorite(outcomes);
|
|
4717
|
+
if (favorite) out2.favorite = favorite;
|
|
4718
|
+
if (liquidity !== void 0) out2.liquidity = liquidity;
|
|
4719
|
+
if (volume24h !== void 0) out2.volume24h = volume24h;
|
|
4720
|
+
out2.stale = s.stale !== false;
|
|
4721
|
+
out2.ambiguous = isAmbiguous || out2.source === "";
|
|
4722
|
+
out2.stale = out2.stale || isStaleSignal(out2, { now: options.now, maxAgeMs: options.maxAgeMs });
|
|
4723
|
+
return valid(out2);
|
|
4724
|
+
}
|
|
4725
|
+
function parseCachedMarketSignal(raw, options = {}) {
|
|
4726
|
+
return sealMarketSignal(raw, options);
|
|
4727
|
+
}
|
|
4157
4728
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
4158
4729
|
function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
|
|
4159
4730
|
if (isLive(match.status)) return true;
|
|
@@ -4172,9 +4743,9 @@ function normalizeOutcomes(outcomes) {
|
|
|
4172
4743
|
probability: Number.isFinite(o.probability) && o.probability > 0 ? o.probability / sum : 0
|
|
4173
4744
|
}));
|
|
4174
4745
|
}
|
|
4175
|
-
function favoriteStrength(
|
|
4176
|
-
if (
|
|
4177
|
-
if (
|
|
4746
|
+
function favoriteStrength(probability2) {
|
|
4747
|
+
if (probability2 >= 0.65) return "clear";
|
|
4748
|
+
if (probability2 >= 0.52) return "slight";
|
|
4178
4749
|
return "close";
|
|
4179
4750
|
}
|
|
4180
4751
|
function deriveFavorite(outcomes) {
|
|
@@ -4193,14 +4764,16 @@ function deriveFavorite(outcomes) {
|
|
|
4193
4764
|
}
|
|
4194
4765
|
function mapsCleanly(match, outcomes) {
|
|
4195
4766
|
if (outcomes.some((o) => o.kind === "other")) return false;
|
|
4767
|
+
const kinds = outcomes.map((o) => o.kind);
|
|
4768
|
+
if (new Set(kinds).size !== kinds.length) return false;
|
|
4196
4769
|
const home = outcomes.find((o) => o.kind === "home");
|
|
4197
4770
|
const away = outcomes.find((o) => o.kind === "away");
|
|
4198
4771
|
const draw = outcomes.find((o) => o.kind === "draw");
|
|
4199
4772
|
if (!home || !away) return false;
|
|
4200
|
-
if (home.teamCode
|
|
4773
|
+
if (!home.teamCode || home.teamCode.toUpperCase() !== match.home.code.toUpperCase()) {
|
|
4201
4774
|
return false;
|
|
4202
4775
|
}
|
|
4203
|
-
if (away.teamCode
|
|
4776
|
+
if (!away.teamCode || away.teamCode.toUpperCase() !== match.away.code.toUpperCase()) {
|
|
4204
4777
|
return false;
|
|
4205
4778
|
}
|
|
4206
4779
|
if (match.stage === "GROUP" && !draw) return false;
|
|
@@ -4215,11 +4788,13 @@ function hasSaneDistribution(outcomes) {
|
|
|
4215
4788
|
const sum = priced.reduce((s, o) => s + o.probability, 0);
|
|
4216
4789
|
return sum > 0.97 && sum < 1.03;
|
|
4217
4790
|
}
|
|
4791
|
+
var FUTURE_SKEW_MS = 6e4;
|
|
4218
4792
|
function isStaleSignal(signal, options = {}) {
|
|
4219
4793
|
const maxAge = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
4220
4794
|
const asOf = Date.parse(signal.asOf);
|
|
4221
4795
|
if (!Number.isFinite(asOf)) return true;
|
|
4222
4796
|
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
4797
|
+
if (asOf - now > FUTURE_SKEW_MS) return true;
|
|
4223
4798
|
return now - asOf > maxAge;
|
|
4224
4799
|
}
|
|
4225
4800
|
function isReliableMarketSignal(signal, options = {}) {
|
|
@@ -4235,8 +4810,8 @@ function isReliableMarketSignal(signal, options = {}) {
|
|
|
4235
4810
|
}
|
|
4236
4811
|
function buildMarketSignal(input) {
|
|
4237
4812
|
const outcomes = normalizeOutcomes(input.outcomes);
|
|
4238
|
-
const
|
|
4239
|
-
const favorite =
|
|
4813
|
+
const ambiguous2 = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
|
|
4814
|
+
const favorite = ambiguous2 ? void 0 : deriveFavorite(outcomes);
|
|
4240
4815
|
const signal = {
|
|
4241
4816
|
matchId: input.match.id,
|
|
4242
4817
|
source: input.source,
|
|
@@ -4248,66 +4823,33 @@ function buildMarketSignal(input) {
|
|
|
4248
4823
|
liquidity: input.liquidity,
|
|
4249
4824
|
volume24h: input.volume24h,
|
|
4250
4825
|
stale: false,
|
|
4251
|
-
ambiguous
|
|
4826
|
+
ambiguous: ambiguous2
|
|
4252
4827
|
};
|
|
4253
4828
|
signal.stale = isStaleSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
return Math.round(p * 100);
|
|
4258
|
-
}
|
|
4259
|
-
function marketSourceLabel(source) {
|
|
4260
|
-
if (source === "polymarket") return "Polymarket";
|
|
4261
|
-
if (source === "fake") return "demo data";
|
|
4262
|
-
return source.charAt(0).toUpperCase() + source.slice(1);
|
|
4263
|
-
}
|
|
4264
|
-
function outcomeLabel(o, match) {
|
|
4265
|
-
if (o.kind === "home") return match.home.name;
|
|
4266
|
-
if (o.kind === "away") return match.away.name;
|
|
4267
|
-
if (o.kind === "draw") return "Draw";
|
|
4268
|
-
return o.label;
|
|
4269
|
-
}
|
|
4270
|
-
function utcHhmm(iso) {
|
|
4271
|
-
const t2 = Date.parse(iso);
|
|
4272
|
-
if (!Number.isFinite(t2)) return "";
|
|
4273
|
-
return `${new Date(t2).toISOString().slice(11, 16)} UTC`;
|
|
4274
|
-
}
|
|
4275
|
-
function marketFavoriteText(signal, match) {
|
|
4276
|
-
const fav = signal.favorite;
|
|
4277
|
-
if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
|
|
4278
|
-
if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
|
|
4279
|
-
const name = fav.kind === "home" ? match.home.name : match.away.name;
|
|
4280
|
-
return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
|
|
4281
|
-
}
|
|
4282
|
-
function marketProbabilityText(signal, match) {
|
|
4283
|
-
const order = ["home", "draw", "away"];
|
|
4284
|
-
const parts = [];
|
|
4285
|
-
for (const kind of order) {
|
|
4286
|
-
const o = signal.outcomes.find((x) => x.kind === kind);
|
|
4287
|
-
if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4829
|
+
const sealed = sealMarketSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
|
|
4830
|
+
if (sealed.kind !== "valid") {
|
|
4831
|
+
return { ...signal, outcomes: [], favorite: void 0, stale: true, ambiguous: true };
|
|
4288
4832
|
}
|
|
4289
|
-
|
|
4290
|
-
if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4291
|
-
}
|
|
4292
|
-
return parts.join(" \xB7 ");
|
|
4833
|
+
return { ...sealed.value, ambiguous: sealed.value.ambiguous || ambiguous2 };
|
|
4293
4834
|
}
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4835
|
+
var NONE = { kind: "none" };
|
|
4836
|
+
function selectOne(candidates) {
|
|
4837
|
+
if (candidates.length === 1) return { kind: "one", value: candidates[0] };
|
|
4838
|
+
if (candidates.length === 0) return NONE;
|
|
4839
|
+
return { kind: "ambiguous", count: candidates.length };
|
|
4298
4840
|
}
|
|
4299
|
-
function
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4841
|
+
function resolvedValues(batch) {
|
|
4842
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
4843
|
+
for (const [key, r] of batch.results) if (r.kind === "valid") out2.set(key, r.value);
|
|
4844
|
+
return out2;
|
|
4303
4845
|
}
|
|
4304
|
-
function
|
|
4305
|
-
const
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
return
|
|
4846
|
+
function cacheableKeys(batch) {
|
|
4847
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
4848
|
+
for (const [key, r] of batch.results) if (isCacheable(r)) out2.add(key);
|
|
4849
|
+
return out2;
|
|
4850
|
+
}
|
|
4851
|
+
function emptyBatch() {
|
|
4852
|
+
return { results: /* @__PURE__ */ new Map(), complete: false };
|
|
4311
4853
|
}
|
|
4312
4854
|
var FakeMarketProvider = class {
|
|
4313
4855
|
constructor(opts = {}) {
|
|
@@ -4322,14 +4864,12 @@ var FakeMarketProvider = class {
|
|
|
4322
4864
|
return void 0;
|
|
4323
4865
|
}
|
|
4324
4866
|
async findSignals(matches, options) {
|
|
4325
|
-
const
|
|
4326
|
-
const checked = /* @__PURE__ */ new Set();
|
|
4867
|
+
const results = /* @__PURE__ */ new Map();
|
|
4327
4868
|
for (const m of matches) {
|
|
4328
|
-
checked.add(m.id);
|
|
4329
4869
|
const s = await this.findSignal(m, options);
|
|
4330
|
-
|
|
4870
|
+
results.set(m.id, s ? valid(s) : definitiveNone("fake provider has no signal"));
|
|
4331
4871
|
}
|
|
4332
|
-
return {
|
|
4872
|
+
return { results, complete: true };
|
|
4333
4873
|
}
|
|
4334
4874
|
synthesize(match, options) {
|
|
4335
4875
|
const seed = hash(`${match.home.code}-${match.away.code}`);
|
|
@@ -4346,7 +4886,9 @@ var FakeMarketProvider = class {
|
|
|
4346
4886
|
return buildMarketSignal({
|
|
4347
4887
|
match,
|
|
4348
4888
|
source: "fake",
|
|
4349
|
-
|
|
4889
|
+
// Must satisfy the boundary's opaque-id grammar, like a real one:
|
|
4890
|
+
// a source id that only the live path accepts is the asymmetry itself.
|
|
4891
|
+
sourceMarketId: match.id,
|
|
4350
4892
|
asOf,
|
|
4351
4893
|
fetchedAt: now.toISOString(),
|
|
4352
4894
|
outcomes,
|
|
@@ -4370,6 +4912,8 @@ var DEFAULT_BASE2 = "https://gamma-api.polymarket.com";
|
|
|
4370
4912
|
var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["gamma-api.polymarket.com"]);
|
|
4371
4913
|
var USER_AGENT2 = "claudinho/0.0 (+https://github.com/arturogarrido/claudinho)";
|
|
4372
4914
|
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
4915
|
+
var MAX_EVENT_MARKETS = 256;
|
|
4916
|
+
var DEFAULT_DEADLINE_MS = 15e3;
|
|
4373
4917
|
var WC_SERIES_SLUG = "soccer-fifwc";
|
|
4374
4918
|
var WC_SPORT = "fifwc";
|
|
4375
4919
|
var KICKOFF_TOLERANCE_MS = 6 * 60 * 6e4;
|
|
@@ -4382,49 +4926,81 @@ var PolymarketProvider = class {
|
|
|
4382
4926
|
opts;
|
|
4383
4927
|
name = "polymarket";
|
|
4384
4928
|
async findSignal(match, options) {
|
|
4385
|
-
const deadline =
|
|
4386
|
-
return (await this.resolveOne(match, options, deadline))
|
|
4929
|
+
const deadline = Date.now() + (options?.deadlineMs ?? DEFAULT_DEADLINE_MS);
|
|
4930
|
+
return parsedValue(await this.resolveOne(match, options, deadline));
|
|
4387
4931
|
}
|
|
4388
4932
|
async findSignals(matches, options) {
|
|
4389
|
-
const
|
|
4390
|
-
const
|
|
4391
|
-
|
|
4933
|
+
const results = /* @__PURE__ */ new Map();
|
|
4934
|
+
const deadline = Date.now() + (options?.deadlineMs ?? DEFAULT_DEADLINE_MS);
|
|
4935
|
+
let complete = true;
|
|
4392
4936
|
for (const m of matches) {
|
|
4393
|
-
if (Date.now() >= deadline)
|
|
4937
|
+
if (Date.now() >= deadline) {
|
|
4938
|
+
results.set(m.id, unresolved("enrichment deadline expired"));
|
|
4939
|
+
complete = false;
|
|
4940
|
+
continue;
|
|
4941
|
+
}
|
|
4394
4942
|
const r = await this.resolveOne(m, options, deadline);
|
|
4395
|
-
if (r.
|
|
4396
|
-
|
|
4943
|
+
if (r.kind === "unresolved" || r.kind === "malformed") complete = false;
|
|
4944
|
+
results.set(m.id, r);
|
|
4397
4945
|
}
|
|
4398
|
-
return {
|
|
4946
|
+
return { results, complete };
|
|
4399
4947
|
}
|
|
4400
4948
|
/**
|
|
4401
|
-
* Resolve one match
|
|
4402
|
-
*
|
|
4403
|
-
*
|
|
4404
|
-
*
|
|
4949
|
+
* Resolve one match into a verdict.
|
|
4950
|
+
*
|
|
4951
|
+
* Every exit says which KIND of non-answer it is, because that decides
|
|
4952
|
+
* whether it may be remembered — see `isCacheable`: a conclusion we drew from
|
|
4953
|
+
* a payload we READ is cacheable (including an ambiguity, which is stable),
|
|
4954
|
+
* while a shape we could not read is not. Previously a single
|
|
4955
|
+
* `checked: boolean` collapsed five distinct situations into two, and the
|
|
4956
|
+
* ones that landed on the wrong side of it — an ambiguous payload, a
|
|
4957
|
+
* two-legged market, an incoherent 1X2 — were negative-cached as the fact
|
|
4958
|
+
* that this fixture has no market.
|
|
4405
4959
|
*/
|
|
4406
4960
|
async resolveOne(match, options, deadline = Number.POSITIVE_INFINITY) {
|
|
4407
|
-
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
4408
|
-
const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
|
|
4409
|
-
if (slugs.length === 0) return { checked: true };
|
|
4410
4961
|
const configured = options?.timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
4411
4962
|
try {
|
|
4963
|
+
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
4964
|
+
const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
|
|
4965
|
+
if (slugs.length === 0) return definitiveNone("fixture has no derivable event slug");
|
|
4966
|
+
const RANK = {
|
|
4967
|
+
"definitive-none": 0,
|
|
4968
|
+
ambiguous: 1,
|
|
4969
|
+
unresolved: 2,
|
|
4970
|
+
malformed: 3
|
|
4971
|
+
};
|
|
4972
|
+
let worst;
|
|
4973
|
+
const keepWorst = (r) => {
|
|
4974
|
+
if (r.kind === "definitive-none" || r.kind === "valid") return;
|
|
4975
|
+
if (!worst || (RANK[r.kind] ?? 0) > (RANK[worst.kind] ?? 0)) worst = r;
|
|
4976
|
+
};
|
|
4412
4977
|
for (const slug of slugs) {
|
|
4413
4978
|
const remaining = deadline - Date.now();
|
|
4414
|
-
if (remaining <= 0) return
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4979
|
+
if (remaining <= 0) return unresolved("deadline expired between candidate slugs");
|
|
4980
|
+
let found;
|
|
4981
|
+
try {
|
|
4982
|
+
found = await this.fetchEvent(slug, Math.min(configured, remaining));
|
|
4983
|
+
} catch {
|
|
4984
|
+
keepWorst(malformed("candidate request failed"));
|
|
4985
|
+
continue;
|
|
4986
|
+
}
|
|
4987
|
+
if (found.kind !== "valid") {
|
|
4988
|
+
keepWorst(found);
|
|
4989
|
+
continue;
|
|
4990
|
+
}
|
|
4991
|
+
const r = this.toSignal(match, slug, found.value, options);
|
|
4992
|
+
if (r.kind === "valid") return r;
|
|
4993
|
+
keepWorst(r);
|
|
4418
4994
|
}
|
|
4419
|
-
return
|
|
4995
|
+
return worst ?? definitiveNone("no candidate slug yielded a usable market");
|
|
4420
4996
|
} catch {
|
|
4421
|
-
return
|
|
4997
|
+
return malformed("provider request failed");
|
|
4422
4998
|
}
|
|
4423
4999
|
}
|
|
4424
5000
|
async fetchEvent(slug, timeoutMs) {
|
|
4425
5001
|
const base = this.opts.baseUrl ?? DEFAULT_BASE2;
|
|
4426
5002
|
assertAllowedHost(base);
|
|
4427
|
-
const url = `${base}/events
|
|
5003
|
+
const url = `${base}/events/slug/${encodeURIComponent(slug)}`;
|
|
4428
5004
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
4429
5005
|
const res = await doFetch(url, {
|
|
4430
5006
|
signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2),
|
|
@@ -4433,7 +5009,7 @@ var PolymarketProvider = class {
|
|
|
4433
5009
|
redirect: "error",
|
|
4434
5010
|
headers: { Accept: "application/json", "User-Agent": USER_AGENT2 }
|
|
4435
5011
|
});
|
|
4436
|
-
if (res.status === 404) return
|
|
5012
|
+
if (res.status === 404) return definitiveNone("slug returns 404");
|
|
4437
5013
|
if (!res.ok) {
|
|
4438
5014
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
4439
5015
|
}
|
|
@@ -4442,63 +5018,148 @@ var PolymarketProvider = class {
|
|
|
4442
5018
|
throw new Error(`Polymarket response too large: ${length} bytes`);
|
|
4443
5019
|
}
|
|
4444
5020
|
const data = await res.json();
|
|
5021
|
+
if (Array.isArray(data) && data.length > 1) {
|
|
5022
|
+
return ambiguous("slug returned more than one event");
|
|
5023
|
+
}
|
|
5024
|
+
if (Array.isArray(data) && data.length === 0) return definitiveNone("slug returns no event");
|
|
4445
5025
|
const event = Array.isArray(data) ? data[0] : data;
|
|
4446
|
-
|
|
5026
|
+
if (!event || typeof event !== "object") return malformed("event body is not an object");
|
|
5027
|
+
return valid(event);
|
|
4447
5028
|
}
|
|
4448
5029
|
toSignal(match, eventSlug, event, options) {
|
|
4449
|
-
if (event.active
|
|
4450
|
-
|
|
4451
|
-
|
|
5030
|
+
if (typeof event.active !== "boolean" || typeof event.closed !== "boolean") {
|
|
5031
|
+
return malformed("event active/closed is not a boolean");
|
|
5032
|
+
}
|
|
5033
|
+
if (event.active === false || event.closed === true) {
|
|
5034
|
+
return definitiveNone("event is closed or inactive");
|
|
5035
|
+
}
|
|
5036
|
+
if (event.seriesSlug !== WC_SERIES_SLUG && event.sport?.sport !== WC_SPORT) {
|
|
5037
|
+
return definitiveNone("event is not in this competition");
|
|
5038
|
+
}
|
|
5039
|
+
if (typeof event.slug !== "string") {
|
|
5040
|
+
return malformed("event states no slug");
|
|
4452
5041
|
}
|
|
4453
|
-
if (event.slug
|
|
4454
|
-
|
|
5042
|
+
if (event.slug !== eventSlug) return definitiveNone("event is not the one requested");
|
|
5043
|
+
if (typeof event.startTime !== "string" || !canonicalTimestamp(event.startTime)) {
|
|
5044
|
+
return malformed("event startTime missing or unparseable");
|
|
5045
|
+
}
|
|
5046
|
+
const start = Date.parse(event.startTime);
|
|
4455
5047
|
const kick = Date.parse(match.kickoff);
|
|
4456
|
-
if (Number.isFinite(start)
|
|
4457
|
-
return
|
|
5048
|
+
if (!Number.isFinite(start) || !Number.isFinite(kick)) {
|
|
5049
|
+
return malformed("event or fixture kickoff is unreadable");
|
|
5050
|
+
}
|
|
5051
|
+
if (Math.abs(start - kick) > KICKOFF_TOLERANCE_MS) {
|
|
5052
|
+
return definitiveNone("event kickoff does not match the fixture");
|
|
5053
|
+
}
|
|
5054
|
+
if (!Array.isArray(event.markets)) {
|
|
5055
|
+
return malformed("event markets is not an array");
|
|
4458
5056
|
}
|
|
4459
|
-
const
|
|
4460
|
-
|
|
5057
|
+
const marketsTruncated = Array.isArray(event.markets) && event.markets.length > MAX_EVENT_MARKETS;
|
|
5058
|
+
if (marketsTruncated) {
|
|
5059
|
+
return malformed("event market list exceeded the cap");
|
|
5060
|
+
}
|
|
5061
|
+
const marketList = takeBounded(event.markets, MAX_EVENT_MARKETS);
|
|
5062
|
+
if (marketList.some(
|
|
5063
|
+
(market) => !market || typeof market !== "object" || typeof market.sportsMarketType !== "string"
|
|
5064
|
+
)) {
|
|
5065
|
+
return malformed("event market is missing its market-type discriminator");
|
|
5066
|
+
}
|
|
5067
|
+
const moneyline = marketList.filter(
|
|
5068
|
+
(m) => m?.sportsMarketType === "moneyline"
|
|
4461
5069
|
);
|
|
4462
|
-
const
|
|
4463
|
-
const
|
|
4464
|
-
const
|
|
4465
|
-
|
|
5070
|
+
const homeSel = pickMarket(moneyline, match.home.code, match.home.name);
|
|
5071
|
+
const awaySel = pickMarket(moneyline, match.away.code, match.away.name);
|
|
5072
|
+
const drawSel = pickDraw(moneyline);
|
|
5073
|
+
for (const [side, sel] of [
|
|
5074
|
+
["home", homeSel],
|
|
5075
|
+
["away", awaySel],
|
|
5076
|
+
["draw", drawSel]
|
|
5077
|
+
]) {
|
|
5078
|
+
if (sel.kind === "ambiguous") {
|
|
5079
|
+
return ambiguous(`${sel.count} markets claim the ${side} outcome`);
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
if (homeSel.kind !== "one" || awaySel.kind !== "one" || drawSel.kind !== "one") {
|
|
5083
|
+
return definitiveNone("event does not carry all three 1X2 legs");
|
|
5084
|
+
}
|
|
5085
|
+
const homeMarket = homeSel.value;
|
|
5086
|
+
const awayMarket = awaySel.value;
|
|
5087
|
+
const drawMarket = drawSel.value;
|
|
4466
5088
|
const legIds = [homeMarket, awayMarket, drawMarket].filter((m) => m != null).map((m) => m.id ?? m.slug ?? "");
|
|
4467
|
-
if (new Set(legIds).size !== legIds.length)
|
|
5089
|
+
if (new Set(legIds).size !== legIds.length) {
|
|
5090
|
+
return ambiguous("two outcome legs are the same market");
|
|
5091
|
+
}
|
|
4468
5092
|
const legs = [
|
|
4469
5093
|
["home", homeMarket, match.home.code, match.home.name],
|
|
4470
5094
|
["draw", drawMarket, void 0, "Draw"],
|
|
4471
5095
|
["away", awayMarket, match.away.code, match.away.name]
|
|
4472
5096
|
];
|
|
4473
5097
|
const outcomes = [];
|
|
4474
|
-
let asOf = event.updatedAt;
|
|
5098
|
+
let asOf = canonicalTimestamp(event.updatedAt);
|
|
4475
5099
|
let liquidity;
|
|
4476
|
-
for (const [kind, market,
|
|
5100
|
+
for (const [kind, market, teamCode2, label] of legs) {
|
|
4477
5101
|
if (!market) continue;
|
|
4478
|
-
if (market.closed
|
|
4479
|
-
|
|
5102
|
+
if (typeof market.closed !== "boolean" || typeof market.active !== "boolean") {
|
|
5103
|
+
return malformed("market active/closed is not a boolean");
|
|
5104
|
+
}
|
|
5105
|
+
if (market.closed === true || market.active === false) {
|
|
5106
|
+
return definitiveNone("an outcome leg is closed or inactive");
|
|
5107
|
+
}
|
|
5108
|
+
if (market.description && NON_REGULAR_TIME.test(market.description)) {
|
|
5109
|
+
return definitiveNone("an outcome leg is not a regular-time market");
|
|
5110
|
+
}
|
|
4480
5111
|
const yes = yesPrice(market);
|
|
4481
|
-
if (yes == null) return
|
|
4482
|
-
outcomes.push({ kind, teamCode, label, probability: yes });
|
|
4483
|
-
|
|
4484
|
-
|
|
5112
|
+
if (yes == null) return malformed("market is not a readable Yes/No binary");
|
|
5113
|
+
outcomes.push({ kind, teamCode: teamCode2, label, probability: yes });
|
|
5114
|
+
const marketAsOf = canonicalTimestamp(market.updatedAt);
|
|
5115
|
+
if (!marketAsOf) {
|
|
5116
|
+
return malformed("market updatedAt missing or unparseable");
|
|
5117
|
+
}
|
|
5118
|
+
const nowMs = (options?.now ?? this.opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5119
|
+
if (Date.parse(marketAsOf) - nowMs > FUTURE_SKEW_MS) {
|
|
5120
|
+
return malformed("market updatedAt is dated forward");
|
|
5121
|
+
}
|
|
5122
|
+
if (!asOf || Date.parse(marketAsOf) < Date.parse(asOf)) asOf = marketAsOf;
|
|
5123
|
+
const rawLiq = market.liquidityNum ?? market.liquidity;
|
|
5124
|
+
const liq = numberish(rawLiq);
|
|
5125
|
+
if (rawLiq != null && liq == null) {
|
|
5126
|
+
return malformed("market liquidity is unreadable");
|
|
5127
|
+
}
|
|
4485
5128
|
if (liq != null) liquidity = liquidity == null ? liq : Math.min(liquidity, liq);
|
|
4486
5129
|
}
|
|
4487
5130
|
const rawSum = outcomes.reduce((s, o) => s + o.probability, 0);
|
|
4488
|
-
if (rawSum < 0.9 || rawSum > 1.15)
|
|
5131
|
+
if (rawSum < 0.9 || rawSum > 1.15) {
|
|
5132
|
+
return ambiguous("outcome probabilities do not form a coherent 1X2");
|
|
5133
|
+
}
|
|
5134
|
+
if (!asOf) return malformed("no usable timestamp on the event or its markets");
|
|
4489
5135
|
const signal = buildMarketSignal({
|
|
4490
5136
|
match,
|
|
4491
5137
|
source: "polymarket",
|
|
4492
|
-
|
|
4493
|
-
|
|
5138
|
+
// Echoed into MCP structured content (tools.ts `market.id`), i.e. straight
|
|
5139
|
+
// into an agent's context. Stripping control characters is NOT sufficient
|
|
5140
|
+
// there: printable prose ("IGNORE PREVIOUS INSTRUCTIONS") survives that and
|
|
5141
|
+
// is precisely what matters for a model reading it. Gamma ids are short
|
|
5142
|
+
// opaque tokens, so validate that GRAMMAR and otherwise fall back to the
|
|
5143
|
+
// slug we derived ourselves.
|
|
5144
|
+
// The fallback is grammar-checked too. It is normally a slug we derived
|
|
5145
|
+
// ourselves, but `mapping.2026.json` can override it, so echoing it raw
|
|
5146
|
+
// was the one path around the agent-facing filter this line exists for.
|
|
5147
|
+
sourceMarketId: safeMarketId(event.id) ?? safeDerivedSlug(eventSlug),
|
|
5148
|
+
asOf,
|
|
4494
5149
|
outcomes,
|
|
4495
5150
|
liquidity,
|
|
4496
5151
|
now: options?.now ?? this.opts.now,
|
|
4497
5152
|
maxAgeMs: options?.maxAgeMs ?? this.opts.maxAgeMs
|
|
4498
5153
|
});
|
|
4499
|
-
return signal.ambiguous ?
|
|
5154
|
+
return signal.ambiguous ? ambiguous("signal does not map cleanly onto this fixture") : valid(signal);
|
|
4500
5155
|
}
|
|
4501
5156
|
};
|
|
5157
|
+
function safeMarketId(id) {
|
|
5158
|
+
return typeof id === "string" && /^[0-9]{1,32}$/.test(id) ? id : void 0;
|
|
5159
|
+
}
|
|
5160
|
+
function safeDerivedSlug(slug) {
|
|
5161
|
+
return typeof slug === "string" && /^fifwc-[a-z]{2,3}-[a-z]{2,3}-\d{4}-\d{2}-\d{2}$/.test(slug) ? slug : void 0;
|
|
5162
|
+
}
|
|
4502
5163
|
var POLYMARKET_TOKEN = {
|
|
4503
5164
|
SUI: "che",
|
|
4504
5165
|
// Switzerland
|
|
@@ -4512,8 +5173,16 @@ var POLYMARKET_TOKEN = {
|
|
|
4512
5173
|
// Croatia
|
|
4513
5174
|
COD: "cdr",
|
|
4514
5175
|
// DR Congo
|
|
4515
|
-
CPV: "cvi"
|
|
5176
|
+
CPV: "cvi",
|
|
4516
5177
|
// Cabo Verde
|
|
5178
|
+
// TWO letters, not three — the one entry that is not ISO alpha-3. Verified
|
|
5179
|
+
// live: `fifwc-kor-cze-2026-06-11` is a 404, `fifwc-kr-cze-2026-06-11`
|
|
5180
|
+
// resolves to "Korea Republic vs. Czechia". Korea's three group fixtures
|
|
5181
|
+
// therefore had no market line at all. The `^[a-z]{3}$` guard in
|
|
5182
|
+
// `deriveEventSlugs` validates the FIFA CODE, not the token, so a two-letter
|
|
5183
|
+
// alias passes through it unharmed.
|
|
5184
|
+
KOR: "kr"
|
|
5185
|
+
// Korea Republic
|
|
4517
5186
|
};
|
|
4518
5187
|
function pmTokens(code) {
|
|
4519
5188
|
const c = code.toLowerCase();
|
|
@@ -4544,18 +5213,21 @@ function slugToken(m) {
|
|
|
4544
5213
|
return (m.slug ?? "").toLowerCase().split("-").pop() ?? "";
|
|
4545
5214
|
}
|
|
4546
5215
|
function isDrawMarket(m) {
|
|
4547
|
-
|
|
5216
|
+
const title = (m.groupItemTitle ?? "").trim().toLowerCase();
|
|
5217
|
+
return slugToken(m) === "draw" || title === "draw" || /^draw\s*\(/.test(title);
|
|
4548
5218
|
}
|
|
4549
|
-
function pickMarket(markets,
|
|
4550
|
-
const tokens = pmTokens(
|
|
5219
|
+
function pickMarket(markets, teamCode2, teamName) {
|
|
5220
|
+
const tokens = pmTokens(teamCode2);
|
|
4551
5221
|
const name = teamName.trim().toLowerCase();
|
|
4552
5222
|
const teamMarkets = markets.filter((m) => !isDrawMarket(m));
|
|
4553
|
-
const bySlug = teamMarkets.
|
|
4554
|
-
if (bySlug) return bySlug;
|
|
4555
|
-
|
|
5223
|
+
const bySlug = teamMarkets.filter((m) => tokens.includes(slugToken(m)));
|
|
5224
|
+
if (bySlug.length > 1) return { kind: "ambiguous", count: bySlug.length };
|
|
5225
|
+
const byTitle = name ? teamMarkets.filter((m) => (m.groupItemTitle ?? "").trim().toLowerCase() === name) : [];
|
|
5226
|
+
if (byTitle.length > 1) return { kind: "ambiguous", count: byTitle.length };
|
|
5227
|
+
return selectOne([.../* @__PURE__ */ new Set([...bySlug, ...byTitle])]);
|
|
4556
5228
|
}
|
|
4557
5229
|
function pickDraw(markets) {
|
|
4558
|
-
return markets.
|
|
5230
|
+
return selectOne(markets.filter(isDrawMarket));
|
|
4559
5231
|
}
|
|
4560
5232
|
function assertAllowedHost(base) {
|
|
4561
5233
|
let host;
|
|
@@ -4569,20 +5241,29 @@ function assertAllowedHost(base) {
|
|
|
4569
5241
|
}
|
|
4570
5242
|
}
|
|
4571
5243
|
function yesPrice(market) {
|
|
4572
|
-
const labels = parseJsonArray(market.outcomes);
|
|
4573
|
-
const
|
|
4574
|
-
if (
|
|
4575
|
-
const
|
|
4576
|
-
if (
|
|
4577
|
-
const
|
|
4578
|
-
|
|
5244
|
+
const labels = parseJsonArray(market.outcomes).map((l) => l.trim().toLowerCase());
|
|
5245
|
+
const raw = parseJsonArray(market.outcomePrices);
|
|
5246
|
+
if (raw.some((v) => v.trim() === "" || !Number.isFinite(Number(v)))) return void 0;
|
|
5247
|
+
const prices = raw.map((v) => Number(v));
|
|
5248
|
+
if (labels.length !== 2 || prices.length !== 2) return void 0;
|
|
5249
|
+
const i = labels.indexOf("yes");
|
|
5250
|
+
const j = labels.indexOf("no");
|
|
5251
|
+
if (i < 0 || j < 0) return void 0;
|
|
5252
|
+
const yes = prices[i];
|
|
5253
|
+
const no = prices[j];
|
|
5254
|
+
if (![yes, no].every((v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1)) {
|
|
5255
|
+
return void 0;
|
|
5256
|
+
}
|
|
5257
|
+
if (Math.abs(yes + no - 1) > 0.05) return void 0;
|
|
5258
|
+
return yes > 0 ? yes : void 0;
|
|
4579
5259
|
}
|
|
4580
5260
|
function parseJsonArray(v) {
|
|
4581
|
-
|
|
5261
|
+
const asText = (x) => typeof x === "string" || typeof x === "number" ? String(x) : "";
|
|
5262
|
+
if (Array.isArray(v)) return v.map(asText);
|
|
4582
5263
|
if (typeof v === "string") {
|
|
4583
5264
|
try {
|
|
4584
5265
|
const parsed = JSON.parse(v);
|
|
4585
|
-
return Array.isArray(parsed) ? parsed.map(
|
|
5266
|
+
return Array.isArray(parsed) ? parsed.map(asText) : [];
|
|
4586
5267
|
} catch {
|
|
4587
5268
|
return [];
|
|
4588
5269
|
}
|
|
@@ -4590,10 +5271,10 @@ function parseJsonArray(v) {
|
|
|
4590
5271
|
return [];
|
|
4591
5272
|
}
|
|
4592
5273
|
function numberish(v) {
|
|
4593
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
|
|
5274
|
+
if (typeof v === "number") return Number.isFinite(v) && v >= 0 ? v : void 0;
|
|
4594
5275
|
if (typeof v === "string") {
|
|
4595
5276
|
const n = Number(v);
|
|
4596
|
-
return Number.isFinite(n) ? n : void 0;
|
|
5277
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
4597
5278
|
}
|
|
4598
5279
|
return void 0;
|
|
4599
5280
|
}
|
|
@@ -4620,7 +5301,7 @@ async function getMarketSignals(provider, matches, options) {
|
|
|
4620
5301
|
try {
|
|
4621
5302
|
return await provider.findSignals(matches, options);
|
|
4622
5303
|
} catch {
|
|
4623
|
-
return
|
|
5304
|
+
return emptyBatch();
|
|
4624
5305
|
}
|
|
4625
5306
|
}
|
|
4626
5307
|
var SHARE_HASHTAG = "#VibingLaVidaLoca";
|
|
@@ -4701,6 +5382,9 @@ function formatShareSnippet(input, options = {}) {
|
|
|
4701
5382
|
if (input.degraded && input.matches.length > 0) {
|
|
4702
5383
|
blocks.push("(Live data unavailable \u2014 showing the bundled schedule, not live scores.)");
|
|
4703
5384
|
}
|
|
5385
|
+
if (includeMarkets && input.marketComplete === false) {
|
|
5386
|
+
blocks.push("(Market data unavailable or incomplete \u2014 not all fixtures were checked.)");
|
|
5387
|
+
}
|
|
4704
5388
|
blocks.push(
|
|
4705
5389
|
shareFooter({
|
|
4706
5390
|
source: input.source,
|
|
@@ -4731,7 +5415,9 @@ function formatShareTable(input, options = {}) {
|
|
|
4731
5415
|
const includeInstall = options.includeInstallLine !== false;
|
|
4732
5416
|
const blocks = [];
|
|
4733
5417
|
if (input.tables.length === 0) {
|
|
4734
|
-
blocks.push(
|
|
5418
|
+
blocks.push(
|
|
5419
|
+
input.emptyNote ?? (input.degraded ? "Live standings unavailable." : "No standings available.")
|
|
5420
|
+
);
|
|
4735
5421
|
} else {
|
|
4736
5422
|
for (const { group, rows } of input.tables) {
|
|
4737
5423
|
blocks.push(
|
|
@@ -5015,6 +5701,7 @@ var EN2 = {
|
|
|
5015
5701
|
"table.title": "Group {group}",
|
|
5016
5702
|
"table.none": "No group found for {group}.",
|
|
5017
5703
|
"table.degraded": "Live standings unavailable \u2014 showing the group roster.",
|
|
5704
|
+
"table.unavailable": "Live standings unavailable.",
|
|
5018
5705
|
"table.empty": "No standings available.",
|
|
5019
5706
|
"match.none": "No match found with id {id}.",
|
|
5020
5707
|
"status.live": "LIVE",
|
|
@@ -5055,6 +5742,7 @@ var ES2 = {
|
|
|
5055
5742
|
"table.title": "Grupo {group}",
|
|
5056
5743
|
"table.none": "No se encontr\xF3 el grupo {group}.",
|
|
5057
5744
|
"table.degraded": "Tabla en vivo no disponible \u2014 mostrando la lista del grupo.",
|
|
5745
|
+
"table.unavailable": "Tabla en vivo no disponible.",
|
|
5058
5746
|
"table.empty": "No hay clasificaci\xF3n disponible.",
|
|
5059
5747
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
5060
5748
|
"status.live": "EN VIVO",
|
|
@@ -5095,6 +5783,7 @@ var PT2 = {
|
|
|
5095
5783
|
"table.title": "Grupo {group}",
|
|
5096
5784
|
"table.none": "Grupo {group} n\xE3o encontrado.",
|
|
5097
5785
|
"table.degraded": "Classifica\xE7\xE3o ao vivo indispon\xEDvel \u2014 mostrando os times do grupo.",
|
|
5786
|
+
"table.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
|
|
5098
5787
|
"table.empty": "Classifica\xE7\xE3o indispon\xEDvel.",
|
|
5099
5788
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
5100
5789
|
"status.live": "AO VIVO",
|
|
@@ -5135,6 +5824,7 @@ var FR2 = {
|
|
|
5135
5824
|
"table.title": "Groupe {group}",
|
|
5136
5825
|
"table.none": "Groupe {group} introuvable.",
|
|
5137
5826
|
"table.degraded": "Classement en direct indisponible \u2014 affichage de la composition du groupe.",
|
|
5827
|
+
"table.unavailable": "Classement en direct indisponible.",
|
|
5138
5828
|
"table.empty": "Aucun classement disponible.",
|
|
5139
5829
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
5140
5830
|
"status.live": "DIRECT",
|
|
@@ -5245,7 +5935,19 @@ function dataSource(source, lang, c) {
|
|
|
5245
5935
|
}
|
|
5246
5936
|
|
|
5247
5937
|
// src/marketCache.ts
|
|
5248
|
-
import { readFileSync as
|
|
5938
|
+
import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
5939
|
+
import { join as join3 } from "path";
|
|
5940
|
+
|
|
5941
|
+
// src/cache.ts
|
|
5942
|
+
import {
|
|
5943
|
+
closeSync as closeSync2,
|
|
5944
|
+
mkdirSync as mkdirSync2,
|
|
5945
|
+
openSync as openSync2,
|
|
5946
|
+
readFileSync as readFileSync2,
|
|
5947
|
+
rmSync,
|
|
5948
|
+
statSync,
|
|
5949
|
+
writeSync as writeSync2
|
|
5950
|
+
} from "fs";
|
|
5249
5951
|
import { join as join2 } from "path";
|
|
5250
5952
|
|
|
5251
5953
|
// src/paths.ts
|
|
@@ -5268,133 +5970,63 @@ function writeFileAtomic(path, data) {
|
|
|
5268
5970
|
renameSync(tmp, path);
|
|
5269
5971
|
}
|
|
5270
5972
|
|
|
5271
|
-
// src/marketCache.ts
|
|
5272
|
-
var POSITIVE_TTL_MS = 10 * 6e4;
|
|
5273
|
-
var NEGATIVE_TTL_MS = 3 * 6e4;
|
|
5274
|
-
function cachePath() {
|
|
5275
|
-
return join2(cacheDir(), "market-signals.json");
|
|
5276
|
-
}
|
|
5277
|
-
function readFile() {
|
|
5278
|
-
try {
|
|
5279
|
-
return JSON.parse(readFileSync2(cachePath(), "utf8"));
|
|
5280
|
-
} catch {
|
|
5281
|
-
return void 0;
|
|
5282
|
-
}
|
|
5283
|
-
}
|
|
5284
|
-
function readMarketCache(source, competition, now = Date.now()) {
|
|
5285
|
-
const signals = /* @__PURE__ */ new Map();
|
|
5286
|
-
const checked = /* @__PURE__ */ new Set();
|
|
5287
|
-
const file = readFile();
|
|
5288
|
-
if (!file || file.source !== source || file.competition !== competition) {
|
|
5289
|
-
return { signals, checked };
|
|
5290
|
-
}
|
|
5291
|
-
for (const [id, entry] of Object.entries(file.entries ?? {})) {
|
|
5292
|
-
const t2 = Date.parse(entry.fetchedAt);
|
|
5293
|
-
if (!Number.isFinite(t2)) continue;
|
|
5294
|
-
const ttl = entry.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS;
|
|
5295
|
-
if (now - t2 > ttl) continue;
|
|
5296
|
-
checked.add(id);
|
|
5297
|
-
if (entry.signal) signals.set(id, entry.signal);
|
|
5298
|
-
}
|
|
5299
|
-
return { signals, checked };
|
|
5300
|
-
}
|
|
5301
|
-
function writeMarketCache(source, competition, attempted, fetched, now = Date.now()) {
|
|
5302
|
-
if (attempted.length === 0) return;
|
|
5303
|
-
try {
|
|
5304
|
-
const existing = readFile();
|
|
5305
|
-
const base = existing && existing.source === source && existing.competition === competition ? existing : { source, competition, entries: {} };
|
|
5306
|
-
const fetchedAt = new Date(now).toISOString();
|
|
5307
|
-
for (const id of attempted) {
|
|
5308
|
-
base.entries[id] = { fetchedAt, signal: fetched.get(id) ?? null };
|
|
5309
|
-
}
|
|
5310
|
-
writeFileAtomic(cachePath(), JSON.stringify(base));
|
|
5311
|
-
} catch {
|
|
5312
|
-
}
|
|
5313
|
-
}
|
|
5314
|
-
|
|
5315
|
-
// src/starNudge.ts
|
|
5316
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
5317
|
-
import { join as join3 } from "path";
|
|
5318
|
-
var REPO_URL = "https://github.com/arturogarrido/claudinho";
|
|
5319
|
-
var NUDGE_EVERY = 5;
|
|
5320
|
-
function counterPath() {
|
|
5321
|
-
return join3(cacheDir(), "runs.json");
|
|
5322
|
-
}
|
|
5323
|
-
function shouldNudge(runCount, every = NUDGE_EVERY) {
|
|
5324
|
-
return runCount > 0 && runCount % every === 0;
|
|
5325
|
-
}
|
|
5326
|
-
function bumpRunCount(path = counterPath()) {
|
|
5327
|
-
try {
|
|
5328
|
-
let count = 0;
|
|
5329
|
-
try {
|
|
5330
|
-
const raw = JSON.parse(readFileSync3(path, "utf8"));
|
|
5331
|
-
if (typeof raw.count === "number" && Number.isFinite(raw.count)) count = raw.count;
|
|
5332
|
-
} catch {
|
|
5333
|
-
count = 0;
|
|
5334
|
-
}
|
|
5335
|
-
count += 1;
|
|
5336
|
-
writeFileAtomic(path, JSON.stringify({ count }));
|
|
5337
|
-
return count;
|
|
5338
|
-
} catch {
|
|
5339
|
-
return void 0;
|
|
5340
|
-
}
|
|
5341
|
-
}
|
|
5342
|
-
|
|
5343
|
-
// src/clipboard.ts
|
|
5344
|
-
import { spawnSync } from "child_process";
|
|
5345
|
-
function clipboardTools(platform) {
|
|
5346
|
-
if (platform === "darwin") return [{ cmd: "pbcopy", args: [] }];
|
|
5347
|
-
if (platform === "win32") return [{ cmd: "clip", args: [] }];
|
|
5348
|
-
return [
|
|
5349
|
-
{ cmd: "wl-copy", args: [] },
|
|
5350
|
-
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
5351
|
-
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
5352
|
-
];
|
|
5353
|
-
}
|
|
5354
|
-
function copyToClipboard(text, platform = process.platform) {
|
|
5355
|
-
for (const { cmd, args } of clipboardTools(platform)) {
|
|
5356
|
-
try {
|
|
5357
|
-
const res = spawnSync(cmd, args, {
|
|
5358
|
-
input: text,
|
|
5359
|
-
stdio: ["pipe", "ignore", "ignore"],
|
|
5360
|
-
timeout: 1e3
|
|
5361
|
-
});
|
|
5362
|
-
if (!res.error && res.status === 0) return true;
|
|
5363
|
-
} catch {
|
|
5364
|
-
}
|
|
5365
|
-
}
|
|
5366
|
-
return false;
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
5973
|
// src/cache.ts
|
|
5370
|
-
import {
|
|
5371
|
-
closeSync as closeSync2,
|
|
5372
|
-
mkdirSync as mkdirSync2,
|
|
5373
|
-
openSync as openSync2,
|
|
5374
|
-
readFileSync as readFileSync4,
|
|
5375
|
-
rmSync,
|
|
5376
|
-
statSync,
|
|
5377
|
-
writeSync as writeSync2
|
|
5378
|
-
} from "fs";
|
|
5379
|
-
import { join as join4 } from "path";
|
|
5380
5974
|
var CACHE_VERSION = 2;
|
|
5975
|
+
var MAX_STATE_BYTES = 1024 * 1024;
|
|
5976
|
+
var MAX_STATE_RECORDS = 1024;
|
|
5381
5977
|
var LOCK_STALE_MS = 6e4;
|
|
5382
|
-
function
|
|
5978
|
+
function cachePath(source = "espn", competition = DEFAULT_COMPETITION) {
|
|
5383
5979
|
if (source === "espn" && competition === DEFAULT_COMPETITION) {
|
|
5384
|
-
return
|
|
5980
|
+
return join2(cacheDir(), "state.json");
|
|
5385
5981
|
}
|
|
5386
5982
|
const slug = `${source}.${competition}`.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
5387
|
-
return
|
|
5983
|
+
return join2(cacheDir(), `state.${slug}.json`);
|
|
5388
5984
|
}
|
|
5389
5985
|
function lockPath() {
|
|
5390
|
-
return
|
|
5986
|
+
return join2(cacheDir(), "refresh.lock");
|
|
5987
|
+
}
|
|
5988
|
+
function validStamp(value) {
|
|
5989
|
+
if (typeof value !== "string") return false;
|
|
5990
|
+
const match = value.match(
|
|
5991
|
+
/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,3}))?Z$/
|
|
5992
|
+
);
|
|
5993
|
+
if (!match) return false;
|
|
5994
|
+
const parsed = Date.parse(value);
|
|
5995
|
+
if (!Number.isFinite(parsed)) return false;
|
|
5996
|
+
const canonical = `${match[1]}.${(match[2] ?? "").padEnd(3, "0")}Z`;
|
|
5997
|
+
return new Date(parsed).toISOString() === canonical;
|
|
5998
|
+
}
|
|
5999
|
+
function validScope(value) {
|
|
6000
|
+
return typeof value === "string" && value.length > 0 && value.length <= 128 && /^[a-zA-Z0-9._-]+$/.test(value);
|
|
6001
|
+
}
|
|
6002
|
+
function isCacheState(value) {
|
|
6003
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
6004
|
+
const s = value;
|
|
6005
|
+
if (s.version !== CACHE_VERSION) return false;
|
|
6006
|
+
if (!validStamp(s.updatedAt) || typeof s.degraded !== "boolean") return false;
|
|
6007
|
+
if (!validScope(s.source) || !validScope(s.competition)) return false;
|
|
6008
|
+
if (!Array.isArray(s.live) || s.live.length > MAX_STATE_RECORDS) return false;
|
|
6009
|
+
if (s.fixtures !== void 0 && (!Array.isArray(s.fixtures) || s.fixtures.length > MAX_STATE_RECORDS)) {
|
|
6010
|
+
return false;
|
|
6011
|
+
}
|
|
6012
|
+
for (const key of [
|
|
6013
|
+
"fixturesUpdatedAt",
|
|
6014
|
+
"fixturesAttemptedAt",
|
|
6015
|
+
"backoffUntil"
|
|
6016
|
+
]) {
|
|
6017
|
+
if (s[key] !== void 0 && !validStamp(s[key])) return false;
|
|
6018
|
+
}
|
|
6019
|
+
return true;
|
|
5391
6020
|
}
|
|
5392
6021
|
function readState(source = "espn", competition = DEFAULT_COMPETITION) {
|
|
5393
6022
|
try {
|
|
5394
|
-
const
|
|
5395
|
-
|
|
5396
|
-
);
|
|
5397
|
-
|
|
6023
|
+
const path = cachePath(source, competition);
|
|
6024
|
+
const info = statSync(path);
|
|
6025
|
+
if (!info.isFile() || info.size > MAX_STATE_BYTES) return void 0;
|
|
6026
|
+
const bytes = readFileSync2(path);
|
|
6027
|
+
if (bytes.byteLength > MAX_STATE_BYTES) return void 0;
|
|
6028
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
6029
|
+
return isCacheState(parsed) ? parsed : void 0;
|
|
5398
6030
|
} catch {
|
|
5399
6031
|
return void 0;
|
|
5400
6032
|
}
|
|
@@ -5405,41 +6037,45 @@ function readCurrentState(source, competition) {
|
|
|
5405
6037
|
}
|
|
5406
6038
|
function writeState(state) {
|
|
5407
6039
|
writeFileAtomic(
|
|
5408
|
-
|
|
6040
|
+
cachePath(state.source, state.competition),
|
|
5409
6041
|
JSON.stringify({ ...state, version: CACHE_VERSION })
|
|
5410
6042
|
);
|
|
5411
6043
|
}
|
|
6044
|
+
var MAX_BACKOFF_MS = 30 * 6e4;
|
|
5412
6045
|
function backoffActive(state, now = Date.now()) {
|
|
5413
6046
|
if (!state?.backoffUntil) return false;
|
|
5414
6047
|
const t2 = Date.parse(state.backoffUntil);
|
|
5415
|
-
|
|
6048
|
+
if (!Number.isFinite(t2)) return false;
|
|
6049
|
+
return now < t2 && t2 - now <= MAX_BACKOFF_MS;
|
|
5416
6050
|
}
|
|
5417
6051
|
function fixturesAttemptAgeMs(state, now = Date.now()) {
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
6052
|
+
return stampAgeMs(state?.fixturesAttemptedAt, now);
|
|
6053
|
+
}
|
|
6054
|
+
var FUTURE_SKEW_MS2 = 6e4;
|
|
6055
|
+
function stampAgeMs(value, now) {
|
|
6056
|
+
if (!value) return Infinity;
|
|
6057
|
+
const t2 = Date.parse(value);
|
|
6058
|
+
if (!Number.isFinite(t2)) return Infinity;
|
|
6059
|
+
const age = now - t2;
|
|
6060
|
+
return age < -FUTURE_SKEW_MS2 ? Infinity : age;
|
|
5421
6061
|
}
|
|
5422
6062
|
function ageMs(state, now = Date.now()) {
|
|
5423
|
-
|
|
5424
|
-
const t2 = Date.parse(state.updatedAt);
|
|
5425
|
-
return Number.isFinite(t2) ? now - t2 : Infinity;
|
|
6063
|
+
return stampAgeMs(state?.updatedAt, now);
|
|
5426
6064
|
}
|
|
5427
6065
|
function fixturesAgeMs(state, now = Date.now()) {
|
|
5428
|
-
|
|
5429
|
-
const t2 = Date.parse(state.fixturesUpdatedAt);
|
|
5430
|
-
return Number.isFinite(t2) ? now - t2 : Infinity;
|
|
6066
|
+
return stampAgeMs(state?.fixturesUpdatedAt, now);
|
|
5431
6067
|
}
|
|
5432
6068
|
function lockAgeMs(now = Date.now()) {
|
|
5433
6069
|
const lp = lockPath();
|
|
5434
6070
|
try {
|
|
5435
|
-
const contents =
|
|
6071
|
+
const contents = readFileSync2(lp, "utf8");
|
|
5436
6072
|
const written = Number.parseInt(contents.split(/\s+/)[1] ?? "", 10);
|
|
5437
|
-
if (Number.isFinite(written)) return
|
|
6073
|
+
if (Number.isFinite(written)) return stampAgeMs(new Date(written).toISOString(), now);
|
|
5438
6074
|
} catch {
|
|
5439
6075
|
return Infinity;
|
|
5440
6076
|
}
|
|
5441
6077
|
try {
|
|
5442
|
-
return
|
|
6078
|
+
return stampAgeMs(new Date(statSync(lp).mtimeMs).toISOString(), now);
|
|
5443
6079
|
} catch {
|
|
5444
6080
|
return Infinity;
|
|
5445
6081
|
}
|
|
@@ -5487,6 +6123,169 @@ function releaseLock() {
|
|
|
5487
6123
|
}
|
|
5488
6124
|
}
|
|
5489
6125
|
|
|
6126
|
+
// src/marketCache.ts
|
|
6127
|
+
var POSITIVE_TTL_MS = 10 * 6e4;
|
|
6128
|
+
var NEGATIVE_TTL_MS = 3 * 6e4;
|
|
6129
|
+
var MAX_MARKET_CACHE_BYTES = 1024 * 1024;
|
|
6130
|
+
var FUTURE_SKEW_MS3 = 6e4;
|
|
6131
|
+
function cachePath2() {
|
|
6132
|
+
return join3(cacheDir(), "market-signals.json");
|
|
6133
|
+
}
|
|
6134
|
+
function readFile() {
|
|
6135
|
+
try {
|
|
6136
|
+
const path = cachePath2();
|
|
6137
|
+
const info = statSync2(path);
|
|
6138
|
+
if (!info.isFile() || info.size > MAX_MARKET_CACHE_BYTES) return void 0;
|
|
6139
|
+
const bytes = readFileSync3(path);
|
|
6140
|
+
if (bytes.byteLength > MAX_MARKET_CACHE_BYTES) return void 0;
|
|
6141
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
6142
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
6143
|
+
return parsed;
|
|
6144
|
+
} catch {
|
|
6145
|
+
return void 0;
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
function isEntryShaped(e) {
|
|
6149
|
+
if (!e || typeof e !== "object") return false;
|
|
6150
|
+
const entry = e;
|
|
6151
|
+
if (typeof entry.fetchedAt !== "string" || !Number.isFinite(Date.parse(entry.fetchedAt))) {
|
|
6152
|
+
return false;
|
|
6153
|
+
}
|
|
6154
|
+
if (entry.signal === null) return true;
|
|
6155
|
+
return !!entry.signal && typeof entry.signal === "object";
|
|
6156
|
+
}
|
|
6157
|
+
function isUsableSignal(s) {
|
|
6158
|
+
if (!s) return false;
|
|
6159
|
+
if (s.source === "" || s.asOf === "" || s.outcomes.length === 0) return false;
|
|
6160
|
+
if (s.outcomes.some((o) => o.kind === "other")) return false;
|
|
6161
|
+
if (s.ambiguous) return false;
|
|
6162
|
+
if (!s.favorite) return false;
|
|
6163
|
+
return hasSaneDistribution(s.outcomes);
|
|
6164
|
+
}
|
|
6165
|
+
function readMarketCache(source, competition, now = Date.now()) {
|
|
6166
|
+
const signals = /* @__PURE__ */ new Map();
|
|
6167
|
+
const checked = /* @__PURE__ */ new Set();
|
|
6168
|
+
const file = readFile();
|
|
6169
|
+
if (!file || file.source !== source || file.competition !== competition) {
|
|
6170
|
+
return { signals, checked };
|
|
6171
|
+
}
|
|
6172
|
+
const entries = file.entries;
|
|
6173
|
+
if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
|
|
6174
|
+
return { signals, checked };
|
|
6175
|
+
}
|
|
6176
|
+
let examined = 0;
|
|
6177
|
+
for (const id in entries) {
|
|
6178
|
+
if (!Object.hasOwn(entries, id)) continue;
|
|
6179
|
+
if (examined >= 256) break;
|
|
6180
|
+
examined += 1;
|
|
6181
|
+
const raw = entries[id];
|
|
6182
|
+
if (!isEntryShaped(raw)) continue;
|
|
6183
|
+
const entry = raw;
|
|
6184
|
+
const t2 = Date.parse(entry.fetchedAt);
|
|
6185
|
+
const ttl = entry.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS;
|
|
6186
|
+
const age = now - t2;
|
|
6187
|
+
if (age > ttl || age < -FUTURE_SKEW_MS3) continue;
|
|
6188
|
+
if (entry.signal === null) {
|
|
6189
|
+
checked.add(id);
|
|
6190
|
+
continue;
|
|
6191
|
+
}
|
|
6192
|
+
const sealed = parseCachedMarketSignal(entry.signal, { now: new Date(now) });
|
|
6193
|
+
if (sealed.kind !== "valid") continue;
|
|
6194
|
+
const clean = sealed.value;
|
|
6195
|
+
if (clean.matchId !== id || clean.source !== source) continue;
|
|
6196
|
+
if (!isUsableSignal(clean)) continue;
|
|
6197
|
+
checked.add(id);
|
|
6198
|
+
signals.set(id, clean);
|
|
6199
|
+
}
|
|
6200
|
+
return { signals, checked };
|
|
6201
|
+
}
|
|
6202
|
+
function writeMarketCache(source, competition, attempted, fetched, now = Date.now()) {
|
|
6203
|
+
if (attempted.length === 0) return;
|
|
6204
|
+
try {
|
|
6205
|
+
const existing = readFile();
|
|
6206
|
+
const reuse = existing && existing.source === source && existing.competition === competition;
|
|
6207
|
+
const carried = {};
|
|
6208
|
+
if (reuse && existing.entries && typeof existing.entries === "object" && !Array.isArray(existing.entries)) {
|
|
6209
|
+
let examined = 0;
|
|
6210
|
+
for (const id in existing.entries) {
|
|
6211
|
+
if (!Object.hasOwn(existing.entries, id)) continue;
|
|
6212
|
+
if (examined >= 256) break;
|
|
6213
|
+
examined += 1;
|
|
6214
|
+
const raw = existing.entries[id];
|
|
6215
|
+
if (!isEntryShaped(raw)) continue;
|
|
6216
|
+
const age = stampAgeMs(raw.fetchedAt, now);
|
|
6217
|
+
if (age > (raw.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS)) continue;
|
|
6218
|
+
if (age < -FUTURE_SKEW_MS3) continue;
|
|
6219
|
+
if (raw.signal !== null && !isUsableSignal(parsedValue(parseCachedMarketSignal(raw.signal, { now: new Date(now) })))) {
|
|
6220
|
+
continue;
|
|
6221
|
+
}
|
|
6222
|
+
carried[id] = raw;
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
const base = { source, competition, entries: carried };
|
|
6226
|
+
const fetchedAt = new Date(now).toISOString();
|
|
6227
|
+
for (const id of attempted.slice(0, 256)) {
|
|
6228
|
+
base.entries[id] = { fetchedAt, signal: fetched.get(id) ?? null };
|
|
6229
|
+
}
|
|
6230
|
+
writeFileAtomic(cachePath2(), JSON.stringify(base));
|
|
6231
|
+
} catch {
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
|
|
6235
|
+
// src/starNudge.ts
|
|
6236
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
6237
|
+
import { join as join4 } from "path";
|
|
6238
|
+
var REPO_URL = "https://github.com/arturogarrido/claudinho";
|
|
6239
|
+
var NUDGE_EVERY = 5;
|
|
6240
|
+
function counterPath() {
|
|
6241
|
+
return join4(cacheDir(), "runs.json");
|
|
6242
|
+
}
|
|
6243
|
+
function shouldNudge(runCount, every = NUDGE_EVERY) {
|
|
6244
|
+
return runCount > 0 && runCount % every === 0;
|
|
6245
|
+
}
|
|
6246
|
+
function bumpRunCount(path = counterPath()) {
|
|
6247
|
+
try {
|
|
6248
|
+
let count2 = 0;
|
|
6249
|
+
try {
|
|
6250
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
6251
|
+
if (typeof raw.count === "number" && Number.isFinite(raw.count)) count2 = raw.count;
|
|
6252
|
+
} catch {
|
|
6253
|
+
count2 = 0;
|
|
6254
|
+
}
|
|
6255
|
+
count2 += 1;
|
|
6256
|
+
writeFileAtomic(path, JSON.stringify({ count: count2 }));
|
|
6257
|
+
return count2;
|
|
6258
|
+
} catch {
|
|
6259
|
+
return void 0;
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
// src/clipboard.ts
|
|
6264
|
+
import { spawnSync } from "child_process";
|
|
6265
|
+
function clipboardTools(platform) {
|
|
6266
|
+
if (platform === "darwin") return [{ cmd: "pbcopy", args: [] }];
|
|
6267
|
+
if (platform === "win32") return [{ cmd: "clip", args: [] }];
|
|
6268
|
+
return [
|
|
6269
|
+
{ cmd: "wl-copy", args: [] },
|
|
6270
|
+
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
6271
|
+
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
6272
|
+
];
|
|
6273
|
+
}
|
|
6274
|
+
function copyToClipboard(text, platform = process.platform) {
|
|
6275
|
+
for (const { cmd, args } of clipboardTools(platform)) {
|
|
6276
|
+
try {
|
|
6277
|
+
const res = spawnSync(cmd, args, {
|
|
6278
|
+
input: text,
|
|
6279
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
6280
|
+
timeout: 1e3
|
|
6281
|
+
});
|
|
6282
|
+
if (!res.error && res.status === 0) return true;
|
|
6283
|
+
} catch {
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
return false;
|
|
6287
|
+
}
|
|
6288
|
+
|
|
5490
6289
|
// src/statusline.ts
|
|
5491
6290
|
var DISPLAY_STALE_MS = 5 * 6e4;
|
|
5492
6291
|
var TOURNAMENT_COMPLETE_LINE = "\u26BD World Cup 2026 is complete \xB7 Thanks for vibing with Claudinho";
|
|
@@ -5523,36 +6322,110 @@ function matchSegment(m, compact, flags) {
|
|
|
5523
6322
|
const away = compact ? m.away.flag : `${m.away.code} ${m.away.flag}`;
|
|
5524
6323
|
return `${home} ${scoreline(m)} ${away} ${minute}`;
|
|
5525
6324
|
}
|
|
6325
|
+
var MAX_LIVE_CONSIDERED = 64;
|
|
6326
|
+
var MAX_LIVE_EXAMINED = 512;
|
|
6327
|
+
function sealFixtures(raw) {
|
|
6328
|
+
if (raw === void 0) {
|
|
6329
|
+
return { items: [], total: 0, shown: 0, truncated: false, complete: true };
|
|
6330
|
+
}
|
|
6331
|
+
if (!Array.isArray(raw)) {
|
|
6332
|
+
return { items: [], total: 0, shown: 0, truncated: false, complete: false };
|
|
6333
|
+
}
|
|
6334
|
+
const out2 = [];
|
|
6335
|
+
let inspected = 0;
|
|
6336
|
+
let readable = true;
|
|
6337
|
+
for (const rec of raw) {
|
|
6338
|
+
if (out2.length >= MAX_LIVE_CONSIDERED || inspected >= MAX_LIVE_EXAMINED) break;
|
|
6339
|
+
inspected++;
|
|
6340
|
+
if (!isMatchShaped(rec)) {
|
|
6341
|
+
readable = false;
|
|
6342
|
+
continue;
|
|
6343
|
+
}
|
|
6344
|
+
const sealed = parsedValue(parseCachedMatch(rec, { events: false }));
|
|
6345
|
+
if (sealed) out2.push(sealed);
|
|
6346
|
+
else readable = false;
|
|
6347
|
+
}
|
|
6348
|
+
const exhausted = inspected === raw.length;
|
|
6349
|
+
const complete = exhausted && readable;
|
|
6350
|
+
return {
|
|
6351
|
+
items: out2,
|
|
6352
|
+
// Exact only when complete; otherwise this is the number actually sealed.
|
|
6353
|
+
// Callers use a nonnumeric marker for an incomplete scan.
|
|
6354
|
+
total: out2.length,
|
|
6355
|
+
shown: out2.length,
|
|
6356
|
+
truncated: !exhausted,
|
|
6357
|
+
complete
|
|
6358
|
+
};
|
|
6359
|
+
}
|
|
5526
6360
|
function liveMatchesFromCache(state, nowMs = Date.now()) {
|
|
5527
6361
|
const fresh = state && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
5528
|
-
const
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
6362
|
+
const rawLive = fresh ? state?.live : [];
|
|
6363
|
+
if (!Array.isArray(rawLive)) {
|
|
6364
|
+
return { items: [], total: 0, shown: 0, truncated: false, complete: false };
|
|
6365
|
+
}
|
|
6366
|
+
const liveArr = rawLive;
|
|
6367
|
+
const out2 = [];
|
|
6368
|
+
let inspected = 0;
|
|
6369
|
+
let readable = true;
|
|
6370
|
+
for (let i = 0; i < liveArr.length; i++) {
|
|
6371
|
+
if (out2.length >= MAX_LIVE_CONSIDERED || inspected >= MAX_LIVE_EXAMINED) break;
|
|
6372
|
+
inspected++;
|
|
6373
|
+
const raw = liveArr[i];
|
|
6374
|
+
if (!raw || typeof raw !== "object") {
|
|
6375
|
+
readable = false;
|
|
6376
|
+
continue;
|
|
6377
|
+
}
|
|
6378
|
+
const m = raw;
|
|
6379
|
+
if (!isLive(m.status) || !m.home?.code || !m.away?.code) {
|
|
6380
|
+
readable = false;
|
|
6381
|
+
continue;
|
|
6382
|
+
}
|
|
6383
|
+
const sealed = parsedValue(parseCachedMatch(m, { events: false }));
|
|
6384
|
+
if (sealed) out2.push(sealed);
|
|
6385
|
+
else readable = false;
|
|
6386
|
+
}
|
|
6387
|
+
const exhausted = inspected === liveArr.length;
|
|
6388
|
+
const complete = exhausted && readable;
|
|
6389
|
+
return {
|
|
6390
|
+
items: out2,
|
|
6391
|
+
total: out2.length,
|
|
6392
|
+
shown: out2.length,
|
|
6393
|
+
truncated: !exhausted,
|
|
6394
|
+
// False when we stopped early or a record was unreadable. In either case an
|
|
6395
|
+
// empty list must not render as the authoritative "nothing is on".
|
|
6396
|
+
complete
|
|
6397
|
+
};
|
|
5532
6398
|
}
|
|
6399
|
+
var DEFAULT_MAX_SEGMENTS = 8;
|
|
6400
|
+
var MAX_LINE_COLUMNS = 200;
|
|
5533
6401
|
function renderPrompt(state, opts = {}) {
|
|
6402
|
+
return truncateVisible(renderPromptLine(state, opts), MAX_LINE_COLUMNS);
|
|
6403
|
+
}
|
|
6404
|
+
function renderPromptLine(state, opts = {}) {
|
|
5534
6405
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
5535
6406
|
const nowMs = now.getTime();
|
|
5536
6407
|
const defaultCompetition = opts.defaultCompetition ?? true;
|
|
5537
6408
|
const compact = opts.compact ?? true;
|
|
5538
6409
|
const flags = opts.flags ?? true;
|
|
5539
6410
|
const team = opts.team?.toUpperCase();
|
|
5540
|
-
const
|
|
5541
|
-
const
|
|
6411
|
+
const liveList = liveMatchesFromCache(state, nowMs);
|
|
6412
|
+
const live = liveList.items;
|
|
6413
|
+
const cachedFixtureList = sealFixtures(state?.fixtures);
|
|
6414
|
+
const cachedFixtures = [...cachedFixtureList.items];
|
|
5542
6415
|
const schedule = cachedFixtures.length ? mergeLive(allFixtures(), cachedFixtures) : void 0;
|
|
5543
6416
|
if (team) {
|
|
5544
6417
|
const mine = live.find((m) => m.home?.code === team || m.away?.code === team);
|
|
5545
6418
|
if (mine) return `\u26BD ${matchSegment(mine, compact, flags)}`;
|
|
5546
6419
|
} else if (live.length > 0) {
|
|
5547
|
-
const max = opts.max && opts.max > 0 ? opts.max :
|
|
6420
|
+
const max = opts.max && opts.max > 0 ? Math.min(opts.max, DEFAULT_MAX_SEGMENTS) : DEFAULT_MAX_SEGMENTS;
|
|
5548
6421
|
const shown = live.slice(0, max);
|
|
5549
|
-
let line2 = "\u26BD " + shown.map((m) => matchSegment(m, compact, flags)).join(" \xB7 ");
|
|
5550
6422
|
const overflow = live.length - shown.length;
|
|
5551
|
-
|
|
5552
|
-
|
|
6423
|
+
const marker = !liveList.complete ? " +more" : overflow > 0 ? ` +${overflow}` : "";
|
|
6424
|
+
const body = "\u26BD " + shown.map((m) => matchSegment(m, compact, flags)).join(" \xB7 ");
|
|
6425
|
+
return truncateVisible(body, MAX_LINE_COLUMNS - displayWidth(marker)) + marker;
|
|
5553
6426
|
}
|
|
5554
6427
|
const cacheFresh = !!state && state.degraded !== true && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
5555
|
-
if (!cacheFresh) {
|
|
6428
|
+
if (!cacheFresh || !liveList.complete) {
|
|
5556
6429
|
const win = fixturesInLiveWindow(nowMs, schedule).filter(
|
|
5557
6430
|
(m) => !team || m.home.code === team || m.away.code === team
|
|
5558
6431
|
);
|
|
@@ -5574,6 +6447,15 @@ function renderPrompt(state, opts = {}) {
|
|
|
5574
6447
|
}
|
|
5575
6448
|
|
|
5576
6449
|
// src/hook.ts
|
|
6450
|
+
var MAX_HOOK_MATCHES = 12;
|
|
6451
|
+
var MAX_HOOK_CODE_POINTS = 4096;
|
|
6452
|
+
function boundContext(text, marker = "") {
|
|
6453
|
+
const points = [...text];
|
|
6454
|
+
if (points.length + [...marker].length <= MAX_HOOK_CODE_POINTS) return text + marker;
|
|
6455
|
+
const room = Math.max(0, MAX_HOOK_CODE_POINTS - [...marker].length);
|
|
6456
|
+
return `${points.slice(0, room).join("")}
|
|
6457
|
+
(context truncated)${marker}`;
|
|
6458
|
+
}
|
|
5577
6459
|
function rosterPinned(t2) {
|
|
5578
6460
|
const { team } = lookupTeam(t2.code);
|
|
5579
6461
|
return team ? { ...t2, name: team.name, flag: team.flag } : t2;
|
|
@@ -5590,7 +6472,8 @@ function renderHook(state, opts = {}) {
|
|
|
5590
6472
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
5591
6473
|
const team = opts.team?.toUpperCase();
|
|
5592
6474
|
const flags = opts.flags ?? true;
|
|
5593
|
-
|
|
6475
|
+
const liveList = liveMatchesFromCache(state, now.getTime());
|
|
6476
|
+
let live = [...liveList.items];
|
|
5594
6477
|
if (live.length === 0) return "";
|
|
5595
6478
|
if (team) {
|
|
5596
6479
|
live = [...live].sort((a, b) => {
|
|
@@ -5599,9 +6482,13 @@ function renderHook(state, opts = {}) {
|
|
|
5599
6482
|
return aHas - bHas;
|
|
5600
6483
|
});
|
|
5601
6484
|
}
|
|
5602
|
-
const
|
|
5603
|
-
|
|
5604
|
-
|
|
6485
|
+
const shown = live.slice(0, MAX_HOOK_MATCHES);
|
|
6486
|
+
const overflow = live.length - shown.length;
|
|
6487
|
+
const lines = shown.map((mm) => line(mm, flags)).join("\n");
|
|
6488
|
+
const more = !liveList.complete ? "\n(more live matches may not be shown)" : overflow > 0 ? `
|
|
6489
|
+
(+${overflow} more not shown)` : "";
|
|
6490
|
+
return boundContext(`[Claudinho \u2014 live football scores right now]
|
|
6491
|
+
${lines}`, more);
|
|
5605
6492
|
}
|
|
5606
6493
|
|
|
5607
6494
|
// src/refresh.ts
|
|
@@ -5890,11 +6777,15 @@ function adapterFor({ cfg, adapter }) {
|
|
|
5890
6777
|
}
|
|
5891
6778
|
var DEFAULT_ON_MARKET_OPTS = { deadlineMs: 2e3, timeoutMs: 2500 };
|
|
5892
6779
|
var MARKETS_CMD_OPTS = { deadlineMs: 12e3, timeoutMs: 6e3 };
|
|
5893
|
-
async function marketSignalsFor(ctx, matches, opts = {}) {
|
|
5894
|
-
if (ctx.marketProvider)
|
|
6780
|
+
async function marketSignalsFor(ctx, matches, opts = {}, providerFactory = makeMarketProvider) {
|
|
6781
|
+
if (ctx.marketProvider) {
|
|
6782
|
+
const b = await getMarketSignals(ctx.marketProvider, matches, opts);
|
|
6783
|
+
return { signals: resolvedValues(b), complete: b.complete };
|
|
6784
|
+
}
|
|
5895
6785
|
const source = resolveMarketSource();
|
|
5896
6786
|
if (source !== "polymarket") {
|
|
5897
|
-
|
|
6787
|
+
const b = await getMarketSignals(providerFactory(source), matches, opts);
|
|
6788
|
+
return { signals: resolvedValues(b), complete: b.complete };
|
|
5898
6789
|
}
|
|
5899
6790
|
const competition = resolveCompetition();
|
|
5900
6791
|
const { signals: cached, checked: cachedIds } = readMarketCache("polymarket", competition);
|
|
@@ -5902,35 +6793,39 @@ async function marketSignalsFor(ctx, matches, opts = {}) {
|
|
|
5902
6793
|
const miss = [];
|
|
5903
6794
|
for (const m of matches) {
|
|
5904
6795
|
const hit = cached.get(m.id);
|
|
5905
|
-
if (hit
|
|
5906
|
-
|
|
6796
|
+
if (hit && marketSignalRendersFor(m, hit)) {
|
|
6797
|
+
result.set(m.id, hit);
|
|
6798
|
+
continue;
|
|
6799
|
+
}
|
|
6800
|
+
if (!hit && cachedIds.has(m.id)) continue;
|
|
6801
|
+
miss.push(m);
|
|
5907
6802
|
}
|
|
6803
|
+
let complete = true;
|
|
5908
6804
|
if (miss.length > 0) {
|
|
5909
|
-
const
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
);
|
|
5914
|
-
writeMarketCache("polymarket", competition, [...checked], fetched);
|
|
6805
|
+
const batch = await getMarketSignals(providerFactory("polymarket"), miss, opts);
|
|
6806
|
+
const fetched = resolvedValues(batch);
|
|
6807
|
+
complete = batch.complete;
|
|
6808
|
+
writeMarketCache("polymarket", competition, [...cacheableKeys(batch)], fetched);
|
|
5915
6809
|
for (const [id, s] of fetched) result.set(id, s);
|
|
5916
6810
|
}
|
|
5917
|
-
return result;
|
|
6811
|
+
return { signals: result, complete };
|
|
5918
6812
|
}
|
|
5919
6813
|
async function reliableMarketSignals(ctx, matches) {
|
|
5920
|
-
if (ctx.cfg.markets === false) return /* @__PURE__ */ new Map();
|
|
6814
|
+
if (ctx.cfg.markets === false) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
5921
6815
|
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
5922
6816
|
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
5923
|
-
if (relevant.length === 0) return /* @__PURE__ */ new Map();
|
|
6817
|
+
if (relevant.length === 0) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
5924
6818
|
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
|
|
5925
6819
|
const out2 = /* @__PURE__ */ new Map();
|
|
5926
|
-
for (const [id, s] of raw) {
|
|
6820
|
+
for (const [id, s] of raw.signals) {
|
|
5927
6821
|
const m = relevant.find((x) => x.id === id);
|
|
5928
6822
|
if (m && isReliableMarketSignal(s, { now }) && marketSignalRendersFor(m, s)) out2.set(id, s);
|
|
5929
6823
|
}
|
|
5930
|
-
return out2;
|
|
6824
|
+
return { signals: out2, complete: raw.complete };
|
|
5931
6825
|
}
|
|
5932
6826
|
async function reliableMarketSignalFor(ctx, match) {
|
|
5933
|
-
|
|
6827
|
+
const result = await reliableMarketSignals(ctx, [match]);
|
|
6828
|
+
return { signal: result.signals.get(match.id), complete: result.complete };
|
|
5934
6829
|
}
|
|
5935
6830
|
function out(line2 = "") {
|
|
5936
6831
|
process.stdout.write(line2 + "\n");
|
|
@@ -5989,14 +6884,15 @@ async function cmdToday(date, ctx) {
|
|
|
5989
6884
|
const targetDate = date ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
|
|
5990
6885
|
const { matches, degraded, source } = await getMatchesForDate(adapter, targetDate);
|
|
5991
6886
|
const todays = fixturesByDate(targetDate, matches, cfg.tz);
|
|
5992
|
-
const
|
|
6887
|
+
const market = await reliableMarketSignals(ctx, todays);
|
|
5993
6888
|
if (cfg.json) {
|
|
5994
6889
|
emitJson({
|
|
5995
6890
|
date: targetDate,
|
|
5996
6891
|
degraded,
|
|
5997
6892
|
source: source ?? null,
|
|
5998
6893
|
matches: todays,
|
|
5999
|
-
|
|
6894
|
+
marketComplete: market.complete,
|
|
6895
|
+
marketSignals: Object.fromEntries(market.signals)
|
|
6000
6896
|
});
|
|
6001
6897
|
return;
|
|
6002
6898
|
}
|
|
@@ -6011,10 +6907,13 @@ async function cmdToday(date, ctx) {
|
|
|
6011
6907
|
} else {
|
|
6012
6908
|
for (const m of todays) {
|
|
6013
6909
|
out(matchLine(m, cfg, t2, c, flags));
|
|
6014
|
-
const s = signals.get(m.id);
|
|
6910
|
+
const s = market.signals.get(m.id);
|
|
6015
6911
|
if (s) out(" " + c.dim(marketLine(s, m)));
|
|
6016
6912
|
}
|
|
6017
6913
|
}
|
|
6914
|
+
if (!market.complete) {
|
|
6915
|
+
out(c.dim(" Market data unavailable or incomplete \u2014 not all fixtures were checked."));
|
|
6916
|
+
}
|
|
6018
6917
|
out();
|
|
6019
6918
|
if (degraded) out(c.dim(" " + t2("feed.degraded")));
|
|
6020
6919
|
const src = dataSource(source, cfg.lang, c);
|
|
@@ -6099,9 +6998,9 @@ function cmdTeam(query, ctx) {
|
|
|
6099
6998
|
const c = painterFor(cfg);
|
|
6100
6999
|
const flags = flagsEnabled();
|
|
6101
7000
|
const label = (tm) => {
|
|
6102
|
-
const
|
|
7001
|
+
const flag2 = flags ? `${tm.flag} ` : "";
|
|
6103
7002
|
const grp = tm.group ? ` \xB7 ${t2("team.group", { group: tm.group })}` : "";
|
|
6104
|
-
return ` ${
|
|
7003
|
+
return ` ${flag2}${c.bold(tm.name)} ${c.dim(tm.code + grp)}`;
|
|
6105
7004
|
};
|
|
6106
7005
|
out();
|
|
6107
7006
|
if (!q) {
|
|
@@ -6137,11 +7036,12 @@ async function cmdTable(group, ctx) {
|
|
|
6137
7036
|
out();
|
|
6138
7037
|
out(
|
|
6139
7038
|
c.dim(
|
|
6140
|
-
" " + (group ? t2("table.none", { group: group.toUpperCase() }) : t2("table.empty"))
|
|
7039
|
+
" " + (degraded ? t2("table.unavailable") : group ? t2("table.none", { group: group.toUpperCase() }) : t2("table.empty"))
|
|
6141
7040
|
)
|
|
6142
7041
|
);
|
|
6143
7042
|
out();
|
|
6144
|
-
|
|
7043
|
+
const src2 = dataSource(source, cfg.lang, c);
|
|
7044
|
+
if (src2) out(src2);
|
|
6145
7045
|
out(disclaimer(t2, c));
|
|
6146
7046
|
return;
|
|
6147
7047
|
}
|
|
@@ -6352,13 +7252,14 @@ async function cmdMatch(id, ctx) {
|
|
|
6352
7252
|
const { cfg, t: t2 } = ctx;
|
|
6353
7253
|
precheck(cfg, t2);
|
|
6354
7254
|
const { match, degraded, source: liveSource } = await getMatchById(adapterFor(ctx), id);
|
|
6355
|
-
const
|
|
7255
|
+
const market = match ? await reliableMarketSignalFor(ctx, match) : { signal: void 0, complete: true };
|
|
6356
7256
|
if (cfg.json) {
|
|
6357
7257
|
emitJson({
|
|
6358
7258
|
degraded,
|
|
6359
7259
|
match: match ?? null,
|
|
6360
7260
|
source: liveSource ?? null,
|
|
6361
|
-
|
|
7261
|
+
marketComplete: market.complete,
|
|
7262
|
+
marketSignal: market.signal ?? null
|
|
6362
7263
|
});
|
|
6363
7264
|
return;
|
|
6364
7265
|
}
|
|
@@ -6386,9 +7287,13 @@ async function cmdMatch(id, ctx) {
|
|
|
6386
7287
|
out(` ${e.minute}' ${e.type} ${e.teamCode}${e.player ? ` \u2014 ${e.player}` : ""}`);
|
|
6387
7288
|
}
|
|
6388
7289
|
}
|
|
6389
|
-
if (
|
|
7290
|
+
if (market.signal) {
|
|
7291
|
+
out();
|
|
7292
|
+
for (const mline of marketBlock(market.signal, match)) out(" " + c.dim(mline));
|
|
7293
|
+
}
|
|
7294
|
+
if (!market.complete) {
|
|
6390
7295
|
out();
|
|
6391
|
-
|
|
7296
|
+
out(c.dim(" Market data unavailable or incomplete \u2014 this match could not be checked."));
|
|
6392
7297
|
}
|
|
6393
7298
|
out();
|
|
6394
7299
|
if (degraded) out(c.dim(" " + t2("feed.degraded")));
|
|
@@ -6419,14 +7324,16 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6419
7324
|
const code = resolveTeamArg(team, "Usage: claudinho markets next <team> (or set CLAUDINHO_TEAM)", t2);
|
|
6420
7325
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
6421
7326
|
const { match: fixture, degraded } = await marketFixtureForTeam(adapterFor(ctx), code, now2);
|
|
6422
|
-
const
|
|
6423
|
-
const
|
|
7327
|
+
const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
7328
|
+
const sig = fixture ? market.signals.get(fixture.id) : void 0;
|
|
7329
|
+
const shown = market.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
6424
7330
|
if (cfg.json) {
|
|
6425
7331
|
emitJson({
|
|
6426
7332
|
team: code,
|
|
6427
7333
|
matchId: fixture?.id ?? null,
|
|
6428
7334
|
degraded,
|
|
6429
7335
|
informationalOnly: true,
|
|
7336
|
+
complete: market.complete,
|
|
6430
7337
|
signal: shown ?? null
|
|
6431
7338
|
});
|
|
6432
7339
|
return;
|
|
@@ -6439,7 +7346,9 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6439
7346
|
out(header(marketHeaderLine(fixture, cfg), c2));
|
|
6440
7347
|
out();
|
|
6441
7348
|
if (shown) printMarketBlock(fixture, shown, c2);
|
|
6442
|
-
else
|
|
7349
|
+
else if (!market.complete) {
|
|
7350
|
+
out(c2.dim(" Market data unavailable or incomplete \u2014 this match could not be checked."));
|
|
7351
|
+
} else out(c2.dim(" " + noSignalLine(fixture, now2)));
|
|
6443
7352
|
}
|
|
6444
7353
|
out();
|
|
6445
7354
|
out(disclaimer(t2, c2));
|
|
@@ -6450,10 +7359,16 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6450
7359
|
precheck(cfg, t2);
|
|
6451
7360
|
const now2 = ctx.now ?? /* @__PURE__ */ new Date();
|
|
6452
7361
|
const { match } = await getMatchById(adapterFor(ctx), target);
|
|
6453
|
-
const
|
|
6454
|
-
const
|
|
7362
|
+
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
7363
|
+
const sig = match ? market.signals.get(match.id) : void 0;
|
|
7364
|
+
const shown = market.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
6455
7365
|
if (cfg.json) {
|
|
6456
|
-
emitJson({
|
|
7366
|
+
emitJson({
|
|
7367
|
+
matchId: target,
|
|
7368
|
+
informationalOnly: true,
|
|
7369
|
+
complete: market.complete,
|
|
7370
|
+
signal: shown ?? null
|
|
7371
|
+
});
|
|
6457
7372
|
return;
|
|
6458
7373
|
}
|
|
6459
7374
|
const c2 = painterFor(cfg);
|
|
@@ -6464,7 +7379,9 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6464
7379
|
out(header(marketHeaderLine(match, cfg), c2));
|
|
6465
7380
|
out();
|
|
6466
7381
|
if (shown) printMarketBlock(match, shown, c2);
|
|
6467
|
-
else
|
|
7382
|
+
else if (!market.complete) {
|
|
7383
|
+
out(c2.dim(" Market data unavailable or incomplete \u2014 this match could not be checked."));
|
|
7384
|
+
} else out(c2.dim(" " + noSignalLine(match, now2)));
|
|
6468
7385
|
}
|
|
6469
7386
|
out();
|
|
6470
7387
|
out(disclaimer(t2, c2));
|
|
@@ -6478,14 +7395,14 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6478
7395
|
const { matches } = await getMatchesForDate(adapterFor(ctx), date);
|
|
6479
7396
|
const todays = fixturesByDate(date, matches, cfg.tz);
|
|
6480
7397
|
const relevant = todays.filter((m) => marketRelevant(m, now));
|
|
6481
|
-
const signals = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
7398
|
+
const { signals, complete } = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
6482
7399
|
const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
6483
7400
|
(r) => !!r.signal && marketDisplayable(r.match, r.signal)
|
|
6484
7401
|
);
|
|
6485
7402
|
if (cfg.json) {
|
|
6486
7403
|
const marketSignals = {};
|
|
6487
7404
|
for (const r of rows) marketSignals[r.match.id] = r.signal;
|
|
6488
|
-
emitJson({ date, informationalOnly: true, marketSignals });
|
|
7405
|
+
emitJson({ date, informationalOnly: true, complete, marketSignals });
|
|
6489
7406
|
return;
|
|
6490
7407
|
}
|
|
6491
7408
|
const c = painterFor(cfg);
|
|
@@ -6493,13 +7410,21 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
6493
7410
|
out(header(`Market signals \xB7 ${date}`, c));
|
|
6494
7411
|
out();
|
|
6495
7412
|
if (rows.length === 0) {
|
|
6496
|
-
out(
|
|
7413
|
+
out(
|
|
7414
|
+
c.dim(
|
|
7415
|
+
complete ? ` No market signals available for ${date}.` : ` Market data unavailable or incomplete for ${date} \u2014 not all fixtures could be checked.`
|
|
7416
|
+
)
|
|
7417
|
+
);
|
|
6497
7418
|
} else {
|
|
6498
7419
|
for (const { match, signal } of rows) {
|
|
6499
7420
|
out(" " + c.bold(marketHeaderLine(match, cfg)));
|
|
6500
7421
|
printMarketBlock(match, signal, c);
|
|
6501
7422
|
out();
|
|
6502
7423
|
}
|
|
7424
|
+
if (!complete) {
|
|
7425
|
+
out(c.dim(` Market data unavailable or incomplete for ${date} \u2014 not all fixtures could be checked.`));
|
|
7426
|
+
out();
|
|
7427
|
+
}
|
|
6503
7428
|
}
|
|
6504
7429
|
out(disclaimer(t2, c));
|
|
6505
7430
|
out(c.dim(MARKET_INFO));
|
|
@@ -6510,11 +7435,11 @@ function pickShareStyle(v) {
|
|
|
6510
7435
|
async function reliableShareSignals(ctx, matches) {
|
|
6511
7436
|
const raw = await reliableMarketSignals(ctx, matches);
|
|
6512
7437
|
const out2 = /* @__PURE__ */ new Map();
|
|
6513
|
-
for (const [id, s] of raw) {
|
|
7438
|
+
for (const [id, s] of raw.signals) {
|
|
6514
7439
|
const m = matches.find((x) => x.id === id);
|
|
6515
7440
|
if (m && marketDisplayable(m, s)) out2.set(id, s);
|
|
6516
7441
|
}
|
|
6517
|
-
return out2;
|
|
7442
|
+
return { signals: out2, complete: raw.complete };
|
|
6518
7443
|
}
|
|
6519
7444
|
function emitShare(ctx, e, copy) {
|
|
6520
7445
|
const snippet = formatShareSnippet(e.input, e.options);
|
|
@@ -6529,6 +7454,7 @@ function emitShare(ctx, e, copy) {
|
|
|
6529
7454
|
style: e.options.style ?? "social",
|
|
6530
7455
|
snippet,
|
|
6531
7456
|
matches: e.input.matches,
|
|
7457
|
+
marketComplete: e.input.marketComplete ?? true,
|
|
6532
7458
|
marketSignals: Object.fromEntries(e.input.marketSignals ?? /* @__PURE__ */ new Map())
|
|
6533
7459
|
});
|
|
6534
7460
|
} else {
|
|
@@ -6648,11 +7574,12 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6648
7574
|
{
|
|
6649
7575
|
group,
|
|
6650
7576
|
tables,
|
|
6651
|
-
// Degraded ⇒
|
|
7577
|
+
// Degraded ⇒ no live provider: no attribution. Open-scope outages
|
|
7578
|
+
// have no compatible bundled roster, so name the outage in the empty card.
|
|
6652
7579
|
source: degraded2 ? void 0 : source2,
|
|
6653
7580
|
degraded: degraded2,
|
|
6654
7581
|
installLine: group ? `npx @claudinho/cli table ${group}` : "npx @claudinho/cli table",
|
|
6655
|
-
emptyNote: group ? `No group ${group}.` : "No standings available.",
|
|
7582
|
+
emptyNote: degraded2 ? "Live standings unavailable." : group ? `No group ${group}.` : "No standings available.",
|
|
6656
7583
|
options: baseOptions
|
|
6657
7584
|
},
|
|
6658
7585
|
copy
|
|
@@ -6699,7 +7626,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6699
7626
|
ctx.now ?? /* @__PURE__ */ new Date()
|
|
6700
7627
|
);
|
|
6701
7628
|
const matches = fixture ? [fixture] : [];
|
|
6702
|
-
const
|
|
7629
|
+
const market2 = await reliableShareSignals(ctx, matches);
|
|
6703
7630
|
const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
|
|
6704
7631
|
emitShare(
|
|
6705
7632
|
ctx,
|
|
@@ -6710,7 +7637,8 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6710
7637
|
input: {
|
|
6711
7638
|
title: `Next up for ${teamName}`,
|
|
6712
7639
|
matches,
|
|
6713
|
-
marketSignals:
|
|
7640
|
+
marketSignals: market2.signals,
|
|
7641
|
+
marketComplete: market2.complete,
|
|
6714
7642
|
// Attribute the provider when the overlay resolved the tie (knockout);
|
|
6715
7643
|
// undefined for a static group fixture — parity with CLI `next`.
|
|
6716
7644
|
source: source2,
|
|
@@ -6731,7 +7659,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6731
7659
|
precheck(cfg, t2);
|
|
6732
7660
|
const { match, degraded: degraded2, source: source2 } = await getMatchById(adapterFor(ctx), target);
|
|
6733
7661
|
const matches = match ? [match] : [];
|
|
6734
|
-
const
|
|
7662
|
+
const market2 = await reliableShareSignals(ctx, matches);
|
|
6735
7663
|
emitShare(
|
|
6736
7664
|
ctx,
|
|
6737
7665
|
{
|
|
@@ -6740,7 +7668,8 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6740
7668
|
input: {
|
|
6741
7669
|
title: "Match pulse",
|
|
6742
7670
|
matches,
|
|
6743
|
-
marketSignals:
|
|
7671
|
+
marketSignals: market2.signals,
|
|
7672
|
+
marketComplete: market2.complete,
|
|
6744
7673
|
source: source2,
|
|
6745
7674
|
degraded: degraded2,
|
|
6746
7675
|
emptyNote: `No match found with id ${target}.`,
|
|
@@ -6759,7 +7688,7 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6759
7688
|
const date = explicitDate ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
|
|
6760
7689
|
const { matches: all, degraded, source } = await getMatchesForDate(adapterFor(ctx), date);
|
|
6761
7690
|
const todays = fixturesByDate(date, all, cfg.tz);
|
|
6762
|
-
const
|
|
7691
|
+
const market = await reliableShareSignals(ctx, todays);
|
|
6763
7692
|
const human = formatDate(`${date}T12:00:00.000Z`, { tz: cfg.tz, locale: cfg.lang });
|
|
6764
7693
|
const title = explicitDate ? `Matches \xB7 ${human}` : `Today's matches \xB7 ${human}`;
|
|
6765
7694
|
emitShare(
|
|
@@ -6770,7 +7699,8 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
6770
7699
|
input: {
|
|
6771
7700
|
title,
|
|
6772
7701
|
matches: todays,
|
|
6773
|
-
marketSignals: signals,
|
|
7702
|
+
marketSignals: market.signals,
|
|
7703
|
+
marketComplete: market.complete,
|
|
6774
7704
|
source,
|
|
6775
7705
|
degraded,
|
|
6776
7706
|
emptyNote: `No matches scheduled for ${human}.`,
|
|
@@ -6873,7 +7803,7 @@ function cmdVibe(ctx) {
|
|
|
6873
7803
|
try {
|
|
6874
7804
|
const state = readCurrentState(cfg.source, resolveCompetition());
|
|
6875
7805
|
liveSeg = vibeLiveSegment(
|
|
6876
|
-
liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()),
|
|
7806
|
+
liveMatchesFromCache(state, (ctx.now ?? /* @__PURE__ */ new Date()).getTime()).items,
|
|
6877
7807
|
// Name-or-code, matching the statusline/hook (offline lookup).
|
|
6878
7808
|
resolveEnvTeam(process.env.CLAUDINHO_TEAM)
|
|
6879
7809
|
);
|
|
@@ -6899,7 +7829,7 @@ function handlePipeError(stream) {
|
|
|
6899
7829
|
}
|
|
6900
7830
|
handlePipeError(process.stdout);
|
|
6901
7831
|
handlePipeError(process.stderr);
|
|
6902
|
-
var VERSION = "0.9.
|
|
7832
|
+
var VERSION = "0.9.4";
|
|
6903
7833
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
6904
7834
|
function ctxFrom(cmd) {
|
|
6905
7835
|
let root = cmd;
|