@claudinho/core 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +550 -181
- package/dist/index.js +300 -20
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -332,6 +332,13 @@ var EN = {
|
|
|
332
332
|
"status.fullTime": "full-time",
|
|
333
333
|
"status.postponed": "postponed",
|
|
334
334
|
"status.cancelled": "cancelled",
|
|
335
|
+
// A timeline row's kind word (core `timelineRows`).
|
|
336
|
+
"event.goal": "Goal",
|
|
337
|
+
"event.ownGoal": "Own goal",
|
|
338
|
+
"event.penalty": "Penalty",
|
|
339
|
+
"event.yellow": "Yellow card",
|
|
340
|
+
"event.red": "Red card",
|
|
341
|
+
"event.sub": "Substitution",
|
|
335
342
|
"live.title": "Live now",
|
|
336
343
|
"today.title": "Today's matches",
|
|
337
344
|
"today.on": "Matches",
|
|
@@ -340,6 +347,23 @@ var EN = {
|
|
|
340
347
|
"next.in": "in {countdown}",
|
|
341
348
|
// A kickoff that has passed, where the countdown would say "in" (core `countdownPhrase`).
|
|
342
349
|
"countdown.now": "now",
|
|
350
|
+
// The ambient line's syncing phrase (`claudinho ambient --json` alone; the
|
|
351
|
+
// statusline's own `prompt` stays English).
|
|
352
|
+
"ambient.syncing": "live \xB7 syncing\u2026",
|
|
353
|
+
// The plugin's moment words, sent by `claudinho ambient --json` as `words`
|
|
354
|
+
// (`{team}` a template the plugin fills with the record's own names). Each
|
|
355
|
+
// at most 40 characters; `{team}` once in `moment.goalFor` and `moment.win`
|
|
356
|
+
// and nowhere else, or the plugin falls back to its English words.
|
|
357
|
+
"moment.goal": "GOAL!",
|
|
358
|
+
"moment.ownGoal": "Own goal",
|
|
359
|
+
"moment.ownGoalDetail": "own goal",
|
|
360
|
+
"moment.goalFor": "Goal for {team}",
|
|
361
|
+
"moment.goals": "Goals",
|
|
362
|
+
"moment.scoreUpdated": "Score updated",
|
|
363
|
+
"moment.penalties": "Penalties",
|
|
364
|
+
"moment.fullTime": "Full time",
|
|
365
|
+
"moment.win": "{team} win",
|
|
366
|
+
"moment.draw": "Draw",
|
|
343
367
|
// A title that introduces the lines below it (the MCP text).
|
|
344
368
|
heading: "{title}:",
|
|
345
369
|
// A list's empty state and the outage lines: the CLI's sentences, the MCP
|
|
@@ -351,6 +375,7 @@ var EN = {
|
|
|
351
375
|
"feed.earlierRecord": "Live state could not be refreshed \u2014 showing the provider's earlier record.",
|
|
352
376
|
"next.none": "No upcoming fixture found for {team}.",
|
|
353
377
|
"match.none": "No match found with id {id}.",
|
|
378
|
+
"match.kickoff": "Kickoff",
|
|
354
379
|
// The MCP text's own sentences for these states (the CLI says `live.degraded`
|
|
355
380
|
// there): they keep the team or the id the answer is about.
|
|
356
381
|
"next.unreachable": "Couldn't reach the data provider \u2014 no upcoming fixture confirmed for {team}.",
|
|
@@ -428,6 +453,12 @@ var ES = {
|
|
|
428
453
|
"status.fullTime": "finalizado",
|
|
429
454
|
"status.postponed": "aplazado",
|
|
430
455
|
"status.cancelled": "cancelado",
|
|
456
|
+
"event.goal": "Gol",
|
|
457
|
+
"event.ownGoal": "Gol en contra",
|
|
458
|
+
"event.penalty": "Penal",
|
|
459
|
+
"event.yellow": "Tarjeta amarilla",
|
|
460
|
+
"event.red": "Tarjeta roja",
|
|
461
|
+
"event.sub": "Cambio",
|
|
431
462
|
"live.title": "En vivo",
|
|
432
463
|
"today.title": "Partidos de hoy",
|
|
433
464
|
"today.on": "Partidos",
|
|
@@ -435,6 +466,17 @@ var ES = {
|
|
|
435
466
|
"next.label": "Pr\xF3ximo partido de {team}",
|
|
436
467
|
"next.in": "en {countdown}",
|
|
437
468
|
"countdown.now": "ahora",
|
|
469
|
+
"ambient.syncing": "en vivo \xB7 sincronizando\u2026",
|
|
470
|
+
"moment.goal": "\xA1GOL!",
|
|
471
|
+
"moment.ownGoal": "Gol en contra",
|
|
472
|
+
"moment.ownGoalDetail": "gol en contra",
|
|
473
|
+
"moment.goalFor": "Gol de {team}",
|
|
474
|
+
"moment.goals": "Goles",
|
|
475
|
+
"moment.scoreUpdated": "Marcador corregido",
|
|
476
|
+
"moment.penalties": "Penales",
|
|
477
|
+
"moment.fullTime": "Final",
|
|
478
|
+
"moment.win": "Gana {team}",
|
|
479
|
+
"moment.draw": "Empate",
|
|
438
480
|
heading: "{title}:",
|
|
439
481
|
"today.none": "No hay partidos para esta fecha.",
|
|
440
482
|
"live.none": "No hay partidos en juego ahora mismo.",
|
|
@@ -443,6 +485,7 @@ var ES = {
|
|
|
443
485
|
"feed.earlierRecord": "No se pudo actualizar el estado en vivo \u2014 se muestra el registro anterior del proveedor.",
|
|
444
486
|
"next.none": "No se encontr\xF3 pr\xF3ximo partido para {team}.",
|
|
445
487
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
488
|
+
"match.kickoff": "Inicio",
|
|
446
489
|
"next.unreachable": "No se pudo contactar al proveedor de datos \u2014 ning\xFAn pr\xF3ximo partido confirmado para {team}.",
|
|
447
490
|
"match.unreachable": "No se pudo contactar al proveedor de datos \u2014 no se pudo consultar el partido {id}.",
|
|
448
491
|
"team.ambiguous": '"{query}" es ambiguo. \xBFQuisiste decir:',
|
|
@@ -517,6 +560,12 @@ var PT = {
|
|
|
517
560
|
"status.fullTime": "encerrado",
|
|
518
561
|
"status.postponed": "adiado",
|
|
519
562
|
"status.cancelled": "cancelado",
|
|
563
|
+
"event.goal": "Gol",
|
|
564
|
+
"event.ownGoal": "Gol contra",
|
|
565
|
+
"event.penalty": "P\xEAnalti",
|
|
566
|
+
"event.yellow": "Cart\xE3o amarelo",
|
|
567
|
+
"event.red": "Cart\xE3o vermelho",
|
|
568
|
+
"event.sub": "Substitui\xE7\xE3o",
|
|
520
569
|
"live.title": "Ao vivo",
|
|
521
570
|
"today.title": "Jogos de hoje",
|
|
522
571
|
"today.on": "Jogos",
|
|
@@ -524,6 +573,17 @@ var PT = {
|
|
|
524
573
|
"next.label": "Pr\xF3ximo jogo de {team}",
|
|
525
574
|
"next.in": "em {countdown}",
|
|
526
575
|
"countdown.now": "agora",
|
|
576
|
+
"ambient.syncing": "ao vivo \xB7 sincronizando\u2026",
|
|
577
|
+
"moment.goal": "GOL!",
|
|
578
|
+
"moment.ownGoal": "Gol contra",
|
|
579
|
+
"moment.ownGoalDetail": "gol contra",
|
|
580
|
+
"moment.goalFor": "Gol \xB7 {team}",
|
|
581
|
+
"moment.goals": "Gols",
|
|
582
|
+
"moment.scoreUpdated": "Placar corrigido",
|
|
583
|
+
"moment.penalties": "P\xEAnaltis",
|
|
584
|
+
"moment.fullTime": "Fim de jogo",
|
|
585
|
+
"moment.win": "{team} vence",
|
|
586
|
+
"moment.draw": "Empate",
|
|
527
587
|
heading: "{title}:",
|
|
528
588
|
"today.none": "Nenhum jogo para esta data.",
|
|
529
589
|
"live.none": "Nenhum jogo em andamento agora.",
|
|
@@ -532,6 +592,7 @@ var PT = {
|
|
|
532
592
|
"feed.earlierRecord": "N\xE3o foi poss\xEDvel atualizar o estado ao vivo \u2014 mostrando o registro anterior do provedor.",
|
|
533
593
|
"next.none": "Nenhum pr\xF3ximo jogo encontrado para {team}.",
|
|
534
594
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
595
|
+
"match.kickoff": "In\xEDcio",
|
|
535
596
|
"next.unreachable": "N\xE3o foi poss\xEDvel contatar o provedor de dados \u2014 nenhum pr\xF3ximo jogo confirmado para {team}.",
|
|
536
597
|
"match.unreachable": "N\xE3o foi poss\xEDvel contatar o provedor de dados \u2014 o jogo {id} n\xE3o p\xF4de ser consultado.",
|
|
537
598
|
"team.ambiguous": '"{query}" \xE9 amb\xEDguo. Voc\xEA quis dizer:',
|
|
@@ -606,6 +667,12 @@ var FR = {
|
|
|
606
667
|
"status.fullTime": "termin\xE9",
|
|
607
668
|
"status.postponed": "report\xE9",
|
|
608
669
|
"status.cancelled": "annul\xE9",
|
|
670
|
+
"event.goal": "But",
|
|
671
|
+
"event.ownGoal": "But contre son camp",
|
|
672
|
+
"event.penalty": "Penalty",
|
|
673
|
+
"event.yellow": "Carton jaune",
|
|
674
|
+
"event.red": "Carton rouge",
|
|
675
|
+
"event.sub": "Remplacement",
|
|
609
676
|
"live.title": "En direct",
|
|
610
677
|
"today.title": "Matchs d'aujourd'hui",
|
|
611
678
|
"today.on": "Matchs",
|
|
@@ -613,6 +680,17 @@ var FR = {
|
|
|
613
680
|
"next.label": "Prochain match de {team}",
|
|
614
681
|
"next.in": "dans {countdown}",
|
|
615
682
|
"countdown.now": "maintenant",
|
|
683
|
+
"ambient.syncing": "en direct \xB7 synchronisation\u2026",
|
|
684
|
+
"moment.goal": "BUT !",
|
|
685
|
+
"moment.ownGoal": "But contre son camp",
|
|
686
|
+
"moment.ownGoalDetail": "contre son camp",
|
|
687
|
+
"moment.goalFor": "But pour {team}",
|
|
688
|
+
"moment.goals": "Buts",
|
|
689
|
+
"moment.scoreUpdated": "Score corrig\xE9",
|
|
690
|
+
"moment.penalties": "Tirs au but",
|
|
691
|
+
"moment.fullTime": "Fin du match",
|
|
692
|
+
"moment.win": "{team} gagne",
|
|
693
|
+
"moment.draw": "Match nul",
|
|
616
694
|
heading: "{title} :",
|
|
617
695
|
"today.none": "Aucun match pr\xE9vu pour cette date.",
|
|
618
696
|
"live.none": "Aucun match en cours pour l'instant.",
|
|
@@ -621,6 +699,7 @@ var FR = {
|
|
|
621
699
|
"feed.earlierRecord": "L\u2019\xE9tat en direct n\u2019a pas pu \xEAtre actualis\xE9 \u2014 affichage de l\u2019enregistrement pr\xE9c\xE9dent du fournisseur.",
|
|
622
700
|
"next.none": "Aucun prochain match trouv\xE9 pour {team}.",
|
|
623
701
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
702
|
+
"match.kickoff": "Coup d\u2019envoi",
|
|
624
703
|
"next.unreachable": "Impossible de joindre le fournisseur de donn\xE9es \u2014 aucun prochain match confirm\xE9 pour {team}.",
|
|
625
704
|
"match.unreachable": "Impossible de joindre le fournisseur de donn\xE9es \u2014 le match {id} n\u2019a pas pu \xEAtre consult\xE9.",
|
|
626
705
|
"team.ambiguous": '"{query}" est ambigu. Vouliez-vous dire :',
|
|
@@ -654,6 +733,20 @@ function t(lang, key, vars) {
|
|
|
654
733
|
(whole, name) => Object.hasOwn(vars, name) ? vars[name] : whole
|
|
655
734
|
);
|
|
656
735
|
}
|
|
736
|
+
function momentWords(lang) {
|
|
737
|
+
return {
|
|
738
|
+
goal: t(lang, "moment.goal"),
|
|
739
|
+
ownGoal: t(lang, "moment.ownGoal"),
|
|
740
|
+
ownGoalDetail: t(lang, "moment.ownGoalDetail"),
|
|
741
|
+
goalFor: t(lang, "moment.goalFor"),
|
|
742
|
+
goals: t(lang, "moment.goals"),
|
|
743
|
+
scoreUpdated: t(lang, "moment.scoreUpdated"),
|
|
744
|
+
penalties: t(lang, "moment.penalties"),
|
|
745
|
+
fullTime: t(lang, "moment.fullTime"),
|
|
746
|
+
win: t(lang, "moment.win"),
|
|
747
|
+
draw: t(lang, "moment.draw")
|
|
748
|
+
};
|
|
749
|
+
}
|
|
657
750
|
var STAGE_KEYS = Object.freeze({
|
|
658
751
|
GROUP: "stage.groupStage",
|
|
659
752
|
R32: "stage.r32",
|
|
@@ -763,7 +856,7 @@ function formatTime(iso, opts = {}) {
|
|
|
763
856
|
}).format(when);
|
|
764
857
|
}
|
|
765
858
|
var COUNTDOWN_NOW = "now";
|
|
766
|
-
function countdown(iso, from
|
|
859
|
+
function countdown(iso, from) {
|
|
767
860
|
const when = parsedDate(iso);
|
|
768
861
|
if (!when) return UNKNOWN_TIME;
|
|
769
862
|
const ms = when.getTime() - from.getTime();
|
|
@@ -776,7 +869,7 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
|
776
869
|
if (hours > 0) return `${hours}h${mins}m`;
|
|
777
870
|
return `${mins}m`;
|
|
778
871
|
}
|
|
779
|
-
function countdownPhrase(lang, iso, from
|
|
872
|
+
function countdownPhrase(lang, iso, from) {
|
|
780
873
|
const left = countdown(iso, from);
|
|
781
874
|
return left === COUNTDOWN_NOW ? t(lang, "countdown.now") : t(lang, "next.in", { countdown: left });
|
|
782
875
|
}
|
|
@@ -842,6 +935,70 @@ function withFlag(text, flag2, side) {
|
|
|
842
935
|
return side === "home" ? `${flag2} ${text}` : `${text} ${flag2}`;
|
|
843
936
|
}
|
|
844
937
|
|
|
938
|
+
// src/timeline.ts
|
|
939
|
+
var KIND_KEYS = Object.freeze({
|
|
940
|
+
GOAL: "event.goal",
|
|
941
|
+
OWN_GOAL: "event.ownGoal",
|
|
942
|
+
PEN: "event.penalty",
|
|
943
|
+
YELLOW: "event.yellow",
|
|
944
|
+
RED: "event.red",
|
|
945
|
+
SUB: "event.sub"
|
|
946
|
+
});
|
|
947
|
+
var KIND_MARKS = Object.freeze({
|
|
948
|
+
GOAL: "\u26BD",
|
|
949
|
+
OWN_GOAL: "\u26BD",
|
|
950
|
+
PEN: "\u26BD",
|
|
951
|
+
YELLOW: "\u{1F7E8}",
|
|
952
|
+
RED: "\u{1F7E5}",
|
|
953
|
+
SUB: "\u{1F504}"
|
|
954
|
+
});
|
|
955
|
+
function timelineParts(match, opts) {
|
|
956
|
+
const events = match.events ?? [];
|
|
957
|
+
if (events.length === 0) return [];
|
|
958
|
+
const cells = events.map((e) => {
|
|
959
|
+
const team = e.side === "home" ? match.home : match.away;
|
|
960
|
+
return {
|
|
961
|
+
minute: e.added !== void 0 ? `${e.minute}+${e.added}'` : `${e.minute}'`,
|
|
962
|
+
mark: KIND_MARKS[e.type],
|
|
963
|
+
kind: t(opts.lang, KIND_KEYS[e.type]),
|
|
964
|
+
label: withFlag(team.code, opts.flags ? team.flag : void 0, e.side),
|
|
965
|
+
player: e.player
|
|
966
|
+
};
|
|
967
|
+
});
|
|
968
|
+
const widest = (pick2) => Math.max(...cells.map((c) => displayWidth(pick2(c))));
|
|
969
|
+
const minuteW = widest((c) => c.minute);
|
|
970
|
+
const kindW = widest((c) => c.kind);
|
|
971
|
+
const labelW = widest((c) => c.label);
|
|
972
|
+
return cells.map((c) => {
|
|
973
|
+
const columns = ` ${padVisible(c.minute, minuteW)} ${c.mark} ${padVisible(c.kind, kindW)} ${padVisible(c.label, labelW)}`;
|
|
974
|
+
if (!c.player) return [{ text: columns.trimEnd() }];
|
|
975
|
+
return [{ text: `${columns} ` }, { text: c.player, style: "strong" }];
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function timelineRows(match, opts) {
|
|
979
|
+
return timelineParts(match, opts).map((row) => row.map((p) => p.text).join(""));
|
|
980
|
+
}
|
|
981
|
+
var SCORING = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN"]);
|
|
982
|
+
function provenLastGoal(match) {
|
|
983
|
+
const score = match.score;
|
|
984
|
+
if (!score) return null;
|
|
985
|
+
let home = 0;
|
|
986
|
+
let away = 0;
|
|
987
|
+
let last = null;
|
|
988
|
+
const identities = /* @__PURE__ */ new Set();
|
|
989
|
+
for (const e of match.events ?? []) {
|
|
990
|
+
if (!SCORING.has(e.type)) continue;
|
|
991
|
+
const identity = JSON.stringify([e.type, e.minute, e.added ?? 0, e.side, e.player ?? null]);
|
|
992
|
+
if (identities.has(identity)) return null;
|
|
993
|
+
identities.add(identity);
|
|
994
|
+
if (e.side === "home") home++;
|
|
995
|
+
else if (e.side === "away") away++;
|
|
996
|
+
else continue;
|
|
997
|
+
last = e;
|
|
998
|
+
}
|
|
999
|
+
return last !== null && home === score.home && away === score.away ? last : null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
845
1002
|
// src/normalize.ts
|
|
846
1003
|
function outcomeFromScore(home, away) {
|
|
847
1004
|
if (home > away) return "H";
|
|
@@ -1132,18 +1289,33 @@ function sealScorePair(raw) {
|
|
|
1132
1289
|
const away = count(v.away, MAX_GOALS);
|
|
1133
1290
|
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
1134
1291
|
}
|
|
1292
|
+
var MAX_ADDED_MINUTES = 30;
|
|
1293
|
+
var SIDES = /* @__PURE__ */ new Set(["home", "away"]);
|
|
1135
1294
|
function sealEvent(raw) {
|
|
1136
|
-
if (!raw || typeof raw !== "object") return
|
|
1295
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1137
1296
|
const e = raw;
|
|
1138
1297
|
const type = member(e.type, EVENT_TYPES);
|
|
1139
|
-
if (!type) return
|
|
1298
|
+
if (!type) return null;
|
|
1140
1299
|
const minute = count(e.minute, MAX_MINUTE);
|
|
1141
|
-
if (minute === void 0) return
|
|
1142
|
-
|
|
1300
|
+
if (minute === void 0) return null;
|
|
1301
|
+
let added;
|
|
1302
|
+
if (e.added !== void 0) {
|
|
1303
|
+
added = count(e.added, MAX_ADDED_MINUTES);
|
|
1304
|
+
if (added === void 0 || added < 1) return null;
|
|
1305
|
+
}
|
|
1306
|
+
const side = member(e.side, SIDES);
|
|
1307
|
+
if (!side) return null;
|
|
1308
|
+
const out = { type, minute };
|
|
1309
|
+
if (added !== void 0) out.added = added;
|
|
1310
|
+
out.side = side;
|
|
1311
|
+
out.teamCode = humanLabel(e.teamCode, TEAM_CODE_COLUMNS);
|
|
1143
1312
|
const player = humanLabel(e.player);
|
|
1144
1313
|
if (player) out.player = player;
|
|
1145
1314
|
return out;
|
|
1146
1315
|
}
|
|
1316
|
+
function byClock(a, b) {
|
|
1317
|
+
return a.minute - b.minute || (a.added ?? 0) - (b.added ?? 0);
|
|
1318
|
+
}
|
|
1147
1319
|
function sealMatch(parts, opts = {}) {
|
|
1148
1320
|
if (!parts || typeof parts !== "object") return malformed("match is not an object");
|
|
1149
1321
|
const id = opaqueId(parts.id, ESPN_ID);
|
|
@@ -1188,7 +1360,8 @@ function sealMatch(parts, opts = {}) {
|
|
|
1188
1360
|
winnerCode = claimedWinner;
|
|
1189
1361
|
}
|
|
1190
1362
|
}
|
|
1191
|
-
const
|
|
1363
|
+
const timelined = status === "LIVE" || status === "HT" || status === "FT";
|
|
1364
|
+
const events = opts.events === false || !timelined ? [] : takeBounded(parts.events, MAX_MATCH_EVENTS).map(sealEvent).filter((e) => !!e).sort(byClock);
|
|
1192
1365
|
const out = { id, stage };
|
|
1193
1366
|
if (stageLabel2) out.stageLabel = stageLabel2;
|
|
1194
1367
|
if (group) out.group = group;
|
|
@@ -4446,6 +4619,75 @@ function parseMinute(st) {
|
|
|
4446
4619
|
}
|
|
4447
4620
|
return void 0;
|
|
4448
4621
|
}
|
|
4622
|
+
var DETAIL_FLAGS = ["scoringPlay", "yellowCard", "redCard", "penaltyKick", "ownGoal", "shootout"];
|
|
4623
|
+
var DETAIL_MINUTE = /^(\d{1,3})'$/;
|
|
4624
|
+
var DETAIL_MINUTE_ADDED = /^(\d{1,3})'\+(\d{1,2})'$/;
|
|
4625
|
+
function detailKind(d) {
|
|
4626
|
+
const f = {};
|
|
4627
|
+
for (const name of DETAIL_FLAGS) {
|
|
4628
|
+
const v = flag(d[name]);
|
|
4629
|
+
if (v === void 0) return void 0;
|
|
4630
|
+
f[name] = v;
|
|
4631
|
+
}
|
|
4632
|
+
if (f.shootout) return void 0;
|
|
4633
|
+
const { scoringPlay: s, yellowCard: y, redCard: r, penaltyKick: p, ownGoal: o } = f;
|
|
4634
|
+
if (s && !y && !r) {
|
|
4635
|
+
if (!p && !o) return "GOAL";
|
|
4636
|
+
if (o && !p) return "OWN_GOAL";
|
|
4637
|
+
if (p && !o) return "PEN";
|
|
4638
|
+
return void 0;
|
|
4639
|
+
}
|
|
4640
|
+
if (p || o || s) return void 0;
|
|
4641
|
+
if (y && !r) return "YELLOW";
|
|
4642
|
+
if (r && !y) return "RED";
|
|
4643
|
+
return void 0;
|
|
4644
|
+
}
|
|
4645
|
+
function detailMinute(clock) {
|
|
4646
|
+
if (!clock || typeof clock !== "object") return void 0;
|
|
4647
|
+
const dv = clock.displayValue;
|
|
4648
|
+
if (typeof dv !== "string") return void 0;
|
|
4649
|
+
const plain = DETAIL_MINUTE.exec(dv);
|
|
4650
|
+
if (plain) return { minute: Number(plain[1]) };
|
|
4651
|
+
const extra = DETAIL_MINUTE_ADDED.exec(dv);
|
|
4652
|
+
if (!extra) return void 0;
|
|
4653
|
+
return { minute: Number(extra[1]), added: Number(extra[2]) };
|
|
4654
|
+
}
|
|
4655
|
+
function rawTeamId(holder) {
|
|
4656
|
+
if (!holder || typeof holder !== "object") return void 0;
|
|
4657
|
+
const team = holder.team;
|
|
4658
|
+
if (!team || typeof team !== "object") return void 0;
|
|
4659
|
+
return opaqueId(team.id, ESPN_ID);
|
|
4660
|
+
}
|
|
4661
|
+
function detailEvents(details, sides) {
|
|
4662
|
+
const out = [];
|
|
4663
|
+
for (const raw of takeBounded(details, MAX_MATCH_EVENTS)) {
|
|
4664
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
4665
|
+
const d = raw;
|
|
4666
|
+
const type = detailKind(d);
|
|
4667
|
+
if (!type) continue;
|
|
4668
|
+
const when = detailMinute(d.clock);
|
|
4669
|
+
if (!when) continue;
|
|
4670
|
+
const teamId = rawTeamId(d);
|
|
4671
|
+
const side = teamId === void 0 ? void 0 : teamId === sides.homeId ? "home" : teamId === sides.awayId ? "away" : void 0;
|
|
4672
|
+
if (!side) continue;
|
|
4673
|
+
const event = { type, minute: when.minute };
|
|
4674
|
+
if (when.added !== void 0) event.added = when.added;
|
|
4675
|
+
event.side = side;
|
|
4676
|
+
event.teamCode = side === "home" ? sides.homeCode : sides.awayCode;
|
|
4677
|
+
const player = detailPlayer(d.athletesInvolved, type === "OWN_GOAL" ? side === "home" ? sides.awayId : sides.homeId : teamId);
|
|
4678
|
+
if (player) event.player = player;
|
|
4679
|
+
out.push(event);
|
|
4680
|
+
}
|
|
4681
|
+
return out;
|
|
4682
|
+
}
|
|
4683
|
+
function detailPlayer(athletes, expectedTeamId) {
|
|
4684
|
+
if (!Array.isArray(athletes) || athletes.length !== 1 || expectedTeamId === void 0) return void 0;
|
|
4685
|
+
const a = athletes[0];
|
|
4686
|
+
if (rawTeamId(a) !== expectedTeamId) return void 0;
|
|
4687
|
+
const name = a.displayName;
|
|
4688
|
+
if (!isHumanLabel(name)) return void 0;
|
|
4689
|
+
return humanLabel(name) || void 0;
|
|
4690
|
+
}
|
|
4449
4691
|
var MAX_SLUG_UNITS2 = 64;
|
|
4450
4692
|
var SLUG = /^[a-z0-9-]+$/;
|
|
4451
4693
|
var EXACT_STAGE = Object.freeze({
|
|
@@ -4567,7 +4809,14 @@ function parseEspnEvent(raw, ctx = {}) {
|
|
|
4567
4809
|
status,
|
|
4568
4810
|
winnerCode,
|
|
4569
4811
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4570
|
-
|
|
4812
|
+
// The goals and cards: ESPN's `details`, read by the detail grammar; the
|
|
4813
|
+
// seal keeps them only on a status that holds a score.
|
|
4814
|
+
events: detailEvents(comp?.details, {
|
|
4815
|
+
homeId: rawTeamId(homeRaw),
|
|
4816
|
+
awayId: rawTeamId(awayRaw),
|
|
4817
|
+
homeCode: home.code,
|
|
4818
|
+
awayCode: away.code
|
|
4819
|
+
})
|
|
4571
4820
|
},
|
|
4572
4821
|
// The final reseal states the same kind the competitors were built with.
|
|
4573
4822
|
{ teamKind: kind }
|
|
@@ -4890,7 +5139,7 @@ function fetchMeta(result) {
|
|
|
4890
5139
|
// src/adapters/espn.ts
|
|
4891
5140
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
4892
5141
|
var DEFAULT_COMPETITION = BUNDLED_SLUG;
|
|
4893
|
-
var USER_AGENT = `claudinho/${"0.
|
|
5142
|
+
var USER_AGENT = `claudinho/${"0.12.0"} (+https://github.com/arturogarrido/claudinho)`;
|
|
4894
5143
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
4895
5144
|
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
4896
5145
|
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
@@ -6606,6 +6855,9 @@ function mergeLive(base, live) {
|
|
|
6606
6855
|
function clockOf(adapter) {
|
|
6607
6856
|
return new Date(adapter.now?.() ?? Date.now());
|
|
6608
6857
|
}
|
|
6858
|
+
function requestClock(adapter, given) {
|
|
6859
|
+
return given ?? clockOf(adapter);
|
|
6860
|
+
}
|
|
6609
6861
|
function providerDayOf(adapter, instant) {
|
|
6610
6862
|
if (Number.isNaN(instant.getTime())) return "";
|
|
6611
6863
|
return adapter.bucketDay?.(instant) || instant.toISOString().slice(0, 10);
|
|
@@ -6780,7 +7032,7 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
6780
7032
|
});
|
|
6781
7033
|
let refreshRead = {};
|
|
6782
7034
|
if (candidate) {
|
|
6783
|
-
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id);
|
|
7035
|
+
const { partial, served: _served, ...r } = await getMatchById(adapter, candidate.id, now);
|
|
6784
7036
|
refreshRead = partial ? { partial } : {};
|
|
6785
7037
|
const m = r.degraded ? candidate : r.match ?? candidate;
|
|
6786
7038
|
if (!isFinished(m.status)) return { ...r, match: m, ...bothReads(windowRead, refreshRead) };
|
|
@@ -7013,10 +7265,19 @@ async function getScheduleAhead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
7013
7265
|
};
|
|
7014
7266
|
}
|
|
7015
7267
|
async function getLiveMatches(adapter, now = /* @__PURE__ */ new Date()) {
|
|
7016
|
-
const { complete: _complete, ...result } = await getLiveRead(adapter, now);
|
|
7268
|
+
const { complete: _complete, finished: _finished, finishedTruncated: _truncated, ...result } = await getLiveRead(adapter, now);
|
|
7017
7269
|
return result;
|
|
7018
7270
|
}
|
|
7019
|
-
|
|
7271
|
+
var MAX_LIVE_FINISHED = 64;
|
|
7272
|
+
function finishedOf(records, seen) {
|
|
7273
|
+
const wanted = new Set(takeBounded(seen, MAX_LIVE_FINISHED));
|
|
7274
|
+
const done = records.filter((m) => m.status === "FT" && m.score !== void 0);
|
|
7275
|
+
const first = done.filter((m) => wanted.has(m.id));
|
|
7276
|
+
const rest = done.filter((m) => !wanted.has(m.id));
|
|
7277
|
+
const all = [...first, ...rest];
|
|
7278
|
+
return { finished: all.slice(0, MAX_LIVE_FINISHED), truncated: all.length > MAX_LIVE_FINISHED };
|
|
7279
|
+
}
|
|
7280
|
+
async function getLiveRead(adapter, now = /* @__PURE__ */ new Date(), seen = []) {
|
|
7020
7281
|
try {
|
|
7021
7282
|
const day = now.toISOString().slice(0, 10);
|
|
7022
7283
|
const fetched = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1), { acrossSeasons: true }) : await adapter.fetchLive();
|
|
@@ -7024,6 +7285,7 @@ async function getLiveRead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
7024
7285
|
const season = meta?.season;
|
|
7025
7286
|
const matches = fetched.filter((m) => isLive(m.status));
|
|
7026
7287
|
const between = betweenEditionsOf(adapter, meta, fetched, providerDayOf(adapter, now));
|
|
7288
|
+
const done = finishedOf(fetched, seen);
|
|
7027
7289
|
return {
|
|
7028
7290
|
matches,
|
|
7029
7291
|
degraded: false,
|
|
@@ -7035,10 +7297,12 @@ async function getLiveRead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
7035
7297
|
// boolean below is the refresher's, unchanged: false for that, for an
|
|
7036
7298
|
// adapter that said nothing, and for a failed read alike.
|
|
7037
7299
|
...partialOfRead(meta),
|
|
7038
|
-
complete: meta?.complete === true
|
|
7300
|
+
complete: meta?.complete === true,
|
|
7301
|
+
finished: done.finished,
|
|
7302
|
+
finishedTruncated: done.truncated
|
|
7039
7303
|
};
|
|
7040
7304
|
} catch {
|
|
7041
|
-
return { matches: [], degraded: true, complete: false };
|
|
7305
|
+
return { matches: [], degraded: true, complete: false, finished: [], finishedTruncated: false };
|
|
7042
7306
|
}
|
|
7043
7307
|
}
|
|
7044
7308
|
|
|
@@ -7288,15 +7552,22 @@ function sealMarketSignal(raw, options = {}) {
|
|
|
7288
7552
|
if (sourceMarketId) out.sourceMarketId = sourceMarketId;
|
|
7289
7553
|
out.asOf = canonicalTimestamp(s.asOf) ?? "";
|
|
7290
7554
|
out.fetchedAt = canonicalTimestamp(s.fetchedAt) ?? "";
|
|
7555
|
+
const judgedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
7556
|
+
const fetchedMs = Date.parse(out.fetchedAt);
|
|
7557
|
+
if (out.fetchedAt === "" || !Number.isFinite(fetchedMs)) {
|
|
7558
|
+
return malformed("signal carries no readable fetch time");
|
|
7559
|
+
}
|
|
7560
|
+
if (fetchedMs > judgedAt.getTime()) {
|
|
7561
|
+
return unresolved("signal was fetched after the clock it is judged by");
|
|
7562
|
+
}
|
|
7291
7563
|
out.outcomes = outcomes;
|
|
7292
7564
|
const isAmbiguous = s.ambiguous !== false;
|
|
7293
7565
|
const favorite = isAmbiguous ? void 0 : deriveFavorite(outcomes);
|
|
7294
7566
|
if (favorite) out.favorite = favorite;
|
|
7295
7567
|
if (liquidity !== void 0) out.liquidity = liquidity;
|
|
7296
7568
|
if (volume24h !== void 0) out.volume24h = volume24h;
|
|
7297
|
-
out.stale =
|
|
7569
|
+
out.stale = isStaleSignal(out, { now: judgedAt, maxAgeMs: options.maxAgeMs });
|
|
7298
7570
|
out.ambiguous = isAmbiguous || out.source === "";
|
|
7299
|
-
out.stale = out.stale || isStaleSignal(out, { now: options.now, maxAgeMs: options.maxAgeMs });
|
|
7300
7571
|
return valid(out);
|
|
7301
7572
|
}
|
|
7302
7573
|
function parseCachedMarketSignal(raw, options = {}) {
|
|
@@ -7305,7 +7576,7 @@ function parseCachedMarketSignal(raw, options = {}) {
|
|
|
7305
7576
|
|
|
7306
7577
|
// src/markets/normalize.ts
|
|
7307
7578
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
7308
|
-
function marketRelevant(match, now
|
|
7579
|
+
function marketRelevant(match, now) {
|
|
7309
7580
|
if (isLive(match.status)) return true;
|
|
7310
7581
|
if (isFinished(match.status)) return false;
|
|
7311
7582
|
const k = Date.parse(match.kickoff);
|
|
@@ -7394,12 +7665,13 @@ function buildMarketSignal(input) {
|
|
|
7394
7665
|
const outcomes = normalizeOutcomes(input.outcomes);
|
|
7395
7666
|
const ambiguous2 = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
|
|
7396
7667
|
const favorite = ambiguous2 ? void 0 : deriveFavorite(outcomes);
|
|
7668
|
+
const clock = input.now ?? /* @__PURE__ */ new Date();
|
|
7397
7669
|
const signal = {
|
|
7398
7670
|
matchId: input.match.id,
|
|
7399
7671
|
source: input.source,
|
|
7400
7672
|
sourceMarketId: input.sourceMarketId,
|
|
7401
7673
|
asOf: input.asOf,
|
|
7402
|
-
fetchedAt: input.fetchedAt ??
|
|
7674
|
+
fetchedAt: input.fetchedAt ?? clock.toISOString(),
|
|
7403
7675
|
outcomes,
|
|
7404
7676
|
favorite,
|
|
7405
7677
|
liquidity: input.liquidity,
|
|
@@ -7407,8 +7679,8 @@ function buildMarketSignal(input) {
|
|
|
7407
7679
|
stale: false,
|
|
7408
7680
|
ambiguous: ambiguous2
|
|
7409
7681
|
};
|
|
7410
|
-
signal.stale = isStaleSignal(signal, { now:
|
|
7411
|
-
const sealed = sealMarketSignal(signal, { now:
|
|
7682
|
+
signal.stale = isStaleSignal(signal, { now: clock, maxAgeMs: input.maxAgeMs });
|
|
7683
|
+
const sealed = sealMarketSignal(signal, { now: clock, maxAgeMs: input.maxAgeMs });
|
|
7412
7684
|
if (sealed.kind !== "valid") {
|
|
7413
7685
|
return { ...signal, outcomes: [], favorite: void 0, stale: true, ambiguous: true };
|
|
7414
7686
|
}
|
|
@@ -8684,6 +8956,7 @@ export {
|
|
|
8684
8956
|
MAX_CONFIG_BYTES,
|
|
8685
8957
|
MAX_COOLDOWN_MS,
|
|
8686
8958
|
MAX_LABEL_COLUMNS,
|
|
8959
|
+
MAX_LIVE_FINISHED,
|
|
8687
8960
|
MAX_RESPONSE_BYTES,
|
|
8688
8961
|
MAX_SCHEDULE_INDEX,
|
|
8689
8962
|
NO_BRACKET,
|
|
@@ -8833,6 +9106,7 @@ export {
|
|
|
8833
9106
|
matchWindowSentence,
|
|
8834
9107
|
mergeLive,
|
|
8835
9108
|
modeLine,
|
|
9109
|
+
momentWords,
|
|
8836
9110
|
nationArg,
|
|
8837
9111
|
nationToFlag,
|
|
8838
9112
|
nationToRegion,
|
|
@@ -8854,11 +9128,13 @@ export {
|
|
|
8854
9128
|
parsedValue,
|
|
8855
9129
|
pinUnder,
|
|
8856
9130
|
productFlag,
|
|
9131
|
+
provenLastGoal,
|
|
8857
9132
|
rallyCryFor,
|
|
8858
9133
|
rallyEntryFor,
|
|
8859
9134
|
readJsonBounded,
|
|
8860
9135
|
readSmallFile,
|
|
8861
9136
|
readUserConfig,
|
|
9137
|
+
requestClock,
|
|
8862
9138
|
resolveClub,
|
|
8863
9139
|
resolveCompetition,
|
|
8864
9140
|
resolveMarketSource,
|
|
@@ -8871,6 +9147,7 @@ export {
|
|
|
8871
9147
|
scheduleEntryOf,
|
|
8872
9148
|
scheduleSpan,
|
|
8873
9149
|
scoreline,
|
|
9150
|
+
sealEvent,
|
|
8874
9151
|
sealMarketSignal,
|
|
8875
9152
|
sealMatch,
|
|
8876
9153
|
sealScheduleEntry,
|
|
@@ -8890,7 +9167,10 @@ export {
|
|
|
8890
9167
|
tableKeyArg,
|
|
8891
9168
|
tableShareCard,
|
|
8892
9169
|
tableTitle,
|
|
9170
|
+
takeBounded,
|
|
8893
9171
|
teamKind,
|
|
9172
|
+
timelineParts,
|
|
9173
|
+
timelineRows,
|
|
8894
9174
|
truncateVisible,
|
|
8895
9175
|
unresolved,
|
|
8896
9176
|
unservedSentence,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "The engine behind Claudinho: the domain model, the provider adapters (ESPN), live scores and standings for the competition you follow (16 supported), the bundled 2026 World Cup schedule with its knockout bracket and read-only Polymarket sidecar. Independent fan project. Not affiliated with FIFA, any confederation, league or club, or Anthropic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"club-world-cup"
|
|
68
68
|
],
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@types/node": "^22.20.
|
|
70
|
+
"@types/node": "^22.20.5",
|
|
71
71
|
"@vitest/coverage-v8": "^5.0.3",
|
|
72
72
|
"tsup": "^8.0.0",
|
|
73
73
|
"tsx": "^4.23.15",
|