@claudinho/cli 0.11.2 → 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/README.md +324 -267
- package/dist/index.js +779 -159
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -335,6 +335,13 @@ var EN = {
|
|
|
335
335
|
"status.fullTime": "full-time",
|
|
336
336
|
"status.postponed": "postponed",
|
|
337
337
|
"status.cancelled": "cancelled",
|
|
338
|
+
// A timeline row's kind word (core `timelineRows`).
|
|
339
|
+
"event.goal": "Goal",
|
|
340
|
+
"event.ownGoal": "Own goal",
|
|
341
|
+
"event.penalty": "Penalty",
|
|
342
|
+
"event.yellow": "Yellow card",
|
|
343
|
+
"event.red": "Red card",
|
|
344
|
+
"event.sub": "Substitution",
|
|
338
345
|
"live.title": "Live now",
|
|
339
346
|
"today.title": "Today's matches",
|
|
340
347
|
"today.on": "Matches",
|
|
@@ -343,6 +350,23 @@ var EN = {
|
|
|
343
350
|
"next.in": "in {countdown}",
|
|
344
351
|
// A kickoff that has passed, where the countdown would say "in" (core `countdownPhrase`).
|
|
345
352
|
"countdown.now": "now",
|
|
353
|
+
// The ambient line's syncing phrase (`claudinho ambient --json` alone; the
|
|
354
|
+
// statusline's own `prompt` stays English).
|
|
355
|
+
"ambient.syncing": "live \xB7 syncing\u2026",
|
|
356
|
+
// The plugin's moment words, sent by `claudinho ambient --json` as `words`
|
|
357
|
+
// (`{team}` a template the plugin fills with the record's own names). Each
|
|
358
|
+
// at most 40 characters; `{team}` once in `moment.goalFor` and `moment.win`
|
|
359
|
+
// and nowhere else, or the plugin falls back to its English words.
|
|
360
|
+
"moment.goal": "GOAL!",
|
|
361
|
+
"moment.ownGoal": "Own goal",
|
|
362
|
+
"moment.ownGoalDetail": "own goal",
|
|
363
|
+
"moment.goalFor": "Goal for {team}",
|
|
364
|
+
"moment.goals": "Goals",
|
|
365
|
+
"moment.scoreUpdated": "Score updated",
|
|
366
|
+
"moment.penalties": "Penalties",
|
|
367
|
+
"moment.fullTime": "Full time",
|
|
368
|
+
"moment.win": "{team} win",
|
|
369
|
+
"moment.draw": "Draw",
|
|
346
370
|
// A title that introduces the lines below it (the MCP text).
|
|
347
371
|
heading: "{title}:",
|
|
348
372
|
// A list's empty state and the outage lines: the CLI's sentences, the MCP
|
|
@@ -354,6 +378,7 @@ var EN = {
|
|
|
354
378
|
"feed.earlierRecord": "Live state could not be refreshed \u2014 showing the provider's earlier record.",
|
|
355
379
|
"next.none": "No upcoming fixture found for {team}.",
|
|
356
380
|
"match.none": "No match found with id {id}.",
|
|
381
|
+
"match.kickoff": "Kickoff",
|
|
357
382
|
// The MCP text's own sentences for these states (the CLI says `live.degraded`
|
|
358
383
|
// there): they keep the team or the id the answer is about.
|
|
359
384
|
"next.unreachable": "Couldn't reach the data provider \u2014 no upcoming fixture confirmed for {team}.",
|
|
@@ -431,6 +456,12 @@ var ES = {
|
|
|
431
456
|
"status.fullTime": "finalizado",
|
|
432
457
|
"status.postponed": "aplazado",
|
|
433
458
|
"status.cancelled": "cancelado",
|
|
459
|
+
"event.goal": "Gol",
|
|
460
|
+
"event.ownGoal": "Gol en contra",
|
|
461
|
+
"event.penalty": "Penal",
|
|
462
|
+
"event.yellow": "Tarjeta amarilla",
|
|
463
|
+
"event.red": "Tarjeta roja",
|
|
464
|
+
"event.sub": "Cambio",
|
|
434
465
|
"live.title": "En vivo",
|
|
435
466
|
"today.title": "Partidos de hoy",
|
|
436
467
|
"today.on": "Partidos",
|
|
@@ -438,6 +469,17 @@ var ES = {
|
|
|
438
469
|
"next.label": "Pr\xF3ximo partido de {team}",
|
|
439
470
|
"next.in": "en {countdown}",
|
|
440
471
|
"countdown.now": "ahora",
|
|
472
|
+
"ambient.syncing": "en vivo \xB7 sincronizando\u2026",
|
|
473
|
+
"moment.goal": "\xA1GOL!",
|
|
474
|
+
"moment.ownGoal": "Gol en contra",
|
|
475
|
+
"moment.ownGoalDetail": "gol en contra",
|
|
476
|
+
"moment.goalFor": "Gol de {team}",
|
|
477
|
+
"moment.goals": "Goles",
|
|
478
|
+
"moment.scoreUpdated": "Marcador corregido",
|
|
479
|
+
"moment.penalties": "Penales",
|
|
480
|
+
"moment.fullTime": "Final",
|
|
481
|
+
"moment.win": "Gana {team}",
|
|
482
|
+
"moment.draw": "Empate",
|
|
441
483
|
heading: "{title}:",
|
|
442
484
|
"today.none": "No hay partidos para esta fecha.",
|
|
443
485
|
"live.none": "No hay partidos en juego ahora mismo.",
|
|
@@ -446,6 +488,7 @@ var ES = {
|
|
|
446
488
|
"feed.earlierRecord": "No se pudo actualizar el estado en vivo \u2014 se muestra el registro anterior del proveedor.",
|
|
447
489
|
"next.none": "No se encontr\xF3 pr\xF3ximo partido para {team}.",
|
|
448
490
|
"match.none": "No se encontr\xF3 partido con id {id}.",
|
|
491
|
+
"match.kickoff": "Inicio",
|
|
449
492
|
"next.unreachable": "No se pudo contactar al proveedor de datos \u2014 ning\xFAn pr\xF3ximo partido confirmado para {team}.",
|
|
450
493
|
"match.unreachable": "No se pudo contactar al proveedor de datos \u2014 no se pudo consultar el partido {id}.",
|
|
451
494
|
"team.ambiguous": '"{query}" es ambiguo. \xBFQuisiste decir:',
|
|
@@ -520,6 +563,12 @@ var PT = {
|
|
|
520
563
|
"status.fullTime": "encerrado",
|
|
521
564
|
"status.postponed": "adiado",
|
|
522
565
|
"status.cancelled": "cancelado",
|
|
566
|
+
"event.goal": "Gol",
|
|
567
|
+
"event.ownGoal": "Gol contra",
|
|
568
|
+
"event.penalty": "P\xEAnalti",
|
|
569
|
+
"event.yellow": "Cart\xE3o amarelo",
|
|
570
|
+
"event.red": "Cart\xE3o vermelho",
|
|
571
|
+
"event.sub": "Substitui\xE7\xE3o",
|
|
523
572
|
"live.title": "Ao vivo",
|
|
524
573
|
"today.title": "Jogos de hoje",
|
|
525
574
|
"today.on": "Jogos",
|
|
@@ -527,6 +576,17 @@ var PT = {
|
|
|
527
576
|
"next.label": "Pr\xF3ximo jogo de {team}",
|
|
528
577
|
"next.in": "em {countdown}",
|
|
529
578
|
"countdown.now": "agora",
|
|
579
|
+
"ambient.syncing": "ao vivo \xB7 sincronizando\u2026",
|
|
580
|
+
"moment.goal": "GOL!",
|
|
581
|
+
"moment.ownGoal": "Gol contra",
|
|
582
|
+
"moment.ownGoalDetail": "gol contra",
|
|
583
|
+
"moment.goalFor": "Gol \xB7 {team}",
|
|
584
|
+
"moment.goals": "Gols",
|
|
585
|
+
"moment.scoreUpdated": "Placar corrigido",
|
|
586
|
+
"moment.penalties": "P\xEAnaltis",
|
|
587
|
+
"moment.fullTime": "Fim de jogo",
|
|
588
|
+
"moment.win": "{team} vence",
|
|
589
|
+
"moment.draw": "Empate",
|
|
530
590
|
heading: "{title}:",
|
|
531
591
|
"today.none": "Nenhum jogo para esta data.",
|
|
532
592
|
"live.none": "Nenhum jogo em andamento agora.",
|
|
@@ -535,6 +595,7 @@ var PT = {
|
|
|
535
595
|
"feed.earlierRecord": "N\xE3o foi poss\xEDvel atualizar o estado ao vivo \u2014 mostrando o registro anterior do provedor.",
|
|
536
596
|
"next.none": "Nenhum pr\xF3ximo jogo encontrado para {team}.",
|
|
537
597
|
"match.none": "Nenhum jogo encontrado com id {id}.",
|
|
598
|
+
"match.kickoff": "In\xEDcio",
|
|
538
599
|
"next.unreachable": "N\xE3o foi poss\xEDvel contatar o provedor de dados \u2014 nenhum pr\xF3ximo jogo confirmado para {team}.",
|
|
539
600
|
"match.unreachable": "N\xE3o foi poss\xEDvel contatar o provedor de dados \u2014 o jogo {id} n\xE3o p\xF4de ser consultado.",
|
|
540
601
|
"team.ambiguous": '"{query}" \xE9 amb\xEDguo. Voc\xEA quis dizer:',
|
|
@@ -609,6 +670,12 @@ var FR = {
|
|
|
609
670
|
"status.fullTime": "termin\xE9",
|
|
610
671
|
"status.postponed": "report\xE9",
|
|
611
672
|
"status.cancelled": "annul\xE9",
|
|
673
|
+
"event.goal": "But",
|
|
674
|
+
"event.ownGoal": "But contre son camp",
|
|
675
|
+
"event.penalty": "Penalty",
|
|
676
|
+
"event.yellow": "Carton jaune",
|
|
677
|
+
"event.red": "Carton rouge",
|
|
678
|
+
"event.sub": "Remplacement",
|
|
612
679
|
"live.title": "En direct",
|
|
613
680
|
"today.title": "Matchs d'aujourd'hui",
|
|
614
681
|
"today.on": "Matchs",
|
|
@@ -616,6 +683,17 @@ var FR = {
|
|
|
616
683
|
"next.label": "Prochain match de {team}",
|
|
617
684
|
"next.in": "dans {countdown}",
|
|
618
685
|
"countdown.now": "maintenant",
|
|
686
|
+
"ambient.syncing": "en direct \xB7 synchronisation\u2026",
|
|
687
|
+
"moment.goal": "BUT !",
|
|
688
|
+
"moment.ownGoal": "But contre son camp",
|
|
689
|
+
"moment.ownGoalDetail": "contre son camp",
|
|
690
|
+
"moment.goalFor": "But pour {team}",
|
|
691
|
+
"moment.goals": "Buts",
|
|
692
|
+
"moment.scoreUpdated": "Score corrig\xE9",
|
|
693
|
+
"moment.penalties": "Tirs au but",
|
|
694
|
+
"moment.fullTime": "Fin du match",
|
|
695
|
+
"moment.win": "{team} gagne",
|
|
696
|
+
"moment.draw": "Match nul",
|
|
619
697
|
heading: "{title} :",
|
|
620
698
|
"today.none": "Aucun match pr\xE9vu pour cette date.",
|
|
621
699
|
"live.none": "Aucun match en cours pour l'instant.",
|
|
@@ -624,6 +702,7 @@ var FR = {
|
|
|
624
702
|
"feed.earlierRecord": "L\u2019\xE9tat en direct n\u2019a pas pu \xEAtre actualis\xE9 \u2014 affichage de l\u2019enregistrement pr\xE9c\xE9dent du fournisseur.",
|
|
625
703
|
"next.none": "Aucun prochain match trouv\xE9 pour {team}.",
|
|
626
704
|
"match.none": "Aucun match trouv\xE9 avec id {id}.",
|
|
705
|
+
"match.kickoff": "Coup d\u2019envoi",
|
|
627
706
|
"next.unreachable": "Impossible de joindre le fournisseur de donn\xE9es \u2014 aucun prochain match confirm\xE9 pour {team}.",
|
|
628
707
|
"match.unreachable": "Impossible de joindre le fournisseur de donn\xE9es \u2014 le match {id} n\u2019a pas pu \xEAtre consult\xE9.",
|
|
629
708
|
"team.ambiguous": '"{query}" est ambigu. Vouliez-vous dire :',
|
|
@@ -657,6 +736,20 @@ function t(lang, key, vars) {
|
|
|
657
736
|
(whole, name) => Object.hasOwn(vars, name) ? vars[name] : whole
|
|
658
737
|
);
|
|
659
738
|
}
|
|
739
|
+
function momentWords(lang) {
|
|
740
|
+
return {
|
|
741
|
+
goal: t(lang, "moment.goal"),
|
|
742
|
+
ownGoal: t(lang, "moment.ownGoal"),
|
|
743
|
+
ownGoalDetail: t(lang, "moment.ownGoalDetail"),
|
|
744
|
+
goalFor: t(lang, "moment.goalFor"),
|
|
745
|
+
goals: t(lang, "moment.goals"),
|
|
746
|
+
scoreUpdated: t(lang, "moment.scoreUpdated"),
|
|
747
|
+
penalties: t(lang, "moment.penalties"),
|
|
748
|
+
fullTime: t(lang, "moment.fullTime"),
|
|
749
|
+
win: t(lang, "moment.win"),
|
|
750
|
+
draw: t(lang, "moment.draw")
|
|
751
|
+
};
|
|
752
|
+
}
|
|
660
753
|
var STAGE_KEYS = Object.freeze({
|
|
661
754
|
GROUP: "stage.groupStage",
|
|
662
755
|
R32: "stage.r32",
|
|
@@ -838,6 +931,65 @@ function withFlag(text, flag2, side) {
|
|
|
838
931
|
if (typeof flag2 !== "string" || flag2 === "") return text;
|
|
839
932
|
return side === "home" ? `${flag2} ${text}` : `${text} ${flag2}`;
|
|
840
933
|
}
|
|
934
|
+
var KIND_KEYS = Object.freeze({
|
|
935
|
+
GOAL: "event.goal",
|
|
936
|
+
OWN_GOAL: "event.ownGoal",
|
|
937
|
+
PEN: "event.penalty",
|
|
938
|
+
YELLOW: "event.yellow",
|
|
939
|
+
RED: "event.red",
|
|
940
|
+
SUB: "event.sub"
|
|
941
|
+
});
|
|
942
|
+
var KIND_MARKS = Object.freeze({
|
|
943
|
+
GOAL: "\u26BD",
|
|
944
|
+
OWN_GOAL: "\u26BD",
|
|
945
|
+
PEN: "\u26BD",
|
|
946
|
+
YELLOW: "\u{1F7E8}",
|
|
947
|
+
RED: "\u{1F7E5}",
|
|
948
|
+
SUB: "\u{1F504}"
|
|
949
|
+
});
|
|
950
|
+
function timelineParts(match, opts) {
|
|
951
|
+
const events = match.events ?? [];
|
|
952
|
+
if (events.length === 0) return [];
|
|
953
|
+
const cells = events.map((e) => {
|
|
954
|
+
const team = e.side === "home" ? match.home : match.away;
|
|
955
|
+
return {
|
|
956
|
+
minute: e.added !== void 0 ? `${e.minute}+${e.added}'` : `${e.minute}'`,
|
|
957
|
+
mark: KIND_MARKS[e.type],
|
|
958
|
+
kind: t(opts.lang, KIND_KEYS[e.type]),
|
|
959
|
+
label: withFlag(team.code, opts.flags ? team.flag : void 0, e.side),
|
|
960
|
+
player: e.player
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
const widest = (pick2) => Math.max(...cells.map((c) => displayWidth(pick2(c))));
|
|
964
|
+
const minuteW = widest((c) => c.minute);
|
|
965
|
+
const kindW = widest((c) => c.kind);
|
|
966
|
+
const labelW = widest((c) => c.label);
|
|
967
|
+
return cells.map((c) => {
|
|
968
|
+
const columns = ` ${padVisible(c.minute, minuteW)} ${c.mark} ${padVisible(c.kind, kindW)} ${padVisible(c.label, labelW)}`;
|
|
969
|
+
if (!c.player) return [{ text: columns.trimEnd() }];
|
|
970
|
+
return [{ text: `${columns} ` }, { text: c.player, style: "strong" }];
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
var SCORING = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN"]);
|
|
974
|
+
function provenLastGoal(match) {
|
|
975
|
+
const score = match.score;
|
|
976
|
+
if (!score) return null;
|
|
977
|
+
let home = 0;
|
|
978
|
+
let away = 0;
|
|
979
|
+
let last = null;
|
|
980
|
+
const identities = /* @__PURE__ */ new Set();
|
|
981
|
+
for (const e of match.events ?? []) {
|
|
982
|
+
if (!SCORING.has(e.type)) continue;
|
|
983
|
+
const identity = JSON.stringify([e.type, e.minute, e.added ?? 0, e.side, e.player ?? null]);
|
|
984
|
+
if (identities.has(identity)) return null;
|
|
985
|
+
identities.add(identity);
|
|
986
|
+
if (e.side === "home") home++;
|
|
987
|
+
else if (e.side === "away") away++;
|
|
988
|
+
else continue;
|
|
989
|
+
last = e;
|
|
990
|
+
}
|
|
991
|
+
return last !== null && home === score.home && away === score.away ? last : null;
|
|
992
|
+
}
|
|
841
993
|
function outcomeFromScore(home, away) {
|
|
842
994
|
if (home > away) return "H";
|
|
843
995
|
if (home < away) return "A";
|
|
@@ -1109,18 +1261,33 @@ function sealScorePair(raw) {
|
|
|
1109
1261
|
const away = count(v.away, MAX_GOALS);
|
|
1110
1262
|
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
1111
1263
|
}
|
|
1264
|
+
var MAX_ADDED_MINUTES = 30;
|
|
1265
|
+
var SIDES = /* @__PURE__ */ new Set(["home", "away"]);
|
|
1112
1266
|
function sealEvent(raw) {
|
|
1113
|
-
if (!raw || typeof raw !== "object") return
|
|
1267
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1114
1268
|
const e = raw;
|
|
1115
1269
|
const type = member(e.type, EVENT_TYPES);
|
|
1116
|
-
if (!type) return
|
|
1270
|
+
if (!type) return null;
|
|
1117
1271
|
const minute = count(e.minute, MAX_MINUTE);
|
|
1118
|
-
if (minute === void 0) return
|
|
1119
|
-
|
|
1272
|
+
if (minute === void 0) return null;
|
|
1273
|
+
let added;
|
|
1274
|
+
if (e.added !== void 0) {
|
|
1275
|
+
added = count(e.added, MAX_ADDED_MINUTES);
|
|
1276
|
+
if (added === void 0 || added < 1) return null;
|
|
1277
|
+
}
|
|
1278
|
+
const side = member(e.side, SIDES);
|
|
1279
|
+
if (!side) return null;
|
|
1280
|
+
const out2 = { type, minute };
|
|
1281
|
+
if (added !== void 0) out2.added = added;
|
|
1282
|
+
out2.side = side;
|
|
1283
|
+
out2.teamCode = humanLabel(e.teamCode, TEAM_CODE_COLUMNS);
|
|
1120
1284
|
const player = humanLabel(e.player);
|
|
1121
1285
|
if (player) out2.player = player;
|
|
1122
1286
|
return out2;
|
|
1123
1287
|
}
|
|
1288
|
+
function byClock(a, b) {
|
|
1289
|
+
return a.minute - b.minute || (a.added ?? 0) - (b.added ?? 0);
|
|
1290
|
+
}
|
|
1124
1291
|
function sealMatch(parts, opts = {}) {
|
|
1125
1292
|
if (!parts || typeof parts !== "object") return malformed("match is not an object");
|
|
1126
1293
|
const id = opaqueId(parts.id, ESPN_ID);
|
|
@@ -1165,7 +1332,8 @@ function sealMatch(parts, opts = {}) {
|
|
|
1165
1332
|
winnerCode = claimedWinner;
|
|
1166
1333
|
}
|
|
1167
1334
|
}
|
|
1168
|
-
const
|
|
1335
|
+
const timelined = status === "LIVE" || status === "HT" || status === "FT";
|
|
1336
|
+
const events = opts.events === false || !timelined ? [] : takeBounded(parts.events, MAX_MATCH_EVENTS).map(sealEvent).filter((e) => !!e).sort(byClock);
|
|
1169
1337
|
const out2 = { id, stage };
|
|
1170
1338
|
if (stageLabel2) out2.stageLabel = stageLabel2;
|
|
1171
1339
|
if (group) out2.group = group;
|
|
@@ -4307,6 +4475,75 @@ function parseMinute(st) {
|
|
|
4307
4475
|
}
|
|
4308
4476
|
return void 0;
|
|
4309
4477
|
}
|
|
4478
|
+
var DETAIL_FLAGS = ["scoringPlay", "yellowCard", "redCard", "penaltyKick", "ownGoal", "shootout"];
|
|
4479
|
+
var DETAIL_MINUTE = /^(\d{1,3})'$/;
|
|
4480
|
+
var DETAIL_MINUTE_ADDED = /^(\d{1,3})'\+(\d{1,2})'$/;
|
|
4481
|
+
function detailKind(d) {
|
|
4482
|
+
const f = {};
|
|
4483
|
+
for (const name of DETAIL_FLAGS) {
|
|
4484
|
+
const v = flag(d[name]);
|
|
4485
|
+
if (v === void 0) return void 0;
|
|
4486
|
+
f[name] = v;
|
|
4487
|
+
}
|
|
4488
|
+
if (f.shootout) return void 0;
|
|
4489
|
+
const { scoringPlay: s, yellowCard: y, redCard: r, penaltyKick: p, ownGoal: o } = f;
|
|
4490
|
+
if (s && !y && !r) {
|
|
4491
|
+
if (!p && !o) return "GOAL";
|
|
4492
|
+
if (o && !p) return "OWN_GOAL";
|
|
4493
|
+
if (p && !o) return "PEN";
|
|
4494
|
+
return void 0;
|
|
4495
|
+
}
|
|
4496
|
+
if (p || o || s) return void 0;
|
|
4497
|
+
if (y && !r) return "YELLOW";
|
|
4498
|
+
if (r && !y) return "RED";
|
|
4499
|
+
return void 0;
|
|
4500
|
+
}
|
|
4501
|
+
function detailMinute(clock) {
|
|
4502
|
+
if (!clock || typeof clock !== "object") return void 0;
|
|
4503
|
+
const dv = clock.displayValue;
|
|
4504
|
+
if (typeof dv !== "string") return void 0;
|
|
4505
|
+
const plain2 = DETAIL_MINUTE.exec(dv);
|
|
4506
|
+
if (plain2) return { minute: Number(plain2[1]) };
|
|
4507
|
+
const extra = DETAIL_MINUTE_ADDED.exec(dv);
|
|
4508
|
+
if (!extra) return void 0;
|
|
4509
|
+
return { minute: Number(extra[1]), added: Number(extra[2]) };
|
|
4510
|
+
}
|
|
4511
|
+
function rawTeamId(holder) {
|
|
4512
|
+
if (!holder || typeof holder !== "object") return void 0;
|
|
4513
|
+
const team = holder.team;
|
|
4514
|
+
if (!team || typeof team !== "object") return void 0;
|
|
4515
|
+
return opaqueId(team.id, ESPN_ID);
|
|
4516
|
+
}
|
|
4517
|
+
function detailEvents(details, sides) {
|
|
4518
|
+
const out2 = [];
|
|
4519
|
+
for (const raw of takeBounded(details, MAX_MATCH_EVENTS)) {
|
|
4520
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
4521
|
+
const d = raw;
|
|
4522
|
+
const type = detailKind(d);
|
|
4523
|
+
if (!type) continue;
|
|
4524
|
+
const when = detailMinute(d.clock);
|
|
4525
|
+
if (!when) continue;
|
|
4526
|
+
const teamId = rawTeamId(d);
|
|
4527
|
+
const side = teamId === void 0 ? void 0 : teamId === sides.homeId ? "home" : teamId === sides.awayId ? "away" : void 0;
|
|
4528
|
+
if (!side) continue;
|
|
4529
|
+
const event = { type, minute: when.minute };
|
|
4530
|
+
if (when.added !== void 0) event.added = when.added;
|
|
4531
|
+
event.side = side;
|
|
4532
|
+
event.teamCode = side === "home" ? sides.homeCode : sides.awayCode;
|
|
4533
|
+
const player = detailPlayer(d.athletesInvolved, type === "OWN_GOAL" ? side === "home" ? sides.awayId : sides.homeId : teamId);
|
|
4534
|
+
if (player) event.player = player;
|
|
4535
|
+
out2.push(event);
|
|
4536
|
+
}
|
|
4537
|
+
return out2;
|
|
4538
|
+
}
|
|
4539
|
+
function detailPlayer(athletes, expectedTeamId) {
|
|
4540
|
+
if (!Array.isArray(athletes) || athletes.length !== 1 || expectedTeamId === void 0) return void 0;
|
|
4541
|
+
const a = athletes[0];
|
|
4542
|
+
if (rawTeamId(a) !== expectedTeamId) return void 0;
|
|
4543
|
+
const name = a.displayName;
|
|
4544
|
+
if (!isHumanLabel(name)) return void 0;
|
|
4545
|
+
return humanLabel(name) || void 0;
|
|
4546
|
+
}
|
|
4310
4547
|
var MAX_SLUG_UNITS2 = 64;
|
|
4311
4548
|
var SLUG = /^[a-z0-9-]+$/;
|
|
4312
4549
|
var EXACT_STAGE = Object.freeze({
|
|
@@ -4428,7 +4665,14 @@ function parseEspnEvent(raw, ctx = {}) {
|
|
|
4428
4665
|
status,
|
|
4429
4666
|
winnerCode,
|
|
4430
4667
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4431
|
-
|
|
4668
|
+
// The goals and cards: ESPN's `details`, read by the detail grammar; the
|
|
4669
|
+
// seal keeps them only on a status that holds a score.
|
|
4670
|
+
events: detailEvents(comp?.details, {
|
|
4671
|
+
homeId: rawTeamId(homeRaw),
|
|
4672
|
+
awayId: rawTeamId(awayRaw),
|
|
4673
|
+
homeCode: home.code,
|
|
4674
|
+
awayCode: away.code
|
|
4675
|
+
})
|
|
4432
4676
|
},
|
|
4433
4677
|
// The final reseal states the same kind the competitors were built with.
|
|
4434
4678
|
{ teamKind: kind }
|
|
@@ -4745,7 +4989,7 @@ function fetchMeta(result) {
|
|
|
4745
4989
|
}
|
|
4746
4990
|
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
4747
4991
|
var DEFAULT_COMPETITION = BUNDLED_SLUG;
|
|
4748
|
-
var USER_AGENT = `claudinho/${"0.
|
|
4992
|
+
var USER_AGENT = `claudinho/${"0.12.0"} (+https://github.com/arturogarrido/claudinho)`;
|
|
4749
4993
|
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
4750
4994
|
var DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
4751
4995
|
var MAX_COOLDOWN_MS = 15 * 6e4;
|
|
@@ -6647,10 +6891,19 @@ async function getScheduleAhead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
6647
6891
|
};
|
|
6648
6892
|
}
|
|
6649
6893
|
async function getLiveMatches(adapter, now = /* @__PURE__ */ new Date()) {
|
|
6650
|
-
const { complete: _complete, ...result } = await getLiveRead(adapter, now);
|
|
6894
|
+
const { complete: _complete, finished: _finished, finishedTruncated: _truncated, ...result } = await getLiveRead(adapter, now);
|
|
6651
6895
|
return result;
|
|
6652
6896
|
}
|
|
6653
|
-
|
|
6897
|
+
var MAX_LIVE_FINISHED = 64;
|
|
6898
|
+
function finishedOf(records, seen) {
|
|
6899
|
+
const wanted = new Set(takeBounded(seen, MAX_LIVE_FINISHED));
|
|
6900
|
+
const done = records.filter((m) => m.status === "FT" && m.score !== void 0);
|
|
6901
|
+
const first = done.filter((m) => wanted.has(m.id));
|
|
6902
|
+
const rest = done.filter((m) => !wanted.has(m.id));
|
|
6903
|
+
const all = [...first, ...rest];
|
|
6904
|
+
return { finished: all.slice(0, MAX_LIVE_FINISHED), truncated: all.length > MAX_LIVE_FINISHED };
|
|
6905
|
+
}
|
|
6906
|
+
async function getLiveRead(adapter, now = /* @__PURE__ */ new Date(), seen = []) {
|
|
6654
6907
|
try {
|
|
6655
6908
|
const day = now.toISOString().slice(0, 10);
|
|
6656
6909
|
const fetched = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1), { acrossSeasons: true }) : await adapter.fetchLive();
|
|
@@ -6658,6 +6911,7 @@ async function getLiveRead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
6658
6911
|
const season = meta?.season;
|
|
6659
6912
|
const matches = fetched.filter((m) => isLive(m.status));
|
|
6660
6913
|
const between = betweenEditionsOf(adapter, meta, fetched, providerDayOf(adapter, now));
|
|
6914
|
+
const done = finishedOf(fetched, seen);
|
|
6661
6915
|
return {
|
|
6662
6916
|
matches,
|
|
6663
6917
|
degraded: false,
|
|
@@ -6669,10 +6923,12 @@ async function getLiveRead(adapter, now = /* @__PURE__ */ new Date()) {
|
|
|
6669
6923
|
// boolean below is the refresher's, unchanged: false for that, for an
|
|
6670
6924
|
// adapter that said nothing, and for a failed read alike.
|
|
6671
6925
|
...partialOfRead(meta),
|
|
6672
|
-
complete: meta?.complete === true
|
|
6926
|
+
complete: meta?.complete === true,
|
|
6927
|
+
finished: done.finished,
|
|
6928
|
+
finishedTruncated: done.truncated
|
|
6673
6929
|
};
|
|
6674
6930
|
} catch {
|
|
6675
|
-
return { matches: [], degraded: true, complete: false };
|
|
6931
|
+
return { matches: [], degraded: true, complete: false, finished: [], finishedTruncated: false };
|
|
6676
6932
|
}
|
|
6677
6933
|
}
|
|
6678
6934
|
function norm2(s) {
|
|
@@ -6908,22 +7164,29 @@ function sealMarketSignal(raw, options = {}) {
|
|
|
6908
7164
|
if (sourceMarketId) out2.sourceMarketId = sourceMarketId;
|
|
6909
7165
|
out2.asOf = canonicalTimestamp(s.asOf) ?? "";
|
|
6910
7166
|
out2.fetchedAt = canonicalTimestamp(s.fetchedAt) ?? "";
|
|
7167
|
+
const judgedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
7168
|
+
const fetchedMs = Date.parse(out2.fetchedAt);
|
|
7169
|
+
if (out2.fetchedAt === "" || !Number.isFinite(fetchedMs)) {
|
|
7170
|
+
return malformed("signal carries no readable fetch time");
|
|
7171
|
+
}
|
|
7172
|
+
if (fetchedMs > judgedAt.getTime()) {
|
|
7173
|
+
return unresolved("signal was fetched after the clock it is judged by");
|
|
7174
|
+
}
|
|
6911
7175
|
out2.outcomes = outcomes;
|
|
6912
7176
|
const isAmbiguous = s.ambiguous !== false;
|
|
6913
7177
|
const favorite = isAmbiguous ? void 0 : deriveFavorite(outcomes);
|
|
6914
7178
|
if (favorite) out2.favorite = favorite;
|
|
6915
7179
|
if (liquidity !== void 0) out2.liquidity = liquidity;
|
|
6916
7180
|
if (volume24h !== void 0) out2.volume24h = volume24h;
|
|
6917
|
-
out2.stale =
|
|
7181
|
+
out2.stale = isStaleSignal(out2, { now: judgedAt, maxAgeMs: options.maxAgeMs });
|
|
6918
7182
|
out2.ambiguous = isAmbiguous || out2.source === "";
|
|
6919
|
-
out2.stale = out2.stale || isStaleSignal(out2, { now: options.now, maxAgeMs: options.maxAgeMs });
|
|
6920
7183
|
return valid(out2);
|
|
6921
7184
|
}
|
|
6922
7185
|
function parseCachedMarketSignal(raw, options = {}) {
|
|
6923
7186
|
return sealMarketSignal(raw, options);
|
|
6924
7187
|
}
|
|
6925
7188
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
6926
|
-
function marketRelevant(match, now
|
|
7189
|
+
function marketRelevant(match, now) {
|
|
6927
7190
|
if (isLive(match.status)) return true;
|
|
6928
7191
|
if (isFinished(match.status)) return false;
|
|
6929
7192
|
const k = Date.parse(match.kickoff);
|
|
@@ -7012,12 +7275,13 @@ function buildMarketSignal(input) {
|
|
|
7012
7275
|
const outcomes = normalizeOutcomes(input.outcomes);
|
|
7013
7276
|
const ambiguous2 = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
|
|
7014
7277
|
const favorite = ambiguous2 ? void 0 : deriveFavorite(outcomes);
|
|
7278
|
+
const clock = input.now ?? /* @__PURE__ */ new Date();
|
|
7015
7279
|
const signal = {
|
|
7016
7280
|
matchId: input.match.id,
|
|
7017
7281
|
source: input.source,
|
|
7018
7282
|
sourceMarketId: input.sourceMarketId,
|
|
7019
7283
|
asOf: input.asOf,
|
|
7020
|
-
fetchedAt: input.fetchedAt ??
|
|
7284
|
+
fetchedAt: input.fetchedAt ?? clock.toISOString(),
|
|
7021
7285
|
outcomes,
|
|
7022
7286
|
favorite,
|
|
7023
7287
|
liquidity: input.liquidity,
|
|
@@ -7025,8 +7289,8 @@ function buildMarketSignal(input) {
|
|
|
7025
7289
|
stale: false,
|
|
7026
7290
|
ambiguous: ambiguous2
|
|
7027
7291
|
};
|
|
7028
|
-
signal.stale = isStaleSignal(signal, { now:
|
|
7029
|
-
const sealed = sealMarketSignal(signal, { now:
|
|
7292
|
+
signal.stale = isStaleSignal(signal, { now: clock, maxAgeMs: input.maxAgeMs });
|
|
7293
|
+
const sealed = sealMarketSignal(signal, { now: clock, maxAgeMs: input.maxAgeMs });
|
|
7030
7294
|
if (sealed.kind !== "valid") {
|
|
7031
7295
|
return { ...signal, outcomes: [], favorite: void 0, stale: true, ambiguous: true };
|
|
7032
7296
|
}
|
|
@@ -8780,17 +9044,33 @@ function painterFor(cfg) {
|
|
|
8780
9044
|
return paint(cfg.color);
|
|
8781
9045
|
}
|
|
8782
9046
|
function statusToken(m, t2, c) {
|
|
9047
|
+
const word = statusWord(m, t2);
|
|
9048
|
+
switch (m.status) {
|
|
9049
|
+
case "LIVE":
|
|
9050
|
+
return c.green(word);
|
|
9051
|
+
case "HT":
|
|
9052
|
+
return c.yellow(word);
|
|
9053
|
+
case "FT":
|
|
9054
|
+
return c.gray(word);
|
|
9055
|
+
case "POSTPONED":
|
|
9056
|
+
case "CANCELLED":
|
|
9057
|
+
return c.red(word);
|
|
9058
|
+
default:
|
|
9059
|
+
return "";
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
function statusWord(m, t2) {
|
|
8783
9063
|
switch (m.status) {
|
|
8784
9064
|
case "LIVE":
|
|
8785
|
-
return
|
|
9065
|
+
return m.minute ? `${m.minute}'` : t2("status.live");
|
|
8786
9066
|
case "HT":
|
|
8787
|
-
return
|
|
9067
|
+
return t2("status.ht");
|
|
8788
9068
|
case "FT":
|
|
8789
|
-
return
|
|
9069
|
+
return t2("status.ft");
|
|
8790
9070
|
case "POSTPONED":
|
|
8791
|
-
return
|
|
9071
|
+
return t2("status.postponed");
|
|
8792
9072
|
case "CANCELLED":
|
|
8793
|
-
return
|
|
9073
|
+
return t2("status.cancelled");
|
|
8794
9074
|
default:
|
|
8795
9075
|
return "";
|
|
8796
9076
|
}
|
|
@@ -9013,12 +9293,60 @@ function writeState(state, now = stampOrNow(state.updatedAt)) {
|
|
|
9013
9293
|
const until = envelopeDeadline(replaced, now);
|
|
9014
9294
|
if (until !== void 0 && !writeBackoffNote(state.source, state.competition, until, now)) return false;
|
|
9015
9295
|
}
|
|
9016
|
-
writeFileAtomic(
|
|
9017
|
-
cachePath(state.source, state.competition),
|
|
9018
|
-
JSON.stringify({ ...state, version: CACHE_VERSION })
|
|
9019
|
-
);
|
|
9296
|
+
writeFileAtomic(cachePath(state.source, state.competition), boundedSnapshot(withoutEvents(state)));
|
|
9020
9297
|
return true;
|
|
9021
9298
|
}
|
|
9299
|
+
function boundedSnapshot(state) {
|
|
9300
|
+
const serialize = (s) => JSON.stringify({ ...s, version: CACHE_VERSION });
|
|
9301
|
+
let text = serialize(state);
|
|
9302
|
+
if (Buffer.byteLength(text, "utf8") <= MAX_STATE_BYTES) return text;
|
|
9303
|
+
let next = state;
|
|
9304
|
+
const play = state.play;
|
|
9305
|
+
if (play && typeof play === "object" && !Array.isArray(play) && Array.isArray(play.finished)) {
|
|
9306
|
+
next = { ...next, play: { ...play, finished: [] } };
|
|
9307
|
+
text = serialize(next);
|
|
9308
|
+
if (Buffer.byteLength(text, "utf8") <= MAX_STATE_BYTES) return text;
|
|
9309
|
+
}
|
|
9310
|
+
if (Array.isArray(next.live)) {
|
|
9311
|
+
next = { ...next, live: next.live.map(dropLastGoal) };
|
|
9312
|
+
text = serialize(next);
|
|
9313
|
+
if (Buffer.byteLength(text, "utf8") <= MAX_STATE_BYTES) return text;
|
|
9314
|
+
}
|
|
9315
|
+
if ("play" in next) {
|
|
9316
|
+
const { play: _play, ...withoutPlay } = next;
|
|
9317
|
+
text = serialize(withoutPlay);
|
|
9318
|
+
}
|
|
9319
|
+
return text;
|
|
9320
|
+
}
|
|
9321
|
+
function dropLastGoal(m) {
|
|
9322
|
+
if (!m || typeof m !== "object" || !("lastGoal" in m)) return m;
|
|
9323
|
+
const copy = { ...m };
|
|
9324
|
+
delete copy.lastGoal;
|
|
9325
|
+
return copy;
|
|
9326
|
+
}
|
|
9327
|
+
function withoutEvents(state) {
|
|
9328
|
+
const strip = (list) => Array.isArray(list) ? list.map(dropEvents) : list;
|
|
9329
|
+
const out2 = { ...state, live: strip(state.live) };
|
|
9330
|
+
if (state.fixtures !== void 0) out2.fixtures = strip(state.fixtures);
|
|
9331
|
+
if (state.schedule?.fixtures !== void 0) out2.schedule = { ...state.schedule, fixtures: strip(state.schedule.fixtures) };
|
|
9332
|
+
const play = state.play;
|
|
9333
|
+
if (play && typeof play === "object" && !Array.isArray(play) && Array.isArray(play.finished)) {
|
|
9334
|
+
const p = play;
|
|
9335
|
+
out2.play = {
|
|
9336
|
+
...p,
|
|
9337
|
+
finished: p.finished.map(
|
|
9338
|
+
(e) => e && typeof e === "object" && !Array.isArray(e) ? { ...e, match: dropEvents(e.match) } : e
|
|
9339
|
+
)
|
|
9340
|
+
};
|
|
9341
|
+
}
|
|
9342
|
+
return out2;
|
|
9343
|
+
}
|
|
9344
|
+
function dropEvents(m) {
|
|
9345
|
+
if (!m || typeof m !== "object" || !("events" in m)) return m;
|
|
9346
|
+
const copy = { ...m };
|
|
9347
|
+
delete copy.events;
|
|
9348
|
+
return copy;
|
|
9349
|
+
}
|
|
9022
9350
|
function stampOrNow(stamp) {
|
|
9023
9351
|
return validStamp(stamp) ? Date.parse(stamp) : Date.now();
|
|
9024
9352
|
}
|
|
@@ -9242,7 +9570,7 @@ function isUsableSignal(s) {
|
|
|
9242
9570
|
if (!s.favorite) return false;
|
|
9243
9571
|
return hasSaneDistribution(s.outcomes);
|
|
9244
9572
|
}
|
|
9245
|
-
function readMarketCache(source, competition, now = Date.now()) {
|
|
9573
|
+
function readMarketCache(source, competition, now = Date.now(), requestNow) {
|
|
9246
9574
|
const signals = /* @__PURE__ */ new Map();
|
|
9247
9575
|
const checked = /* @__PURE__ */ new Set();
|
|
9248
9576
|
const file = readFile();
|
|
@@ -9269,7 +9597,7 @@ function readMarketCache(source, competition, now = Date.now()) {
|
|
|
9269
9597
|
checked.add(id);
|
|
9270
9598
|
continue;
|
|
9271
9599
|
}
|
|
9272
|
-
const sealed = parseCachedMarketSignal(entry.signal, { now:
|
|
9600
|
+
const sealed = parseCachedMarketSignal(entry.signal, { now: requestNow });
|
|
9273
9601
|
if (sealed.kind !== "valid") continue;
|
|
9274
9602
|
const clean = sealed.value;
|
|
9275
9603
|
if (clean.matchId !== id || clean.source !== source) continue;
|
|
@@ -9367,6 +9695,104 @@ function copyToClipboard(text, platform = process.platform) {
|
|
|
9367
9695
|
return false;
|
|
9368
9696
|
}
|
|
9369
9697
|
|
|
9698
|
+
// src/play.ts
|
|
9699
|
+
var FINISHED_TTL_MS = 10 * 6e4;
|
|
9700
|
+
var SEEN_TTL_MS = 6 * 60 * 6e4;
|
|
9701
|
+
var MAX_PLAY = 64;
|
|
9702
|
+
var NO_READ = Object.freeze({ whole: false, matches: [], finished: [], finishedTruncated: false });
|
|
9703
|
+
var ID = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
9704
|
+
function believedAt(at, now, ttl) {
|
|
9705
|
+
if (!validStamp(at)) return false;
|
|
9706
|
+
const ms = Date.parse(at);
|
|
9707
|
+
return ms <= now && now - ms < ttl;
|
|
9708
|
+
}
|
|
9709
|
+
function playRecordIds(play, now) {
|
|
9710
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9711
|
+
if (!play || typeof play !== "object" || Array.isArray(play)) return ids;
|
|
9712
|
+
const p = play;
|
|
9713
|
+
for (const raw of takeBounded(p.seen, MAX_PLAY)) {
|
|
9714
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9715
|
+
const { id, at } = raw;
|
|
9716
|
+
if (typeof id === "string" && ID.test(id) && believedAt(at, now, SEEN_TTL_MS)) ids.add(id);
|
|
9717
|
+
}
|
|
9718
|
+
for (const raw of takeBounded(p.finished, MAX_PLAY)) {
|
|
9719
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9720
|
+
const { match, at } = raw;
|
|
9721
|
+
if (!match || typeof match !== "object" || !believedAt(at, now, FINISHED_TTL_MS)) continue;
|
|
9722
|
+
const id = match.id;
|
|
9723
|
+
if (typeof id === "string" && ID.test(id)) ids.add(id);
|
|
9724
|
+
}
|
|
9725
|
+
return ids;
|
|
9726
|
+
}
|
|
9727
|
+
function believedPlay(prev, now, kind) {
|
|
9728
|
+
const out2 = { seen: [], finished: [] };
|
|
9729
|
+
if (!prev || typeof prev !== "object" || Array.isArray(prev)) return out2;
|
|
9730
|
+
const p = prev;
|
|
9731
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9732
|
+
for (const raw of takeBounded(p.seen, MAX_PLAY)) {
|
|
9733
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9734
|
+
const { id, at } = raw;
|
|
9735
|
+
if (typeof id !== "string" || !ID.test(id) || ids.has(id) || !believedAt(at, now, SEEN_TTL_MS)) continue;
|
|
9736
|
+
ids.add(id);
|
|
9737
|
+
out2.seen.push({ id, at });
|
|
9738
|
+
}
|
|
9739
|
+
const done = /* @__PURE__ */ new Set();
|
|
9740
|
+
for (const raw of takeBounded(p.finished, MAX_PLAY)) {
|
|
9741
|
+
if (!raw || typeof raw !== "object") continue;
|
|
9742
|
+
const { match, at } = raw;
|
|
9743
|
+
if (!believedAt(at, now, FINISHED_TTL_MS)) continue;
|
|
9744
|
+
const sealed = parsedValue(parseCachedMatch(match, { events: false, teamKind: kind }));
|
|
9745
|
+
if (sealed?.status !== "FT" || !sealed.score || done.has(sealed.id)) continue;
|
|
9746
|
+
done.add(sealed.id);
|
|
9747
|
+
out2.finished.push({ match: sealed, at });
|
|
9748
|
+
}
|
|
9749
|
+
return out2;
|
|
9750
|
+
}
|
|
9751
|
+
function advancePlay(prev, read, now, kind = "club") {
|
|
9752
|
+
const nowMs = now.getTime();
|
|
9753
|
+
const believed = believedPlay(prev, nowMs, kind);
|
|
9754
|
+
if (!read.whole) return believed;
|
|
9755
|
+
const at = now.toISOString();
|
|
9756
|
+
const inPlay = /* @__PURE__ */ new Set();
|
|
9757
|
+
const seenNow = [];
|
|
9758
|
+
for (const m of read.matches) {
|
|
9759
|
+
if (typeof m?.id !== "string" || inPlay.has(m.id)) continue;
|
|
9760
|
+
inPlay.add(m.id);
|
|
9761
|
+
seenNow.push({ id: m.id, at });
|
|
9762
|
+
}
|
|
9763
|
+
const finishedRead = /* @__PURE__ */ new Map();
|
|
9764
|
+
for (const m of read.finished) {
|
|
9765
|
+
if (typeof m?.id === "string" && !finishedRead.has(m.id)) finishedRead.set(m.id, m);
|
|
9766
|
+
}
|
|
9767
|
+
const finished = [];
|
|
9768
|
+
const held = /* @__PURE__ */ new Set();
|
|
9769
|
+
for (const e of believed.finished) {
|
|
9770
|
+
const id = e.match.id;
|
|
9771
|
+
if (inPlay.has(id)) continue;
|
|
9772
|
+
const again = finishedRead.get(id);
|
|
9773
|
+
if (again) finished.push({ match: again, at: e.at });
|
|
9774
|
+
else if (read.finishedTruncated) finished.push(e);
|
|
9775
|
+
else continue;
|
|
9776
|
+
held.add(id);
|
|
9777
|
+
}
|
|
9778
|
+
const keptSeen = [];
|
|
9779
|
+
for (const e of believed.seen) {
|
|
9780
|
+
if (inPlay.has(e.id) || held.has(e.id)) continue;
|
|
9781
|
+
const done = finishedRead.get(e.id);
|
|
9782
|
+
if (done) {
|
|
9783
|
+
finished.push({ match: done, at });
|
|
9784
|
+
held.add(e.id);
|
|
9785
|
+
} else if (read.finishedTruncated) {
|
|
9786
|
+
keptSeen.push(e);
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
const seen = [...keptSeen, ...seenNow];
|
|
9790
|
+
return {
|
|
9791
|
+
seen: seen.length > MAX_PLAY ? seen.slice(seen.length - MAX_PLAY) : seen,
|
|
9792
|
+
finished: finished.length > MAX_PLAY ? finished.slice(finished.length - MAX_PLAY) : finished
|
|
9793
|
+
};
|
|
9794
|
+
}
|
|
9795
|
+
|
|
9370
9796
|
// src/scheduleSlice.ts
|
|
9371
9797
|
var SCHEDULE_DISPLAY_MAX = 64;
|
|
9372
9798
|
var RELEVANT_BACK_MS = 6 * 60 * 6e4;
|
|
@@ -9496,8 +9922,17 @@ function isMatchShaped(m) {
|
|
|
9496
9922
|
const x = m;
|
|
9497
9923
|
return !!x && typeof x === "object" && typeof x.id === "string" && typeof x.kickoff === "string" && !!x.home?.code && !!x.away?.code;
|
|
9498
9924
|
}
|
|
9499
|
-
|
|
9500
|
-
|
|
9925
|
+
var KICKOFF_DUE_MS = 30 * 6e4;
|
|
9926
|
+
function isDue(m, nowMs, played) {
|
|
9927
|
+
if (m.status !== "SCHEDULED" || played.has(m.id)) return false;
|
|
9928
|
+
const k = Date.parse(m.kickoff);
|
|
9929
|
+
return k <= nowMs && nowMs <= k + KICKOFF_DUE_MS;
|
|
9930
|
+
}
|
|
9931
|
+
function isAhead(m, nowMs) {
|
|
9932
|
+
return Date.parse(m.kickoff) > nowMs && isUpcoming(m, new Date(nowMs));
|
|
9933
|
+
}
|
|
9934
|
+
function countdownCandidates(nowMs, fixtures = allFixtures(), played) {
|
|
9935
|
+
return [...fixtures].sort(byKickoff).filter((m) => isResolvedFixture(m) && (isAhead(m, nowMs) || isDue(m, nowMs, played)));
|
|
9501
9936
|
}
|
|
9502
9937
|
function defaultTeamKind(defaultCompetition) {
|
|
9503
9938
|
return defaultCompetition === false ? "club" : "nation";
|
|
@@ -9505,14 +9940,38 @@ function defaultTeamKind(defaultCompetition) {
|
|
|
9505
9940
|
function teamTok(t2, flags) {
|
|
9506
9941
|
return flags && t2.flag ? t2.flag : t2.code;
|
|
9507
9942
|
}
|
|
9508
|
-
function matchSegment(m, compact, flags) {
|
|
9509
|
-
const
|
|
9510
|
-
|
|
9511
|
-
|
|
9943
|
+
function matchSegment(m, compact, flags, lang) {
|
|
9944
|
+
const ht = lang === void 0 ? "HT" : t(lang, "status.ht");
|
|
9945
|
+
const live = lang === void 0 ? "LIVE" : t(lang, "status.live");
|
|
9946
|
+
const minute = m.status === "HT" ? ht : m.minute ? `${m.minute}'` : live;
|
|
9947
|
+
const home = !flags ? m.home.code : compact ? teamTok(m.home, true) : withFlag(m.home.code, m.home.flag, "home");
|
|
9948
|
+
const away = !flags ? m.away.code : compact ? teamTok(m.away, true) : withFlag(m.away.code, m.away.flag, "away");
|
|
9949
|
+
const score = scoreline(m);
|
|
9950
|
+
return { text: `${home} ${score} ${away} ${minute}`, score: [home.length + 1, home.length + 1 + score.length] };
|
|
9951
|
+
}
|
|
9952
|
+
var plain = (text) => ({ text, strong: [] });
|
|
9953
|
+
function sharedPrefix(a, b) {
|
|
9954
|
+
const n = Math.min(a.length, b.length);
|
|
9955
|
+
let i = 0;
|
|
9956
|
+
while (i < n && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
9957
|
+
return i;
|
|
9958
|
+
}
|
|
9959
|
+
function keptRanges(from, to, ranges) {
|
|
9960
|
+
if (from === to) return [...ranges];
|
|
9961
|
+
const kept = sharedPrefix(from, to);
|
|
9962
|
+
return ranges.filter(([, end]) => end <= kept);
|
|
9963
|
+
}
|
|
9964
|
+
function partsOf(line, strong) {
|
|
9965
|
+
const parts = [];
|
|
9966
|
+
let at = 0;
|
|
9967
|
+
for (const [start, end] of strong) {
|
|
9968
|
+
if (start < at || end <= start || end > line.length) return [{ text: line }];
|
|
9969
|
+
if (start > at) parts.push({ text: line.slice(at, start) });
|
|
9970
|
+
parts.push({ text: line.slice(start, end), style: "strong" });
|
|
9971
|
+
at = end;
|
|
9512
9972
|
}
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
return `${home} ${scoreline(m)} ${away} ${minute}`;
|
|
9973
|
+
if (at < line.length || parts.length === 0) parts.push({ text: line.slice(at) });
|
|
9974
|
+
return parts.map((p) => p.text).join("") === line ? parts : [{ text: line }];
|
|
9516
9975
|
}
|
|
9517
9976
|
var MAX_LIVE_CONSIDERED = 64;
|
|
9518
9977
|
var MAX_LIVE_EXAMINED = 512;
|
|
@@ -9550,13 +10009,17 @@ function sealFixtures(raw, kind) {
|
|
|
9550
10009
|
};
|
|
9551
10010
|
}
|
|
9552
10011
|
function liveMatchesFromCache(state, nowMs, kind) {
|
|
10012
|
+
return liveRecordsFromCache(state, nowMs, kind).list;
|
|
10013
|
+
}
|
|
10014
|
+
function liveRecordsFromCache(state, nowMs, kind) {
|
|
9553
10015
|
const fresh = state && ageMs(state, nowMs) < DISPLAY_STALE_MS;
|
|
9554
10016
|
const rawLive = fresh ? state?.live : [];
|
|
9555
10017
|
if (!Array.isArray(rawLive)) {
|
|
9556
|
-
return { items: [], total: 0, shown: 0, truncated: false, complete: false };
|
|
10018
|
+
return { list: { items: [], total: 0, shown: 0, truncated: false, complete: false }, sources: [] };
|
|
9557
10019
|
}
|
|
9558
10020
|
const liveArr = rawLive;
|
|
9559
10021
|
const out2 = [];
|
|
10022
|
+
const sources = [];
|
|
9560
10023
|
let inspected = 0;
|
|
9561
10024
|
let readable = true;
|
|
9562
10025
|
for (let i = 0; i < liveArr.length; i++) {
|
|
@@ -9573,19 +10036,24 @@ function liveMatchesFromCache(state, nowMs, kind) {
|
|
|
9573
10036
|
continue;
|
|
9574
10037
|
}
|
|
9575
10038
|
const sealed = parsedValue(parseCachedMatch(m, { events: false, teamKind: kind }));
|
|
9576
|
-
if (sealed)
|
|
9577
|
-
|
|
10039
|
+
if (sealed) {
|
|
10040
|
+
out2.push(sealed);
|
|
10041
|
+
sources.push(raw);
|
|
10042
|
+
} else readable = false;
|
|
9578
10043
|
}
|
|
9579
10044
|
const exhausted = inspected === liveArr.length;
|
|
9580
10045
|
const complete = exhausted && readable;
|
|
9581
10046
|
return {
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
10047
|
+
list: {
|
|
10048
|
+
items: out2,
|
|
10049
|
+
total: out2.length,
|
|
10050
|
+
shown: out2.length,
|
|
10051
|
+
truncated: !exhausted,
|
|
10052
|
+
// False when we stopped early or a record was unreadable. In either case an
|
|
10053
|
+
// empty list must not render as the authoritative "nothing is on".
|
|
10054
|
+
complete
|
|
10055
|
+
},
|
|
10056
|
+
sources
|
|
9589
10057
|
};
|
|
9590
10058
|
}
|
|
9591
10059
|
var DEFAULT_MAX_SEGMENTS = 8;
|
|
@@ -9593,11 +10061,13 @@ var MAX_LINE_COLUMNS = 200;
|
|
|
9593
10061
|
function renderPrompt(state, opts = {}) {
|
|
9594
10062
|
return promptLine(state, opts).line;
|
|
9595
10063
|
}
|
|
9596
|
-
function promptLine(state, opts = {}) {
|
|
9597
|
-
const
|
|
9598
|
-
|
|
10064
|
+
function promptLine(state, opts = {}, played) {
|
|
10065
|
+
const rendered = renderPromptLine(state, opts, played);
|
|
10066
|
+
const unfitted = rendered.text;
|
|
10067
|
+
const line = truncateVisible(unfitted, lineColumns(opts));
|
|
10068
|
+
return { line, unfitted, parts: partsOf(line, keptRanges(unfitted, line, rendered.strong)) };
|
|
9599
10069
|
}
|
|
9600
|
-
function renderPromptLine(state, opts = {}) {
|
|
10070
|
+
function renderPromptLine(state, opts = {}, played) {
|
|
9601
10071
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
9602
10072
|
const nowMs = now.getTime();
|
|
9603
10073
|
const defaultCompetition = opts.defaultCompetition ?? true;
|
|
@@ -9615,23 +10085,27 @@ function renderPromptLine(state, opts = {}) {
|
|
|
9615
10085
|
const max = picked ? 1 : opts.max && opts.max > 0 ? Math.min(opts.max, DEFAULT_MAX_SEGMENTS) : DEFAULT_MAX_SEGMENTS;
|
|
9616
10086
|
const shown = ordered.slice(0, max);
|
|
9617
10087
|
const overflow = live.length - shown.length;
|
|
9618
|
-
return fitLiveLine(shown.map((m) => matchSegment(m, compact, flags)), overflow, liveList.complete, width);
|
|
10088
|
+
return fitLiveLine(shown.map((m) => matchSegment(m, compact, flags, opts.lang)), overflow, liveList.complete, width);
|
|
9619
10089
|
}
|
|
9620
10090
|
const win = syncingWindow(state, nowMs, liveWhole(liveList.complete, state), defaultCompetition, cachedFixtures, schedule, pick2);
|
|
9621
10091
|
if (win) {
|
|
9622
10092
|
const first = win[0];
|
|
9623
10093
|
const matchup = first && isResolvedFixture(first) ? `${teamTok(first.home, flags)} vs ${teamTok(first.away, flags)} ` : "";
|
|
9624
10094
|
const more = win.length - 1;
|
|
9625
|
-
|
|
10095
|
+
const syncing = opts.lang === void 0 ? "live \xB7 syncing\u2026" : t(opts.lang, "ambient.syncing");
|
|
10096
|
+
return plain(fitWithMarker(`\u26BD ${matchup}${syncing}`, more > 0 ? ` +${more}` : "", width));
|
|
9626
10097
|
}
|
|
9627
|
-
const next = countdownFixture(nowMs, schedule, pick2);
|
|
10098
|
+
const next = countdownFixture(nowMs, schedule, pick2, played ?? playRecordIds(state?.play, nowMs));
|
|
9628
10099
|
if (next) {
|
|
9629
|
-
|
|
10100
|
+
const matchup = `${teamTok(next.home, flags)} vs ${teamTok(next.away, flags)}`;
|
|
10101
|
+
if (opts.lang !== void 0) return plain(`\u26BD ${matchup} ${countdownPhrase(opts.lang, next.kickoff, now)}`);
|
|
10102
|
+
const left = countdown(next.kickoff, now);
|
|
10103
|
+
return plain(left === COUNTDOWN_NOW ? `\u26BD ${matchup} ${COUNTDOWN_NOW}` : `\u26BD ${matchup} in ${left}`);
|
|
9630
10104
|
}
|
|
9631
10105
|
if (defaultCompetition && isTournamentWindowOver(nowMs, schedule)) {
|
|
9632
|
-
return TOURNAMENT_COMPLETE_LINE;
|
|
10106
|
+
return plain(TOURNAMENT_COMPLETE_LINE);
|
|
9633
10107
|
}
|
|
9634
|
-
return NOTHING_KNOWN_LINE;
|
|
10108
|
+
return plain(NOTHING_KNOWN_LINE);
|
|
9635
10109
|
}
|
|
9636
10110
|
function lineColumns(opts) {
|
|
9637
10111
|
const c = opts.columns;
|
|
@@ -9645,10 +10119,18 @@ function fitWithMarker(body, marker, width) {
|
|
|
9645
10119
|
}
|
|
9646
10120
|
function fitLiveLine(segments, overflow, complete, width) {
|
|
9647
10121
|
const markerFor = (dropped) => !complete ? " +more" : overflow + dropped > 0 ? ` +${overflow + dropped}` : "";
|
|
9648
|
-
const bodyOf = (kept2) => "\u26BD " + segments.slice(0, kept2).join(" \xB7 ");
|
|
10122
|
+
const bodyOf = (kept2) => "\u26BD " + segments.slice(0, kept2).map((g) => g.text).join(" \xB7 ");
|
|
9649
10123
|
let kept = segments.length;
|
|
9650
10124
|
while (kept > 1 && displayWidth(bodyOf(kept)) + displayWidth(markerFor(segments.length - kept)) > width) kept--;
|
|
9651
|
-
|
|
10125
|
+
const body = bodyOf(kept);
|
|
10126
|
+
const text = fitWithMarker(body, markerFor(segments.length - kept), width);
|
|
10127
|
+
const ranges = [];
|
|
10128
|
+
let offset = "\u26BD ".length;
|
|
10129
|
+
for (const g of segments.slice(0, kept)) {
|
|
10130
|
+
ranges.push([offset + g.score[0], offset + g.score[1]]);
|
|
10131
|
+
offset += g.text.length + " \xB7 ".length;
|
|
10132
|
+
}
|
|
10133
|
+
return { text, strong: keptRanges(body, text, ranges).filter(([, end]) => end <= text.length) };
|
|
9652
10134
|
}
|
|
9653
10135
|
function countdownSchedule(state, defaultCompetition, kind) {
|
|
9654
10136
|
const cachedFixtureList = sealFixtures(defaultCompetition ? state?.fixtures : state?.schedule?.fixtures, kind);
|
|
@@ -9676,8 +10158,8 @@ function syncingWindow(state, nowMs, liveComplete, defaultCompetition, cachedFix
|
|
|
9676
10158
|
const win = pickAmbientMatch(fixturesInLiveWindow(nowMs, schedule), pick2);
|
|
9677
10159
|
return win.length > 0 ? win : void 0;
|
|
9678
10160
|
}
|
|
9679
|
-
function countdownFixture(nowMs, schedule, pick2) {
|
|
9680
|
-
return pickAmbientMatch(
|
|
10161
|
+
function countdownFixture(nowMs, schedule, pick2, played) {
|
|
10162
|
+
return pickAmbientMatch(countdownCandidates(nowMs, schedule, played), pick2)[0];
|
|
9681
10163
|
}
|
|
9682
10164
|
|
|
9683
10165
|
// src/hook.ts
|
|
@@ -9695,9 +10177,12 @@ function rosterPinned(t2, pin) {
|
|
|
9695
10177
|
const { team } = lookupTeam(t2.code);
|
|
9696
10178
|
return team ? { ...t2, name: team.name, flag: team.flag } : t2;
|
|
9697
10179
|
}
|
|
10180
|
+
function hookNames(m, pin) {
|
|
10181
|
+
return { home: rosterPinned(m.home, pin).name, away: rosterPinned(m.away, pin).name };
|
|
10182
|
+
}
|
|
9698
10183
|
function hookLine(m, opts) {
|
|
9699
10184
|
const { flags, pin } = opts;
|
|
9700
|
-
const minute = m.status === "HT" ? "half-time" : m.minute ? `${m.minute}'` : "live";
|
|
10185
|
+
const minute = m.status === "FT" ? "FT" : m.status === "HT" ? "half-time" : m.minute ? `${m.minute}'` : "live";
|
|
9701
10186
|
const h = rosterPinned(m.home, pin);
|
|
9702
10187
|
const a = rosterPinned(m.away, pin);
|
|
9703
10188
|
const home = flags ? withFlag(h.name, h.flag, "home") : h.name;
|
|
@@ -9722,36 +10207,85 @@ ${lines}`, more);
|
|
|
9722
10207
|
}
|
|
9723
10208
|
|
|
9724
10209
|
// src/ambient.ts
|
|
10210
|
+
function pinnedSideOf(m, pin) {
|
|
10211
|
+
if (pin === void 0) return null;
|
|
10212
|
+
if (isPinnedSide(m.home, pin)) return "home";
|
|
10213
|
+
return isPinnedSide(m.away, pin) ? "away" : null;
|
|
10214
|
+
}
|
|
10215
|
+
var SCORING2 = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN"]);
|
|
10216
|
+
function lastGoalOf(m, raw) {
|
|
10217
|
+
if (raw === void 0 || raw === null) return null;
|
|
10218
|
+
const e = sealEvent(raw);
|
|
10219
|
+
if (!e || !SCORING2.has(e.type)) return null;
|
|
10220
|
+
if (e.teamCode !== (e.side === "home" ? m.home.code : m.away.code)) return null;
|
|
10221
|
+
return (m.score?.[e.side] ?? 0) >= 1 ? e : null;
|
|
10222
|
+
}
|
|
10223
|
+
function finishedFromCache(state, nowMs, kind, live) {
|
|
10224
|
+
if (state?.liveComplete !== true) return [];
|
|
10225
|
+
const play = state.play;
|
|
10226
|
+
if (!play || typeof play !== "object" || Array.isArray(play)) return [];
|
|
10227
|
+
const out2 = [];
|
|
10228
|
+
const ids = /* @__PURE__ */ new Set();
|
|
10229
|
+
for (const raw of takeBounded(play.finished, MAX_PLAY)) {
|
|
10230
|
+
if (!raw || typeof raw !== "object") continue;
|
|
10231
|
+
const { match, at } = raw;
|
|
10232
|
+
if (!validStamp(at)) continue;
|
|
10233
|
+
const atMs = Date.parse(at);
|
|
10234
|
+
if (atMs > nowMs || nowMs - atMs >= FINISHED_TTL_MS) continue;
|
|
10235
|
+
const sealed = parsedValue(parseCachedMatch(match, { events: false, teamKind: kind }));
|
|
10236
|
+
if (sealed?.status !== "FT" || !sealed.score || live.has(sealed.id) || ids.has(sealed.id)) continue;
|
|
10237
|
+
ids.add(sealed.id);
|
|
10238
|
+
out2.push(sealed);
|
|
10239
|
+
}
|
|
10240
|
+
return out2;
|
|
10241
|
+
}
|
|
9725
10242
|
function ambientView(state, opts = {}) {
|
|
9726
10243
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
9727
10244
|
const nowMs = now.getTime();
|
|
9728
10245
|
const defaultCompetition = opts.defaultCompetition ?? true;
|
|
9729
10246
|
const kind = opts.teamKind ?? defaultTeamKind(opts.defaultCompetition);
|
|
9730
10247
|
const pin = opts.pin ?? (opts.pick && "team" in opts.pick ? opts.pick.team : void 0);
|
|
9731
|
-
const
|
|
10248
|
+
const { lang, ...rest } = opts;
|
|
10249
|
+
const read = { ...rest, now };
|
|
9732
10250
|
const hookOpts = { flags: opts.flags ?? true, pin: defaultCompetition };
|
|
9733
|
-
const list =
|
|
10251
|
+
const { list, sources } = liveRecordsFromCache(state, nowMs, kind);
|
|
10252
|
+
const lastGoals = /* @__PURE__ */ new Map();
|
|
10253
|
+
list.items.forEach((m, i) => {
|
|
10254
|
+
lastGoals.set(m, lastGoalOf(m, sources[i]?.lastGoal));
|
|
10255
|
+
});
|
|
9734
10256
|
const items = pickAmbientMatch(list.items, opts.pick).map((m) => ({
|
|
9735
10257
|
...m,
|
|
9736
10258
|
picked: isPicked(m, opts.pick),
|
|
9737
10259
|
pinned: pin !== void 0 && (isPinnedSide(m.home, pin) || isPinnedSide(m.away, pin)),
|
|
9738
|
-
|
|
10260
|
+
pinnedSide: pinnedSideOf(m, pin),
|
|
10261
|
+
names: hookNames(m, defaultCompetition),
|
|
10262
|
+
line: hookLine(m, hookOpts),
|
|
10263
|
+
lastGoal: lastGoals.get(m) ?? null
|
|
9739
10264
|
}));
|
|
9740
|
-
const
|
|
10265
|
+
const played = playRecordIds(state?.play, nowMs);
|
|
10266
|
+
const prompt = promptLine(state, { ...read, lang }, played);
|
|
9741
10267
|
const context = renderHook(state, read);
|
|
9742
10268
|
const { cachedFixtures, schedule } = countdownSchedule(state, defaultCompetition, kind);
|
|
9743
10269
|
const age = stampAgeMs(state?.updatedAt, nowMs);
|
|
9744
10270
|
const stamp = state && Number.isFinite(age) ? Date.parse(state.updatedAt) : void 0;
|
|
9745
10271
|
const staleAt = stamp === void 0 ? void 0 : new Date(stamp + DISPLAY_STALE_MS);
|
|
10272
|
+
const finished = finishedFromCache(
|
|
10273
|
+
state,
|
|
10274
|
+
nowMs,
|
|
10275
|
+
kind,
|
|
10276
|
+
new Set(list.items.map((m) => m.id))
|
|
10277
|
+
).map((m) => ({ ...m, pinnedSide: pinnedSideOf(m, pin), names: hookNames(m, defaultCompetition), line: hookLine(m, hookOpts) }));
|
|
9746
10278
|
const syncing = list.items.length === 0 && syncingWindow(state, nowMs, liveWhole(list.complete, state), defaultCompetition, cachedFixtures, schedule, opts.pick) !== void 0;
|
|
9747
10279
|
return {
|
|
9748
10280
|
line: prompt.line,
|
|
10281
|
+
lineParts: prompt.parts,
|
|
9749
10282
|
idle: prompt.unfitted === TOURNAMENT_COMPLETE_LINE,
|
|
9750
10283
|
empty: prompt.unfitted === NOTHING_KNOWN_LINE,
|
|
9751
10284
|
context: context === "" ? null : context,
|
|
9752
10285
|
live: { items, total: list.total, shown: list.shown, truncated: list.truncated, complete: list.complete },
|
|
10286
|
+
finished,
|
|
9753
10287
|
current: stamp !== void 0 && age < DISPLAY_STALE_MS && state?.degraded !== true && state?.liveComplete === true && !syncing,
|
|
9754
|
-
next: countdownFixture(nowMs, schedule, opts.pick) ?? null,
|
|
10288
|
+
next: countdownFixture(nowMs, schedule, opts.pick, played) ?? null,
|
|
9755
10289
|
degraded: state?.degraded === true,
|
|
9756
10290
|
source: typeof state?.source === "string" ? state.source : null,
|
|
9757
10291
|
updatedAt: stamp === void 0 ? null : new Date(stamp).toISOString(),
|
|
@@ -9783,6 +10317,16 @@ function inKnockoutPhase(nowMs, competition) {
|
|
|
9783
10317
|
return !!next && isKnockoutStage(next.stage);
|
|
9784
10318
|
}
|
|
9785
10319
|
var NEVER = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
10320
|
+
function carriedPlay(base, now, kind) {
|
|
10321
|
+
return advancePlay(base?.play, NO_READ, now, kind);
|
|
10322
|
+
}
|
|
10323
|
+
function liveWithLastGoal(matches) {
|
|
10324
|
+
return matches.map((m) => ({ ...m, lastGoal: provenLastGoal(m) }));
|
|
10325
|
+
}
|
|
10326
|
+
function playAfterRead(play, r, at, kind) {
|
|
10327
|
+
const whole = !r.degraded && r.complete === true;
|
|
10328
|
+
return advancePlay(play, { whole, matches: r.matches, finished: r.finished ?? [], finishedTruncated: r.finishedTruncated === true }, at, kind);
|
|
10329
|
+
}
|
|
9786
10330
|
function liveAdapter(source, competition, now) {
|
|
9787
10331
|
return makeAdapter(source, { competition, enrichGroups: false, now });
|
|
9788
10332
|
}
|
|
@@ -9808,7 +10352,9 @@ async function runRefresh(opts) {
|
|
|
9808
10352
|
degraded: true,
|
|
9809
10353
|
// no live provider served this scope — never claim otherwise
|
|
9810
10354
|
source,
|
|
9811
|
-
competition
|
|
10355
|
+
competition,
|
|
10356
|
+
play: { seen: [], finished: [] }
|
|
10357
|
+
// nothing was read, and no snapshot to carry one from
|
|
9812
10358
|
},
|
|
9813
10359
|
idle
|
|
9814
10360
|
);
|
|
@@ -9843,7 +10389,9 @@ async function runRefresh(opts) {
|
|
|
9843
10389
|
degraded: until !== void 0,
|
|
9844
10390
|
source,
|
|
9845
10391
|
competition,
|
|
9846
|
-
...until !== void 0 ? { backoffUntil: new Date(until).toISOString() } : {}
|
|
10392
|
+
...until !== void 0 ? { backoffUntil: new Date(until).toISOString() } : {},
|
|
10393
|
+
play: { seen: [], finished: [] }
|
|
10394
|
+
// nothing was read, and no snapshot to carry one from
|
|
9847
10395
|
},
|
|
9848
10396
|
idle,
|
|
9849
10397
|
nowMs
|
|
@@ -9885,6 +10433,8 @@ async function runRefresh(opts) {
|
|
|
9885
10433
|
let liveComplete = base ? base.liveComplete : false;
|
|
9886
10434
|
let degraded = base?.degraded ?? false;
|
|
9887
10435
|
let updatedAt = base?.updatedAt ?? now.toISOString();
|
|
10436
|
+
const kind = teamKind(competition);
|
|
10437
|
+
let play = carriedPlay(base, now, kind);
|
|
9888
10438
|
let fixtures = base?.fixtures;
|
|
9889
10439
|
let fixturesUpdatedAt = base?.fixturesUpdatedAt;
|
|
9890
10440
|
let fixturesAttemptedAt = base?.fixturesAttemptedAt;
|
|
@@ -9895,10 +10445,11 @@ async function runRefresh(opts) {
|
|
|
9895
10445
|
const adapter = liveAdapter(source, competition, clock);
|
|
9896
10446
|
if (needLive) {
|
|
9897
10447
|
try {
|
|
9898
|
-
const r = await getLiveRead(adapter, now);
|
|
9899
|
-
live = r.matches;
|
|
10448
|
+
const r = await getLiveRead(adapter, now, play.seen.map((e) => e.id));
|
|
10449
|
+
live = liveWithLastGoal(r.matches);
|
|
9900
10450
|
liveComplete = r.complete === true;
|
|
9901
10451
|
degraded = r.degraded;
|
|
10452
|
+
play = playAfterRead(play, r, now, kind);
|
|
9902
10453
|
if (!r.degraded) season = r.season;
|
|
9903
10454
|
} catch {
|
|
9904
10455
|
liveComplete = false;
|
|
@@ -9946,7 +10497,8 @@ async function runRefresh(opts) {
|
|
|
9946
10497
|
...backoffUntil ? { backoffUntil } : {},
|
|
9947
10498
|
...season ? { season } : {},
|
|
9948
10499
|
// Stored with the slice it describes, and only while that slice exists.
|
|
9949
|
-
...fixtures && fixturesSeason ? { fixturesSeason } : {}
|
|
10500
|
+
...fixtures && fixturesSeason ? { fixturesSeason } : {},
|
|
10501
|
+
play
|
|
9950
10502
|
},
|
|
9951
10503
|
token,
|
|
9952
10504
|
// The cycle's clock, not the carried live stamp: the throttle a
|
|
@@ -10028,6 +10580,8 @@ async function refreshOffBundle(c) {
|
|
|
10028
10580
|
let liveComplete = base ? base.liveComplete : false;
|
|
10029
10581
|
let degraded = base?.degraded ?? false;
|
|
10030
10582
|
let updatedAt = base?.updatedAt ?? NEVER;
|
|
10583
|
+
const kind = teamKind(competition);
|
|
10584
|
+
let play = carriedPlay(base, now, kind);
|
|
10031
10585
|
let season = sealSeason(base?.season);
|
|
10032
10586
|
let index = view.index;
|
|
10033
10587
|
let display = [...sealFixtures(base?.schedule?.fixtures, teamKind(competition)).items];
|
|
@@ -10057,7 +10611,8 @@ async function refreshOffBundle(c) {
|
|
|
10057
10611
|
...scheduleSeason ? { season: scheduleSeason } : {},
|
|
10058
10612
|
...inPlayUntil !== void 0 ? { inPlayUntil: new Date(inPlayUntil).toISOString() } : {},
|
|
10059
10613
|
...probe ? { probe: true } : {}
|
|
10060
|
-
}
|
|
10614
|
+
},
|
|
10615
|
+
play
|
|
10061
10616
|
});
|
|
10062
10617
|
if (needDiscovery) {
|
|
10063
10618
|
const before = { attemptedAt, failures };
|
|
@@ -10096,10 +10651,11 @@ async function refreshOffBundle(c) {
|
|
|
10096
10651
|
if (ageMs(base, nowMs) >= MIN_REFRESH_MS) {
|
|
10097
10652
|
const liveAt = clock();
|
|
10098
10653
|
try {
|
|
10099
|
-
const r = await getLiveRead(adapter, now);
|
|
10100
|
-
live = r.matches;
|
|
10654
|
+
const r = await getLiveRead(adapter, now, play.seen.map((e) => e.id));
|
|
10655
|
+
live = liveWithLastGoal(r.matches);
|
|
10101
10656
|
liveComplete = r.complete === true;
|
|
10102
10657
|
degraded = r.degraded;
|
|
10658
|
+
play = playAfterRead(play, r, new Date(liveAt), kind);
|
|
10103
10659
|
if (!r.degraded) season = r.season;
|
|
10104
10660
|
if (r.matches.length > 0) inPlayUntil = raiseInPlay(inPlayUntil, r.matches, nowMs);
|
|
10105
10661
|
else if (!r.degraded && r.complete) inPlayUntil = void 0;
|
|
@@ -10484,7 +11040,8 @@ function adapterServing({ cfg, adapter, now }, competition) {
|
|
|
10484
11040
|
}
|
|
10485
11041
|
var DEFAULT_ON_MARKET_OPTS = { deadlineMs: 2e3, timeoutMs: 2500 };
|
|
10486
11042
|
var MARKETS_CMD_OPTS = { deadlineMs: 12e3, timeoutMs: 6e3 };
|
|
10487
|
-
async function marketSignalsFor(ctx, matches,
|
|
11043
|
+
async function marketSignalsFor(ctx, matches, baseOpts, now, providerFactory = makeMarketProvider) {
|
|
11044
|
+
const opts = { ...baseOpts, now };
|
|
10488
11045
|
if (ctx.marketProvider) {
|
|
10489
11046
|
const b = await getMarketSignals(ctx.marketProvider, matches, opts);
|
|
10490
11047
|
return { signals: resolvedValues(b), complete: b.complete };
|
|
@@ -10495,7 +11052,12 @@ async function marketSignalsFor(ctx, matches, opts = {}, providerFactory = makeM
|
|
|
10495
11052
|
return { signals: resolvedValues(b), complete: b.complete };
|
|
10496
11053
|
}
|
|
10497
11054
|
const competition = ctx.cfg.competition;
|
|
10498
|
-
const { signals: cached, checked: cachedIds } = readMarketCache(
|
|
11055
|
+
const { signals: cached, checked: cachedIds } = readMarketCache(
|
|
11056
|
+
"polymarket",
|
|
11057
|
+
competition,
|
|
11058
|
+
Date.now(),
|
|
11059
|
+
now
|
|
11060
|
+
);
|
|
10499
11061
|
const result = /* @__PURE__ */ new Map();
|
|
10500
11062
|
const miss = [];
|
|
10501
11063
|
for (const m of matches) {
|
|
@@ -10517,12 +11079,11 @@ async function marketSignalsFor(ctx, matches, opts = {}, providerFactory = makeM
|
|
|
10517
11079
|
}
|
|
10518
11080
|
return { signals: result, complete };
|
|
10519
11081
|
}
|
|
10520
|
-
async function reliableMarketSignals(ctx, matches) {
|
|
11082
|
+
async function reliableMarketSignals(ctx, matches, now) {
|
|
10521
11083
|
if (ctx.cfg.markets === false) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
10522
|
-
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
10523
11084
|
const relevant = matches.filter((m) => marketRelevant(m, now));
|
|
10524
11085
|
if (relevant.length === 0) return { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
10525
|
-
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS);
|
|
11086
|
+
const raw = await marketSignalsFor(ctx, relevant, DEFAULT_ON_MARKET_OPTS, now);
|
|
10526
11087
|
const out2 = /* @__PURE__ */ new Map();
|
|
10527
11088
|
for (const [id, s] of raw.signals) {
|
|
10528
11089
|
const m = relevant.find((x) => x.id === id);
|
|
@@ -10530,8 +11091,8 @@ async function reliableMarketSignals(ctx, matches) {
|
|
|
10530
11091
|
}
|
|
10531
11092
|
return { signals: out2, complete: raw.complete };
|
|
10532
11093
|
}
|
|
10533
|
-
async function reliableMarketSignalFor(ctx, match) {
|
|
10534
|
-
const result = await reliableMarketSignals(ctx, [match]);
|
|
11094
|
+
async function reliableMarketSignalFor(ctx, match, now) {
|
|
11095
|
+
const result = await reliableMarketSignals(ctx, [match], now);
|
|
10535
11096
|
return { signal: result.signals.get(match.id), complete: result.complete };
|
|
10536
11097
|
}
|
|
10537
11098
|
function out(line = "") {
|
|
@@ -10541,7 +11102,10 @@ function emitJson(data) {
|
|
|
10541
11102
|
out(JSON.stringify(data, null, 2));
|
|
10542
11103
|
}
|
|
10543
11104
|
function modeOut(cfg, c) {
|
|
10544
|
-
out(
|
|
11105
|
+
out(modeRow(cfg, c));
|
|
11106
|
+
}
|
|
11107
|
+
function modeRow(cfg, c) {
|
|
11108
|
+
return c.dim(` ${modeLine(cfg.selection, cfg.lang)}`);
|
|
10545
11109
|
}
|
|
10546
11110
|
function competitionKey(cfg) {
|
|
10547
11111
|
return selectionExtras(cfg.selection);
|
|
@@ -10665,11 +11229,12 @@ async function cmdToday(date, ctx) {
|
|
|
10665
11229
|
const { cfg, t: t2 } = ctx;
|
|
10666
11230
|
precheck(cfg, t2, date);
|
|
10667
11231
|
const adapter = adapterFor(ctx);
|
|
10668
|
-
const
|
|
11232
|
+
const now = requestClock(adapter, ctx.now);
|
|
11233
|
+
const targetDate = date ?? localDate(now.toISOString(), cfg.tz);
|
|
10669
11234
|
const day = await getMatchesForDate(adapter, targetDate, resolveTz(cfg.tz));
|
|
10670
11235
|
const { matches, degraded, source } = day;
|
|
10671
11236
|
const todays = fixturesByDate(targetDate, matches, cfg.tz);
|
|
10672
|
-
const market = await reliableMarketSignals(ctx, todays);
|
|
11237
|
+
const market = await reliableMarketSignals(ctx, todays, now);
|
|
10673
11238
|
if (cfg.json) {
|
|
10674
11239
|
emitJson({
|
|
10675
11240
|
date: targetDate,
|
|
@@ -10721,13 +11286,14 @@ async function cmdToday(date, ctx) {
|
|
|
10721
11286
|
const src = attribution.attributed ? dataSource(source, cfg.lang, c) : "";
|
|
10722
11287
|
if (src) out(src);
|
|
10723
11288
|
out(disclaimer(t2, c));
|
|
10724
|
-
endScoreCommand(ctx);
|
|
11289
|
+
endScoreCommand(ctx, now);
|
|
10725
11290
|
}
|
|
10726
11291
|
async function cmdLive(ctx) {
|
|
10727
11292
|
const { cfg, t: t2 } = ctx;
|
|
10728
11293
|
precheck(cfg, t2);
|
|
10729
11294
|
const adapter = adapterFor(ctx);
|
|
10730
|
-
const
|
|
11295
|
+
const now = requestClock(adapter, ctx.now);
|
|
11296
|
+
const live = await getLiveMatches(adapter, now);
|
|
10731
11297
|
const { matches, degraded, source } = live;
|
|
10732
11298
|
if (cfg.json) {
|
|
10733
11299
|
emitJson({ degraded, source: source ?? null, matches, ...verdictExtras(live), ...competitionKey(cfg) });
|
|
@@ -10755,7 +11321,7 @@ async function cmdLive(ctx) {
|
|
|
10755
11321
|
const src = dataSource(source, cfg.lang, c);
|
|
10756
11322
|
if (src) out(src);
|
|
10757
11323
|
out(disclaimer(t2, c));
|
|
10758
|
-
endScoreCommand(ctx);
|
|
11324
|
+
endScoreCommand(ctx, now);
|
|
10759
11325
|
}
|
|
10760
11326
|
async function cmdNext(team, ctx) {
|
|
10761
11327
|
const { cfg, t: t2 } = ctx;
|
|
@@ -10796,7 +11362,7 @@ async function cmdNext(team, ctx) {
|
|
|
10796
11362
|
}
|
|
10797
11363
|
out();
|
|
10798
11364
|
out(disclaimer(t2, c));
|
|
10799
|
-
endScoreCommand(ctx);
|
|
11365
|
+
endScoreCommand(ctx, now);
|
|
10800
11366
|
return;
|
|
10801
11367
|
}
|
|
10802
11368
|
for (const q of qualifiers) out(c.dim(" " + q));
|
|
@@ -10814,7 +11380,7 @@ async function cmdNext(team, ctx) {
|
|
|
10814
11380
|
const src = dataSource(source, cfg.lang, c);
|
|
10815
11381
|
if (src) out(src);
|
|
10816
11382
|
out(disclaimer(t2, c));
|
|
10817
|
-
endScoreCommand(ctx);
|
|
11383
|
+
endScoreCommand(ctx, now);
|
|
10818
11384
|
}
|
|
10819
11385
|
function cmdTeam(query, ctx) {
|
|
10820
11386
|
const { cfg, t: t2 } = ctx;
|
|
@@ -11080,6 +11646,7 @@ function cmdAmbient({ cfg, now }, opts = {}) {
|
|
|
11080
11646
|
}
|
|
11081
11647
|
const { compact, max } = promptLineEnv();
|
|
11082
11648
|
const preference = ambientPreference(cfg);
|
|
11649
|
+
const lang = normalizeLang(cfg.lang);
|
|
11083
11650
|
const state = readCurrentState(cfg.source, cfg.competition);
|
|
11084
11651
|
const read = ambientView(state, {
|
|
11085
11652
|
pick: preference.pick,
|
|
@@ -11091,26 +11658,37 @@ function cmdAmbient({ cfg, now }, opts = {}) {
|
|
|
11091
11658
|
defaultCompetition: bundleApplies(cfg.competition),
|
|
11092
11659
|
teamKind: teamKind(cfg.competition),
|
|
11093
11660
|
now,
|
|
11094
|
-
columns: opts.columns
|
|
11661
|
+
columns: opts.columns,
|
|
11662
|
+
// The line's own tokens in the CLI's resolved language (the statusline module reads no environment).
|
|
11663
|
+
lang
|
|
11095
11664
|
});
|
|
11096
11665
|
const pick2 = preference.pick;
|
|
11097
11666
|
const view = {
|
|
11098
11667
|
line: read.line,
|
|
11668
|
+
lineParts: read.lineParts,
|
|
11099
11669
|
idle: read.idle,
|
|
11100
11670
|
empty: read.empty,
|
|
11101
11671
|
context: read.context,
|
|
11102
11672
|
live: read.live,
|
|
11673
|
+
finished: read.finished,
|
|
11103
11674
|
current: read.current,
|
|
11104
11675
|
next: read.next,
|
|
11105
11676
|
pick: pick2 === void 0 ? null : "code" in pick2 ? { code: pick2.code } : pick2.team,
|
|
11106
11677
|
...preference.unreadable ? { pickUnreadable: true } : {},
|
|
11107
11678
|
// A selected competition: core's key is present (see `selectionExtras`).
|
|
11108
11679
|
...selectionExtras(cfg.selection),
|
|
11680
|
+
// The language of the line and of the plugin's moment words (core's `moment.*` keys, templates).
|
|
11681
|
+
lang,
|
|
11682
|
+
words: momentWords(lang),
|
|
11109
11683
|
degraded: read.degraded,
|
|
11110
11684
|
source: read.source,
|
|
11111
11685
|
updatedAt: read.updatedAt,
|
|
11112
11686
|
staleAfter: read.staleAfter,
|
|
11113
|
-
disclaimer: DISCLAIMER
|
|
11687
|
+
disclaimer: DISCLAIMER,
|
|
11688
|
+
// The provider's sentence, English as every ambient string (never the reader's language): when the snapshot
|
|
11689
|
+
// names a provider and the line is neither the sign-off (`idle`) nor nothing known (`empty`); none otherwise.
|
|
11690
|
+
// `degraded` does not decide: a degraded snapshot still shows what an earlier read or discovery served.
|
|
11691
|
+
attribution: read.source !== null && !read.idle && !read.empty ? t("en", "live.data", { source: liveSourceLabel(read.source) }) : null
|
|
11114
11692
|
};
|
|
11115
11693
|
out(JSON.stringify(view));
|
|
11116
11694
|
printed = true;
|
|
@@ -11214,8 +11792,11 @@ async function cmdMatch(id, ctx) {
|
|
|
11214
11792
|
const now = requestClock(adapter, ctx.now);
|
|
11215
11793
|
const found = await getMatchById(adapter, id, now);
|
|
11216
11794
|
const { match, degraded, source: liveSource } = found;
|
|
11217
|
-
const market = match ? await reliableMarketSignalFor(
|
|
11795
|
+
const market = match ? await reliableMarketSignalFor(ctx, match, now) : { signal: void 0, complete: true };
|
|
11796
|
+
const answer = matchAnswerRows(found, id, market, cfg, t2);
|
|
11218
11797
|
if (cfg.json) {
|
|
11798
|
+
const rows = [...answer.rows];
|
|
11799
|
+
while (rows.length && rowText(rows[rows.length - 1]) === "") rows.pop();
|
|
11219
11800
|
emitJson({
|
|
11220
11801
|
degraded,
|
|
11221
11802
|
match: match ?? null,
|
|
@@ -11227,59 +11808,91 @@ async function cmdMatch(id, ctx) {
|
|
|
11227
11808
|
// The span a whole read searched for an id it did not hold (a plain field).
|
|
11228
11809
|
...found.window ? { window: found.window } : {},
|
|
11229
11810
|
...verdictExtras(found),
|
|
11811
|
+
lines: rows.map(rowText),
|
|
11812
|
+
rows,
|
|
11813
|
+
attribution: answer.attribution || null,
|
|
11814
|
+
disclaimer: answer.disclaimer,
|
|
11230
11815
|
...competitionKey(cfg)
|
|
11231
11816
|
});
|
|
11232
11817
|
return;
|
|
11233
11818
|
}
|
|
11234
11819
|
const c = painterFor(cfg);
|
|
11235
|
-
const qualifiers = verdictQualifiers(found, cfg.lang);
|
|
11236
11820
|
out();
|
|
11821
|
+
for (const row of answer.rows) out(paintRow(row, c));
|
|
11822
|
+
if (answer.attribution) out(c.dim(answer.attribution));
|
|
11823
|
+
out(c.dim(answer.disclaimer));
|
|
11824
|
+
if (match) maybeStarNudge(ctx);
|
|
11825
|
+
}
|
|
11826
|
+
function rowText(row) {
|
|
11827
|
+
return row.parts.map((p) => p.text).join("");
|
|
11828
|
+
}
|
|
11829
|
+
function paintPart(p, c) {
|
|
11830
|
+
switch (p.style) {
|
|
11831
|
+
case "strong":
|
|
11832
|
+
return c.bold(p.text);
|
|
11833
|
+
case "dim":
|
|
11834
|
+
case "quote":
|
|
11835
|
+
return c.dim(p.text);
|
|
11836
|
+
case "accent":
|
|
11837
|
+
case "flair":
|
|
11838
|
+
return c.green(p.text);
|
|
11839
|
+
default:
|
|
11840
|
+
return p.text;
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
function paintRow(row, c) {
|
|
11844
|
+
return row.parts.map((p) => paintPart(p, c)).join("");
|
|
11845
|
+
}
|
|
11846
|
+
function matchAnswerRows(found, id, market, cfg, t2) {
|
|
11847
|
+
const { match, degraded, source: liveSource } = found;
|
|
11848
|
+
const rows = [];
|
|
11849
|
+
const row = (role, ...parts) => rows.push({ role, parts });
|
|
11850
|
+
const blank = (role = "blank") => rows.push({ role, parts: [] });
|
|
11851
|
+
const plain2 = painterFor({ ...cfg, color: false });
|
|
11852
|
+
const footer = { disclaimer: disclaimer(t2, plain2) };
|
|
11853
|
+
const mode = { text: modeRow(cfg, plain2), style: "dim" };
|
|
11854
|
+
const qualifiers = verdictQualifiers(found, cfg.lang);
|
|
11237
11855
|
if (!match) {
|
|
11238
|
-
|
|
11239
|
-
for (const q of qualifiers)
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
);
|
|
11245
|
-
|
|
11246
|
-
out(disclaimer(t2, c));
|
|
11247
|
-
return;
|
|
11856
|
+
row("text", mode);
|
|
11857
|
+
for (const q of qualifiers) row("text", { text: " " + q, style: "dim" });
|
|
11858
|
+
row("text", {
|
|
11859
|
+
text: " " + (degraded ? t2("live.degraded") : verdictNotice(found, cfg.lang) ?? matchWindowSentence(found, id, cfg.lang) ?? matchNoneReadSentence(found, id, cfg.lang) ?? t2("match.none", { id })),
|
|
11860
|
+
style: "dim"
|
|
11861
|
+
});
|
|
11862
|
+
blank("text");
|
|
11863
|
+
return { rows, attribution: "", ...footer };
|
|
11248
11864
|
}
|
|
11249
|
-
for (const q of qualifiers)
|
|
11865
|
+
for (const q of qualifiers) row("qualifier", { text: " " + q, style: "dim" });
|
|
11250
11866
|
const attribution = dayAttribution(found, [match], cfg.lang);
|
|
11251
|
-
if (attribution.unserved)
|
|
11252
|
-
|
|
11253
|
-
|
|
11867
|
+
if (attribution.unserved) row("qualifier", { text: " " + attribution.unserved, style: "dim" });
|
|
11868
|
+
const pin = cfg.pin;
|
|
11869
|
+
const side = (team) => pin !== void 0 && isPinnedSide(team, pin) ? { text: team.name, style: "accent" } : { text: team.name };
|
|
11870
|
+
row("title", side(match.home), { text: " " }, { text: scoreline(match), style: "strong" }, { text: " " }, side(match.away));
|
|
11871
|
+
row("mode", mode);
|
|
11254
11872
|
const where = joinSegments([stageLabelI18n(cfg.lang, match), matchLocation(match)]);
|
|
11255
|
-
if (where)
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11260
|
-
);
|
|
11873
|
+
if (where) row("meta", { text: " " }, { text: where, style: "dim" });
|
|
11874
|
+
const word = statusWord(match, t2);
|
|
11875
|
+
const kickoff = { text: `${t2("match.kickoff")} ${formatKickoff(match.kickoff, { tz: cfg.tz, locale: cfg.lang })}`, style: "dim" };
|
|
11876
|
+
if (word) row("status", { text: " " }, { text: word, style: "strong" }, { text: " \xB7 ", style: "dim" }, kickoff);
|
|
11877
|
+
else row("status", { text: " " }, kickoff);
|
|
11261
11878
|
const flair = matchFlair(match, flairOpts(cfg));
|
|
11262
|
-
if (flair.text)
|
|
11263
|
-
if (match.events?.length) {
|
|
11264
|
-
out();
|
|
11265
|
-
for (const e of match.events) {
|
|
11266
|
-
out(` ${e.minute}' ${e.type} ${e.teamCode}${e.player ? ` \u2014 ${e.player}` : ""}`);
|
|
11267
|
-
}
|
|
11268
|
-
}
|
|
11879
|
+
if (flair.text) row("flair", { text: " " }, { text: flair.text, style: flair.rally ? "flair" : "quote" });
|
|
11269
11880
|
if (market.signal) {
|
|
11270
|
-
|
|
11271
|
-
for (const mline of marketBlock(market.signal, match))
|
|
11881
|
+
blank();
|
|
11882
|
+
for (const mline of marketBlock(market.signal, match)) row("market", { text: " " }, { text: mline, style: "dim" });
|
|
11883
|
+
}
|
|
11884
|
+
const timeline = timelineParts(match, { lang: cfg.lang, flags: flagsEnabled() });
|
|
11885
|
+
if (timeline.length) {
|
|
11886
|
+
blank();
|
|
11887
|
+
for (const parts of timeline) row("event", ...parts);
|
|
11272
11888
|
}
|
|
11273
11889
|
if (!market.complete) {
|
|
11274
|
-
|
|
11275
|
-
|
|
11890
|
+
blank();
|
|
11891
|
+
row("note", { text: " Market data unavailable or incomplete \u2014 this match could not be checked.", style: "dim" });
|
|
11276
11892
|
}
|
|
11277
|
-
|
|
11278
|
-
if (degraded)
|
|
11279
|
-
|
|
11280
|
-
if (src) out(src);
|
|
11281
|
-
out(disclaimer(t2, c));
|
|
11282
|
-
maybeStarNudge(ctx);
|
|
11893
|
+
blank();
|
|
11894
|
+
if (degraded) row("note", { text: " " + t2(found.earlierRecord ? "feed.earlierRecord" : "feed.degraded"), style: "dim" });
|
|
11895
|
+
return { rows, attribution: dataSource(liveSource, cfg.lang, plain2), ...footer };
|
|
11283
11896
|
}
|
|
11284
11897
|
var MARKET_INFO = "Prediction-market data is informational only.";
|
|
11285
11898
|
function marketHeaderLine(m, cfg) {
|
|
@@ -11302,10 +11915,11 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11302
11915
|
const asked = teamAsked(team, cfg);
|
|
11303
11916
|
if (!asked) throw new InputError(usage);
|
|
11304
11917
|
const code = "pin" in asked ? asked.pin.code : resolveTeamArg(asked.query, usage, t2, cfg.competition);
|
|
11305
|
-
const
|
|
11306
|
-
const
|
|
11918
|
+
const adapter2 = adapterFor(ctx);
|
|
11919
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
11920
|
+
const picked = await marketFixtureForTeam(adapter2, code, now2);
|
|
11307
11921
|
const { match: fixture, degraded } = picked;
|
|
11308
|
-
const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11922
|
+
const market = fixture && marketRelevant(fixture, now2) ? await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS, now2) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11309
11923
|
const sig = fixture ? market.signals.get(fixture.id) : void 0;
|
|
11310
11924
|
const shown = market.complete && fixture && sig && marketDisplayable(fixture, sig) ? sig : void 0;
|
|
11311
11925
|
if (cfg.json) {
|
|
@@ -11372,11 +11986,11 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11372
11986
|
out(c3.dim(MARKET_INFO));
|
|
11373
11987
|
return;
|
|
11374
11988
|
}
|
|
11375
|
-
const
|
|
11376
|
-
const now2 = requestClock(
|
|
11377
|
-
const found = await getMatchById(
|
|
11989
|
+
const adapter2 = adapterFor(ctx);
|
|
11990
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
11991
|
+
const found = await getMatchById(adapter2, target, now2);
|
|
11378
11992
|
const { match } = found;
|
|
11379
|
-
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11993
|
+
const market = match && marketRelevant(match, now2) ? await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS, now2) : { signals: /* @__PURE__ */ new Map(), complete: true };
|
|
11380
11994
|
const sig = match ? market.signals.get(match.id) : void 0;
|
|
11381
11995
|
const shown = market.complete && match && sig && marketDisplayable(match, sig) ? sig : void 0;
|
|
11382
11996
|
if (cfg.json) {
|
|
@@ -11412,12 +12026,13 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11412
12026
|
}
|
|
11413
12027
|
const explicitDate = target && target !== "today" ? target : void 0;
|
|
11414
12028
|
precheck(cfg, t2, explicitDate);
|
|
11415
|
-
const
|
|
12029
|
+
const adapter = adapterFor(ctx);
|
|
12030
|
+
const now = requestClock(adapter, ctx.now);
|
|
11416
12031
|
const date = explicitDate ?? localDate(now.toISOString(), cfg.tz);
|
|
11417
|
-
const day = await getMatchesForDate(
|
|
12032
|
+
const day = await getMatchesForDate(adapter, date, resolveTz(cfg.tz));
|
|
11418
12033
|
const todays = fixturesByDate(date, day.matches, cfg.tz);
|
|
11419
12034
|
const relevant = todays.filter((m) => marketRelevant(m, now));
|
|
11420
|
-
const { signals, complete } = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS);
|
|
12035
|
+
const { signals, complete } = await marketSignalsFor(ctx, relevant, MARKETS_CMD_OPTS, now);
|
|
11421
12036
|
const rows = relevant.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
11422
12037
|
(r) => !!r.signal && marketDisplayable(r.match, r.signal)
|
|
11423
12038
|
);
|
|
@@ -11466,8 +12081,8 @@ async function cmdMarkets(target, team, ctx) {
|
|
|
11466
12081
|
function pickShareStyle(v) {
|
|
11467
12082
|
return v === "compact" ? "compact" : "social";
|
|
11468
12083
|
}
|
|
11469
|
-
async function reliableShareSignals(ctx, matches) {
|
|
11470
|
-
const raw = await reliableMarketSignals(ctx, matches);
|
|
12084
|
+
async function reliableShareSignals(ctx, matches, now) {
|
|
12085
|
+
const raw = await reliableMarketSignals(ctx, matches, now);
|
|
11471
12086
|
const out2 = /* @__PURE__ */ new Map();
|
|
11472
12087
|
for (const [id, s] of raw.signals) {
|
|
11473
12088
|
const m = matches.find((x) => x.id === id);
|
|
@@ -11568,7 +12183,8 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
11568
12183
|
const where = { tz: cfg.tz, locale: cfg.lang, competition: cfg.competition };
|
|
11569
12184
|
if (target === "live") {
|
|
11570
12185
|
precheck(cfg, t2);
|
|
11571
|
-
const
|
|
12186
|
+
const adapter2 = adapterFor(ctx);
|
|
12187
|
+
const live = await getLiveMatches(adapter2, requestClock(adapter2, ctx.now));
|
|
11572
12188
|
emitMatchCard(ctx, liveShareCard(live, where), { ...baseOptions, includeMarkets: false }, copy);
|
|
11573
12189
|
return;
|
|
11574
12190
|
}
|
|
@@ -11609,27 +12225,29 @@ async function cmdShare(target, team, opts, ctx) {
|
|
|
11609
12225
|
}
|
|
11610
12226
|
if (target === "next") {
|
|
11611
12227
|
precheck(cfg, t2);
|
|
11612
|
-
const { code, next, now } = await nextAsked(ctx, team, teamUsage("share next", cfg));
|
|
11613
|
-
const market2 = await reliableShareSignals(
|
|
12228
|
+
const { code, next, now: now2 } = await nextAsked(ctx, team, teamUsage("share next", cfg));
|
|
12229
|
+
const market2 = await reliableShareSignals(ctx, next.fixture ? [next.fixture] : [], now2);
|
|
11614
12230
|
emitMatchCard(ctx, nextShareCard(next, code, market2, where), baseOptions, copy);
|
|
11615
12231
|
return;
|
|
11616
12232
|
}
|
|
11617
12233
|
if (target && target !== "today" && !isValidDate(target)) {
|
|
11618
12234
|
precheck(cfg, t2);
|
|
11619
|
-
const
|
|
11620
|
-
const
|
|
11621
|
-
const found = await getMatchById(
|
|
11622
|
-
const market2 = await reliableShareSignals(
|
|
12235
|
+
const adapter2 = adapterFor(ctx);
|
|
12236
|
+
const now2 = requestClock(adapter2, ctx.now);
|
|
12237
|
+
const found = await getMatchById(adapter2, target, now2);
|
|
12238
|
+
const market2 = await reliableShareSignals(ctx, found.match ? [found.match] : [], now2);
|
|
11623
12239
|
emitMatchCard(ctx, matchShareCard(found, target, market2, where), baseOptions, copy);
|
|
11624
12240
|
return;
|
|
11625
12241
|
}
|
|
11626
12242
|
const explicitDate = target && target !== "today" ? target : void 0;
|
|
11627
12243
|
precheck(cfg, t2, explicitDate);
|
|
11628
|
-
const
|
|
11629
|
-
const
|
|
12244
|
+
const adapter = adapterFor(ctx);
|
|
12245
|
+
const now = requestClock(adapter, ctx.now);
|
|
12246
|
+
const date = explicitDate ?? localDate(now.toISOString(), cfg.tz);
|
|
12247
|
+
const day = await getMatchesForDate(adapter, date, resolveTz(cfg.tz));
|
|
11630
12248
|
const { matches: all, degraded, source } = day;
|
|
11631
12249
|
const todays = fixturesByDate(date, all, cfg.tz);
|
|
11632
|
-
const market = await reliableShareSignals(ctx, todays);
|
|
12250
|
+
const market = await reliableShareSignals(ctx, todays, now);
|
|
11633
12251
|
emitMatchCard(
|
|
11634
12252
|
ctx,
|
|
11635
12253
|
dateShareCard(
|
|
@@ -11717,7 +12335,8 @@ async function pinFor(ctx, competition, query) {
|
|
|
11717
12335
|
if (!isKnownSource(cfg.source)) {
|
|
11718
12336
|
throw new InputError(t2("err.source", { source: cfg.source, sources: KNOWN_SOURCES.join(", ") }));
|
|
11719
12337
|
}
|
|
11720
|
-
const
|
|
12338
|
+
const adapter = adapterServing(ctx, competition);
|
|
12339
|
+
const next = await getNextFixtureForTeam(adapter, label2, requestClock(adapter, ctx.now));
|
|
11721
12340
|
if (next.team) {
|
|
11722
12341
|
const { id, code, name } = next.team;
|
|
11723
12342
|
return id !== void 0 ? { id, code, name } : { code, name };
|
|
@@ -11910,8 +12529,8 @@ function maybeStarNudge(ctx) {
|
|
|
11910
12529
|
out();
|
|
11911
12530
|
out(c.dim(` \u2B50 Enjoying Claudinho? Star it \u2192 ${REPO_URL} (claudinho star)`));
|
|
11912
12531
|
}
|
|
11913
|
-
function endScoreCommand(ctx) {
|
|
11914
|
-
const over = bundleApplies(ctx.cfg.competition) && isTournamentWindowOver(
|
|
12532
|
+
function endScoreCommand(ctx, now) {
|
|
12533
|
+
const over = bundleApplies(ctx.cfg.competition) && isTournamentWindowOver(now.getTime());
|
|
11915
12534
|
if (over) printTournamentSignOff(ctx);
|
|
11916
12535
|
else maybeStarNudge(ctx);
|
|
11917
12536
|
}
|
|
@@ -11935,7 +12554,8 @@ function printInitStarCta(cfg) {
|
|
|
11935
12554
|
function cmdVibe(ctx) {
|
|
11936
12555
|
const { cfg } = ctx;
|
|
11937
12556
|
const bundled = cfg.selection.kind === "selected" && bundleApplies(cfg.competition);
|
|
11938
|
-
const
|
|
12557
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
12558
|
+
const pool = vibePool(localDate(now.toISOString(), cfg.tz), bundled);
|
|
11939
12559
|
const line = pool[Math.floor(Math.random() * pool.length)];
|
|
11940
12560
|
let liveSeg;
|
|
11941
12561
|
try {
|
|
@@ -11943,7 +12563,7 @@ function cmdVibe(ctx) {
|
|
|
11943
12563
|
const state = readCurrentState(cfg.source, cfg.competition);
|
|
11944
12564
|
liveSeg = vibeLiveSegment(
|
|
11945
12565
|
// Sealed with the competition's written kind, like the statusline's.
|
|
11946
|
-
liveMatchesFromCache(state,
|
|
12566
|
+
liveMatchesFromCache(state, now.getTime(), teamKind(cfg.competition)).items,
|
|
11947
12567
|
// The statusline's and the hook's pick (offline).
|
|
11948
12568
|
ambientPick(cfg)
|
|
11949
12569
|
);
|
|
@@ -11969,7 +12589,7 @@ function handlePipeError(stream) {
|
|
|
11969
12589
|
}
|
|
11970
12590
|
handlePipeError(process.stdout);
|
|
11971
12591
|
handlePipeError(process.stderr);
|
|
11972
|
-
var VERSION = "0.
|
|
12592
|
+
var VERSION = "0.12.0";
|
|
11973
12593
|
var DISCLAIMER2 = `Claudinho is an ${FAN_PROJECT.toLowerCase()}. ${DISCLAIMER}`;
|
|
11974
12594
|
function ctxFrom(cmd) {
|
|
11975
12595
|
let root = cmd;
|