cricinfo-cli-go 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/internal/cli/matches.go +126 -2
- package/internal/cli/matches_test.go +82 -0
- package/internal/cli/search.go +156 -0
- package/internal/cricinfo/analysis.go +393 -93
- package/internal/cricinfo/analysis_phase15_test.go +38 -0
- package/internal/cricinfo/client.go +23 -2
- package/internal/cricinfo/coverage_ledger_test.go +2 -22
- package/internal/cricinfo/entity_index.go +27 -0
- package/internal/cricinfo/historical_hydration.go +82 -42
- package/internal/cricinfo/matches.go +1641 -88
- package/internal/cricinfo/matches_phase7_test.go +11 -4
- package/internal/cricinfo/normalize_entities.go +83 -35
- package/internal/cricinfo/players.go +236 -2
- package/internal/cricinfo/render_contract.go +191 -49
- package/internal/cricinfo/renderer.go +613 -19
- package/internal/cricinfo/resolver.go +134 -13
- package/internal/cricinfo/teams.go +109 -6
- package/internal/cricinfo/testdata/coverage/cricinfo-field-path-catalog.txt +2536 -0
- package/internal/cricinfo/testdata/coverage/cricinfo-working-templates.tsv +56 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"io"
|
|
7
7
|
"regexp"
|
|
8
8
|
"sort"
|
|
9
|
+
"strconv"
|
|
9
10
|
"strings"
|
|
10
11
|
)
|
|
11
12
|
|
|
@@ -201,6 +202,15 @@ func renderText(w io.Writer, result NormalizedResult, opts RenderOptions) error
|
|
|
201
202
|
lines = append(lines, formatInningsTimelines(itemMap)...)
|
|
202
203
|
return writeTextLines(w, lines)
|
|
203
204
|
}
|
|
205
|
+
if result.Kind == EntityMatchSituation {
|
|
206
|
+
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
207
|
+
if err != nil {
|
|
208
|
+
return err
|
|
209
|
+
}
|
|
210
|
+
lines = append(lines, "Match Situation")
|
|
211
|
+
lines = append(lines, formatMatchSituation(itemMap)...)
|
|
212
|
+
return writeTextLines(w, lines)
|
|
213
|
+
}
|
|
204
214
|
if result.Kind == EntityPlayerStats {
|
|
205
215
|
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
206
216
|
if err != nil {
|
|
@@ -255,6 +265,24 @@ func renderText(w io.Writer, result NormalizedResult, opts RenderOptions) error
|
|
|
255
265
|
lines = append(lines, formatMatchView(itemMap)...)
|
|
256
266
|
return writeTextLines(w, lines)
|
|
257
267
|
}
|
|
268
|
+
if result.Kind == EntityMatchPhases {
|
|
269
|
+
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
270
|
+
if err != nil {
|
|
271
|
+
return err
|
|
272
|
+
}
|
|
273
|
+
lines = append(lines, "Match Phases")
|
|
274
|
+
lines = append(lines, formatMatchPhases(itemMap)...)
|
|
275
|
+
return writeTextLines(w, lines)
|
|
276
|
+
}
|
|
277
|
+
if result.Kind == EntityMatchDuel {
|
|
278
|
+
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
279
|
+
if err != nil {
|
|
280
|
+
return err
|
|
281
|
+
}
|
|
282
|
+
lines = append(lines, "Match Duel")
|
|
283
|
+
lines = append(lines, formatMatchDuel(itemMap)...)
|
|
284
|
+
return writeTextLines(w, lines)
|
|
285
|
+
}
|
|
258
286
|
|
|
259
287
|
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
260
288
|
if err != nil {
|
|
@@ -274,13 +302,68 @@ func renderText(w io.Writer, result NormalizedResult, opts RenderOptions) error
|
|
|
274
302
|
return writeTextLines(w, lines)
|
|
275
303
|
}
|
|
276
304
|
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
if result.Kind == EntityTeamStatistics || result.Kind == EntityTeamRecords {
|
|
306
|
+
title := "Team Statistics"
|
|
307
|
+
if result.Kind == EntityTeamRecords {
|
|
308
|
+
title = "Team Records"
|
|
309
|
+
}
|
|
310
|
+
lines = append(lines, fmt.Sprintf("%s Categories (%d)", title, len(result.Items)))
|
|
311
|
+
lines = append(lines, formatStatCategoryList(result.Items)...)
|
|
312
|
+
return writeTextLines(w, lines)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if result.Kind == EntityStandingsGroup {
|
|
316
|
+
lines = append(lines, formatStandingsGroupList(result.Items)...)
|
|
317
|
+
return writeTextLines(w, lines)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if result.Kind == EntityDeliveryEvent || result.Kind == EntityPlayerDelivery {
|
|
321
|
+
lines = append(lines, fmt.Sprintf("%s (%d)", titleize(kindPlural(result.Kind)), len(result.Items)))
|
|
322
|
+
if result.Kind == EntityDeliveryEvent {
|
|
323
|
+
source := ""
|
|
324
|
+
switch {
|
|
325
|
+
case strings.Contains(result.RequestedRef, "/details"):
|
|
326
|
+
source = "details"
|
|
327
|
+
case strings.Contains(result.RequestedRef, "/plays"):
|
|
328
|
+
source = "plays"
|
|
329
|
+
}
|
|
330
|
+
if source != "" {
|
|
331
|
+
lines = append(lines, "Source: "+source)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for i, item := range result.Items {
|
|
335
|
+
summary := summarizeDeliveryListItem(item, result.Kind)
|
|
336
|
+
if strings.TrimSpace(summary) == "" {
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
lines = append(lines, fmt.Sprintf("%d. %s", i+1, summary))
|
|
340
|
+
}
|
|
341
|
+
return writeTextLines(w, lines)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
summaries := make([]string, 0, len(result.Items))
|
|
345
|
+
for _, item := range result.Items {
|
|
279
346
|
itemMap, err := toMap(item, opts.AllFields)
|
|
280
347
|
if err != nil {
|
|
281
348
|
return err
|
|
282
349
|
}
|
|
283
|
-
|
|
350
|
+
summary := summarizeEntity(itemMap, result.Kind, opts.Verbose)
|
|
351
|
+
if strings.TrimSpace(summary) == "" {
|
|
352
|
+
continue
|
|
353
|
+
}
|
|
354
|
+
summaries = append(summaries, summary)
|
|
355
|
+
}
|
|
356
|
+
if len(summaries) == 0 {
|
|
357
|
+
message := result.Message
|
|
358
|
+
if message == "" {
|
|
359
|
+
message = fmt.Sprintf("No %s found.", kindPlural(result.Kind))
|
|
360
|
+
}
|
|
361
|
+
lines = append(lines, sentenceCase(message))
|
|
362
|
+
return writeTextLines(w, lines)
|
|
363
|
+
}
|
|
364
|
+
lines = append(lines, fmt.Sprintf("%s (%d)", titleize(kindPlural(result.Kind)), len(summaries)))
|
|
365
|
+
for i, summary := range summaries {
|
|
366
|
+
lines = append(lines, fmt.Sprintf("%d. %s", i+1, summary))
|
|
284
367
|
}
|
|
285
368
|
|
|
286
369
|
return writeTextLines(w, lines)
|
|
@@ -295,6 +378,71 @@ func writeTextLines(w io.Writer, lines []string) error {
|
|
|
295
378
|
return nil
|
|
296
379
|
}
|
|
297
380
|
|
|
381
|
+
func summarizeDeliveryListItem(item any, kind EntityKind) string {
|
|
382
|
+
switch typed := item.(type) {
|
|
383
|
+
case DeliveryEvent:
|
|
384
|
+
short := firstNonEmpty(strings.TrimSpace(typed.ShortText), strings.TrimSpace(typed.Text))
|
|
385
|
+
if short == "" {
|
|
386
|
+
short = joinParts("over "+intString(typed.OverNumber), "ball "+intString(typed.BallNumber))
|
|
387
|
+
}
|
|
388
|
+
lead := firstNonEmpty(overBallString(typed.OverNumber, typed.BallNumber), "")
|
|
389
|
+
score := firstNonEmpty(scoreLabel(typed.HomeScore), scoreLabel(typed.AwayScore))
|
|
390
|
+
if kind == EntityPlayerDelivery {
|
|
391
|
+
return joinParts(lead, short, score, strings.Join(typed.Involvement, ","))
|
|
392
|
+
}
|
|
393
|
+
return joinParts(lead, short, score)
|
|
394
|
+
case map[string]any:
|
|
395
|
+
short := firstNonEmpty(valueString(typed, "shortText"), valueString(typed, "text"))
|
|
396
|
+
if short == "" {
|
|
397
|
+
short = joinParts("over "+valueString(typed, "overNumber"), "ball "+valueString(typed, "ballNumber"))
|
|
398
|
+
}
|
|
399
|
+
if strings.TrimSpace(short) == "/" || strings.TrimSpace(short) == "-" {
|
|
400
|
+
return ""
|
|
401
|
+
}
|
|
402
|
+
lead := overBallLabel(typed)
|
|
403
|
+
score := firstNonEmpty(scoreLabel(valueString(typed, "homeScore")), scoreLabel(valueString(typed, "awayScore")))
|
|
404
|
+
if kind == EntityPlayerDelivery {
|
|
405
|
+
return joinParts(lead, short, score, involvementLabel(typed))
|
|
406
|
+
}
|
|
407
|
+
return joinParts(lead, short, score)
|
|
408
|
+
default:
|
|
409
|
+
return ""
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
func overBallString(over, ball int) string {
|
|
414
|
+
if over <= 0 || ball <= 0 {
|
|
415
|
+
return ""
|
|
416
|
+
}
|
|
417
|
+
return fmt.Sprintf("%d.%d", over, ball)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
func intString(value int) string {
|
|
421
|
+
if value <= 0 {
|
|
422
|
+
return ""
|
|
423
|
+
}
|
|
424
|
+
return fmt.Sprintf("%d", value)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
func defaultNumeric(raw string) string {
|
|
428
|
+
raw = strings.TrimSpace(raw)
|
|
429
|
+
if raw == "" {
|
|
430
|
+
return "0"
|
|
431
|
+
}
|
|
432
|
+
return raw
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
func scoreLabel(raw string) string {
|
|
436
|
+
raw = strings.TrimSpace(raw)
|
|
437
|
+
if raw == "" {
|
|
438
|
+
return ""
|
|
439
|
+
}
|
|
440
|
+
if strings.Contains(raw, "/") {
|
|
441
|
+
return raw
|
|
442
|
+
}
|
|
443
|
+
return ""
|
|
444
|
+
}
|
|
445
|
+
|
|
298
446
|
func sanitizeValue(value any, allFields bool) (any, error) {
|
|
299
447
|
blob, err := json.Marshal(value)
|
|
300
448
|
if err != nil {
|
|
@@ -339,6 +487,25 @@ func toMap(value any, allFields bool) (map[string]any, error) {
|
|
|
339
487
|
return mapped, nil
|
|
340
488
|
}
|
|
341
489
|
|
|
490
|
+
func mapFromAny(value any) map[string]any {
|
|
491
|
+
if value == nil {
|
|
492
|
+
return nil
|
|
493
|
+
}
|
|
494
|
+
if mapped, ok := value.(map[string]any); ok {
|
|
495
|
+
return mapped
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
blob, err := json.Marshal(value)
|
|
499
|
+
if err != nil {
|
|
500
|
+
return nil
|
|
501
|
+
}
|
|
502
|
+
var mapped map[string]any
|
|
503
|
+
if err := json.Unmarshal(blob, &mapped); err != nil {
|
|
504
|
+
return nil
|
|
505
|
+
}
|
|
506
|
+
return mapped
|
|
507
|
+
}
|
|
508
|
+
|
|
342
509
|
func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) string {
|
|
343
510
|
switch kind {
|
|
344
511
|
case EntityMatch:
|
|
@@ -380,6 +547,20 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
380
547
|
return joinParts("situation", data)
|
|
381
548
|
}
|
|
382
549
|
return joinParts("situation", valueString(entity, "competitionId"))
|
|
550
|
+
case EntityMatchDuel:
|
|
551
|
+
duelLabel := strings.TrimSpace(fmt.Sprintf("%s vs %s",
|
|
552
|
+
firstNonEmpty(valueString(entity, "batterName"), valueString(entity, "batterId")),
|
|
553
|
+
firstNonEmpty(valueString(entity, "bowlerName"), valueString(entity, "bowlerId")),
|
|
554
|
+
))
|
|
555
|
+
return joinParts(
|
|
556
|
+
duelLabel,
|
|
557
|
+
fmt.Sprintf("%s off %s", valueString(entity, "runs"), valueString(entity, "balls")),
|
|
558
|
+
)
|
|
559
|
+
case EntityMatchPhases:
|
|
560
|
+
return joinParts(
|
|
561
|
+
"match "+firstNonEmpty(valueString(entity, "matchId"), valueString(entity, "competitionId")),
|
|
562
|
+
fmt.Sprintf("%d innings", len(sliceValue(entity, "innings"))),
|
|
563
|
+
)
|
|
383
564
|
case EntityCompetition:
|
|
384
565
|
return joinParts(
|
|
385
566
|
firstNonEmpty(valueString(entity, "shortDescription"), valueString(entity, "description"), valueString(entity, "id")),
|
|
@@ -431,7 +612,15 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
431
612
|
name := firstNonEmpty(valueString(entity, "name"), valueString(entity, "shortName"), valueString(entity, "id"))
|
|
432
613
|
return joinParts(name, bracket(valueString(entity, "homeAway")))
|
|
433
614
|
case EntityTeamRoster:
|
|
434
|
-
name :=
|
|
615
|
+
name := strings.TrimSpace(valueString(entity, "displayName"))
|
|
616
|
+
if name == "" {
|
|
617
|
+
playerID := firstNonEmpty(valueString(entity, "playerId"), valueString(entity, "athleteId"))
|
|
618
|
+
if playerID != "" {
|
|
619
|
+
name = "Unknown player (" + playerID + ")"
|
|
620
|
+
} else {
|
|
621
|
+
name = "Unknown player"
|
|
622
|
+
}
|
|
623
|
+
}
|
|
435
624
|
badges := []string{}
|
|
436
625
|
if valueString(entity, "captain") == "true" {
|
|
437
626
|
badges = append(badges, "captain")
|
|
@@ -439,7 +628,7 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
439
628
|
if valueString(entity, "active") == "true" {
|
|
440
629
|
badges = append(badges, "active")
|
|
441
630
|
}
|
|
442
|
-
return joinParts(name, strings.Join(badges, ", "))
|
|
631
|
+
return joinParts(name, firstNonEmpty(valueString(entity, "teamName"), valueString(entity, "teamId")), strings.Join(badges, ", "))
|
|
443
632
|
case EntityTeamScore:
|
|
444
633
|
return joinParts(valueString(entity, "displayValue"), valueString(entity, "value"), bracket(valueString(entity, "source")))
|
|
445
634
|
case EntityTeamLeaders:
|
|
@@ -470,16 +659,26 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
470
659
|
case EntityStandingsGroup:
|
|
471
660
|
return joinParts(valueString(entity, "id"), "season "+valueString(entity, "seasonId"))
|
|
472
661
|
case EntityInnings:
|
|
662
|
+
if valueString(entity, "score") == "" &&
|
|
663
|
+
valueString(entity, "runs") == "" &&
|
|
664
|
+
valueString(entity, "wickets") == "" &&
|
|
665
|
+
valueString(entity, "target") == "" &&
|
|
666
|
+
valueString(entity, "isBatting") == "false" {
|
|
667
|
+
return ""
|
|
668
|
+
}
|
|
473
669
|
score := valueString(entity, "score")
|
|
474
670
|
if score == "" {
|
|
475
671
|
score = joinParts(valueString(entity, "runs")+"/"+valueString(entity, "wickets"), valueString(entity, "overs")+" ov")
|
|
476
672
|
}
|
|
477
|
-
|
|
673
|
+
parts := []string{
|
|
478
674
|
firstNonEmpty(valueString(entity, "teamName"), valueString(entity, "teamId")),
|
|
479
|
-
"innings "+valueString(entity, "inningsNumber")+"/"+valueString(entity, "period"),
|
|
675
|
+
"innings " + valueString(entity, "inningsNumber") + "/" + valueString(entity, "period"),
|
|
480
676
|
score,
|
|
481
|
-
|
|
482
|
-
)
|
|
677
|
+
}
|
|
678
|
+
if wc := len(sliceValue(entity, "wicketTimeline")); wc > 0 {
|
|
679
|
+
parts = append(parts, fmt.Sprintf("%d wickets", wc))
|
|
680
|
+
}
|
|
681
|
+
return joinParts(parts...)
|
|
483
682
|
case EntityDeliveryEvent:
|
|
484
683
|
short := firstNonEmpty(valueString(entity, "shortText"), valueString(entity, "text"))
|
|
485
684
|
if short == "" {
|
|
@@ -489,16 +688,32 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
489
688
|
case EntityStatCategory:
|
|
490
689
|
return joinParts(firstNonEmpty(valueString(entity, "displayName"), valueString(entity, "name")), fmt.Sprintf("%d stats", len(sliceValue(entity, "stats"))))
|
|
491
690
|
case EntityPartnership:
|
|
691
|
+
runsText := ""
|
|
692
|
+
if runs := valueString(entity, "runs"); runs != "" {
|
|
693
|
+
runsText = runs + " runs"
|
|
694
|
+
} else if valueString(entity, "overs") != "" {
|
|
695
|
+
runsText = "0 runs"
|
|
696
|
+
}
|
|
697
|
+
oversText := ""
|
|
698
|
+
if overs := valueString(entity, "overs"); overs != "" {
|
|
699
|
+
oversText = overs + " ov"
|
|
700
|
+
}
|
|
492
701
|
return joinParts(
|
|
493
702
|
firstNonEmpty(valueString(entity, "wicketName"), "partnership "+valueString(entity, "id")),
|
|
494
|
-
|
|
495
|
-
|
|
703
|
+
runsText,
|
|
704
|
+
oversText,
|
|
496
705
|
"innings "+valueString(entity, "inningsId")+"/"+valueString(entity, "period"),
|
|
497
706
|
)
|
|
498
707
|
case EntityFallOfWicket:
|
|
708
|
+
scoreText := ""
|
|
709
|
+
if runs := valueString(entity, "runs"); runs != "" {
|
|
710
|
+
scoreText = runs + "/" + valueString(entity, "wicketNumber")
|
|
711
|
+
} else if valueString(entity, "wicketNumber") == "1" {
|
|
712
|
+
scoreText = "0/1"
|
|
713
|
+
}
|
|
499
714
|
return joinParts(
|
|
500
715
|
"wicket "+valueString(entity, "wicketNumber"),
|
|
501
|
-
|
|
716
|
+
scoreText,
|
|
502
717
|
valueString(entity, "wicketOver")+" ov",
|
|
503
718
|
"innings "+valueString(entity, "inningsId")+"/"+valueString(entity, "period"),
|
|
504
719
|
)
|
|
@@ -609,6 +824,8 @@ func formatSingleEntity(entity map[string]any, kind EntityKind, opts RenderOptio
|
|
|
609
824
|
order = []string{"matchId", "competitionId", "eventId", "leagueId", "battingCards", "bowlingCards", "partnershipCards"}
|
|
610
825
|
case EntityMatchSituation:
|
|
611
826
|
order = []string{"matchId", "competitionId", "eventId", "leagueId", "oddsRef", "data"}
|
|
827
|
+
case EntityMatchPhases:
|
|
828
|
+
order = []string{"matchId", "competitionId", "eventId", "leagueId", "fixture", "result", "innings"}
|
|
612
829
|
case EntityStatCategory:
|
|
613
830
|
order = []string{"name", "displayName", "abbreviation"}
|
|
614
831
|
case EntityPartnership:
|
|
@@ -715,7 +932,7 @@ func formatInningsTimelines(entity map[string]any) []string {
|
|
|
715
932
|
row := joinParts(
|
|
716
933
|
"Over "+valueString(over, "number"),
|
|
717
934
|
valueString(over, "runs")+" runs",
|
|
718
|
-
valueString(over, "wicketCount")
|
|
935
|
+
wicketCountLabel(valueString(over, "wicketCount")),
|
|
719
936
|
)
|
|
720
937
|
if row != "" {
|
|
721
938
|
lines = append(lines, " "+row)
|
|
@@ -754,6 +971,14 @@ func formatInningsTimelines(entity map[string]any) []string {
|
|
|
754
971
|
return lines
|
|
755
972
|
}
|
|
756
973
|
|
|
974
|
+
func wicketCountLabel(raw string) string {
|
|
975
|
+
raw = strings.TrimSpace(raw)
|
|
976
|
+
if raw == "" || raw == "0" {
|
|
977
|
+
return ""
|
|
978
|
+
}
|
|
979
|
+
return raw + " wkts"
|
|
980
|
+
}
|
|
981
|
+
|
|
757
982
|
func formatPlayerProfile(entity map[string]any) []string {
|
|
758
983
|
lines := make([]string, 0, 16)
|
|
759
984
|
if name := firstNonEmpty(valueString(entity, "displayName"), valueString(entity, "fullName"), valueString(entity, "name")); name != "" {
|
|
@@ -790,7 +1015,7 @@ func formatPlayerProfile(entity map[string]any) []string {
|
|
|
790
1015
|
}
|
|
791
1016
|
|
|
792
1017
|
func formatPlayerMatchView(entity map[string]any) []string {
|
|
793
|
-
lines := make([]string, 0,
|
|
1018
|
+
lines := make([]string, 0, 48)
|
|
794
1019
|
if player := valueString(entity, "playerName"); player != "" {
|
|
795
1020
|
lines = append(lines, "Player: "+player)
|
|
796
1021
|
}
|
|
@@ -806,13 +1031,21 @@ func formatPlayerMatchView(entity map[string]any) []string {
|
|
|
806
1031
|
}
|
|
807
1032
|
}
|
|
808
1033
|
if batting := sliceValue(entity, "batting"); len(batting) > 0 {
|
|
809
|
-
lines = append(lines,
|
|
1034
|
+
lines = append(lines, "Batting")
|
|
1035
|
+
lines = append(lines, formatStatCategoryList(batting)...)
|
|
810
1036
|
}
|
|
811
1037
|
if bowling := sliceValue(entity, "bowling"); len(bowling) > 0 {
|
|
812
|
-
lines = append(lines,
|
|
1038
|
+
lines = append(lines, "Bowling")
|
|
1039
|
+
lines = append(lines, formatStatCategoryList(bowling)...)
|
|
813
1040
|
}
|
|
814
1041
|
if fielding := sliceValue(entity, "fielding"); len(fielding) > 0 {
|
|
815
|
-
lines = append(lines,
|
|
1042
|
+
lines = append(lines, "Fielding")
|
|
1043
|
+
lines = append(lines, formatStatCategoryList(fielding)...)
|
|
1044
|
+
}
|
|
1045
|
+
if len(sliceValue(entity, "batting")) == 0 &&
|
|
1046
|
+
len(sliceValue(entity, "bowling")) == 0 &&
|
|
1047
|
+
len(sliceValue(entity, "fielding")) == 0 {
|
|
1048
|
+
lines = append(lines, "No match statistics categories available.")
|
|
816
1049
|
}
|
|
817
1050
|
return lines
|
|
818
1051
|
}
|
|
@@ -867,6 +1100,224 @@ func formatMatchView(entity map[string]any) []string {
|
|
|
867
1100
|
return lines
|
|
868
1101
|
}
|
|
869
1102
|
|
|
1103
|
+
func formatMatchSituation(entity map[string]any) []string {
|
|
1104
|
+
lines := make([]string, 0, 64)
|
|
1105
|
+
if matchID := firstNonEmpty(valueString(entity, "matchId"), valueString(entity, "competitionId")); matchID != "" {
|
|
1106
|
+
lines = append(lines, "Match: "+matchID)
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
live, _ := entity["live"].(map[string]any)
|
|
1110
|
+
if live == nil {
|
|
1111
|
+
if oddsRef := valueString(entity, "oddsRef"); oddsRef != "" {
|
|
1112
|
+
lines = append(lines, "Odds Ref: "+oddsRef)
|
|
1113
|
+
}
|
|
1114
|
+
if data, ok := entity["data"].(map[string]any); ok && len(data) > 0 {
|
|
1115
|
+
lines = append(lines, "Data: "+printableValue(data))
|
|
1116
|
+
}
|
|
1117
|
+
if len(lines) <= 1 {
|
|
1118
|
+
lines = append(lines, "No situation data available for this match.")
|
|
1119
|
+
}
|
|
1120
|
+
return lines
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if fixture := valueString(live, "fixture"); fixture != "" {
|
|
1124
|
+
lines = append(lines, "Fixture: "+fixture)
|
|
1125
|
+
}
|
|
1126
|
+
if status := valueString(live, "status"); status != "" {
|
|
1127
|
+
lines = append(lines, "Status: "+status)
|
|
1128
|
+
}
|
|
1129
|
+
scoreLine := joinParts(valueString(live, "score"), valueString(live, "overs"))
|
|
1130
|
+
if scoreLine != "" {
|
|
1131
|
+
lines = append(lines, "Score: "+scoreLine)
|
|
1132
|
+
}
|
|
1133
|
+
teamsLine := joinParts(
|
|
1134
|
+
"Batting "+valueString(live, "battingTeam"),
|
|
1135
|
+
"Bowling "+valueString(live, "bowlingTeam"),
|
|
1136
|
+
)
|
|
1137
|
+
if teamsLine != "" {
|
|
1138
|
+
lines = append(lines, teamsLine)
|
|
1139
|
+
}
|
|
1140
|
+
if snapshotAt := valueString(live, "snapshotAt"); snapshotAt != "" {
|
|
1141
|
+
lines = append(lines, "Snapshot: "+snapshotAt)
|
|
1142
|
+
}
|
|
1143
|
+
if stale := valueString(live, "stale"); stale == "true" {
|
|
1144
|
+
lines = append(lines, "Stale: true")
|
|
1145
|
+
if reason := valueString(live, "staleReason"); reason != "" {
|
|
1146
|
+
lines = append(lines, "Stale Reason: "+reason)
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
batters := sliceValue(live, "batters")
|
|
1151
|
+
if len(batters) > 0 {
|
|
1152
|
+
lines = append(lines, "Batters")
|
|
1153
|
+
for i, raw := range batters {
|
|
1154
|
+
batter, ok := raw.(map[string]any)
|
|
1155
|
+
if !ok {
|
|
1156
|
+
continue
|
|
1157
|
+
}
|
|
1158
|
+
score := fmt.Sprintf("%s(%s)", defaultNumeric(valueString(batter, "runs")), defaultNumeric(valueString(batter, "balls")))
|
|
1159
|
+
boundaries := joinParts("4s "+defaultNumeric(valueString(batter, "fours")), "6s "+defaultNumeric(valueString(batter, "sixes")))
|
|
1160
|
+
row := joinParts(
|
|
1161
|
+
firstNonEmpty(valueString(batter, "playerName"), valueString(batter, "playerId")),
|
|
1162
|
+
score,
|
|
1163
|
+
"SR "+defaultNumeric(valueString(batter, "strikeRate")),
|
|
1164
|
+
boundaries,
|
|
1165
|
+
)
|
|
1166
|
+
if strings.EqualFold(valueString(batter, "onStrike"), "true") {
|
|
1167
|
+
row = joinParts(row, "*")
|
|
1168
|
+
}
|
|
1169
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, row))
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
bowlers := sliceValue(live, "bowlers")
|
|
1174
|
+
if len(bowlers) > 0 {
|
|
1175
|
+
lines = append(lines, "Bowlers")
|
|
1176
|
+
for i, raw := range bowlers {
|
|
1177
|
+
bowler, ok := raw.(map[string]any)
|
|
1178
|
+
if !ok {
|
|
1179
|
+
continue
|
|
1180
|
+
}
|
|
1181
|
+
figures := fmt.Sprintf("%s-%s-%s-%s",
|
|
1182
|
+
oversLabelFromFields(valueString(bowler, "overs"), valueString(bowler, "balls")),
|
|
1183
|
+
defaultNumeric(valueString(bowler, "maidens")),
|
|
1184
|
+
defaultNumeric(valueString(bowler, "conceded")),
|
|
1185
|
+
defaultNumeric(valueString(bowler, "wickets")),
|
|
1186
|
+
)
|
|
1187
|
+
row := joinParts(
|
|
1188
|
+
firstNonEmpty(valueString(bowler, "playerName"), valueString(bowler, "playerId")),
|
|
1189
|
+
figures,
|
|
1190
|
+
"Econ "+defaultNumeric(valueString(bowler, "economy")),
|
|
1191
|
+
)
|
|
1192
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, row))
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
balls := sliceValue(live, "recentBalls")
|
|
1197
|
+
if len(balls) == 0 {
|
|
1198
|
+
balls = sliceValue(live, "currentOverBalls")
|
|
1199
|
+
}
|
|
1200
|
+
if len(balls) > 0 {
|
|
1201
|
+
lines = append(lines, "Recent Balls")
|
|
1202
|
+
for i, raw := range balls {
|
|
1203
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, summarizeDeliveryListItem(raw, EntityDeliveryEvent)))
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return lines
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
func formatMatchDuel(entity map[string]any) []string {
|
|
1210
|
+
lines := make([]string, 0, 48)
|
|
1211
|
+
if matchID := valueString(entity, "matchId"); matchID != "" {
|
|
1212
|
+
lines = append(lines, "Match: "+matchID)
|
|
1213
|
+
}
|
|
1214
|
+
if fixture := valueString(entity, "fixture"); fixture != "" {
|
|
1215
|
+
lines = append(lines, "Fixture: "+fixture)
|
|
1216
|
+
}
|
|
1217
|
+
if score := valueString(entity, "score"); score != "" {
|
|
1218
|
+
lines = append(lines, "Score: "+score)
|
|
1219
|
+
}
|
|
1220
|
+
lines = append(lines, "Duel: "+firstNonEmpty(valueString(entity, "batterName"), valueString(entity, "batterId"))+" vs "+firstNonEmpty(valueString(entity, "bowlerName"), valueString(entity, "bowlerId")))
|
|
1221
|
+
summary := joinParts(
|
|
1222
|
+
fmt.Sprintf("%s off %s", defaultNumeric(valueString(entity, "runs")), defaultNumeric(valueString(entity, "balls"))),
|
|
1223
|
+
"SR "+defaultNumeric(valueString(entity, "strikeRate")),
|
|
1224
|
+
"dots "+defaultNumeric(valueString(entity, "dots")),
|
|
1225
|
+
"4s "+defaultNumeric(valueString(entity, "fours")),
|
|
1226
|
+
"6s "+defaultNumeric(valueString(entity, "sixes")),
|
|
1227
|
+
"wkts "+defaultNumeric(valueString(entity, "wickets")),
|
|
1228
|
+
)
|
|
1229
|
+
if strings.TrimSpace(summary) != "" {
|
|
1230
|
+
lines = append(lines, "Summary: "+summary)
|
|
1231
|
+
}
|
|
1232
|
+
if snapshot := valueString(entity, "snapshotAt"); snapshot != "" {
|
|
1233
|
+
lines = append(lines, "Snapshot: "+snapshot)
|
|
1234
|
+
}
|
|
1235
|
+
balls := sliceValue(entity, "recentBalls")
|
|
1236
|
+
if len(balls) > 0 {
|
|
1237
|
+
lines = append(lines, "Recent Duel Balls")
|
|
1238
|
+
for i, raw := range balls {
|
|
1239
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, summarizeDeliveryListItem(raw, EntityDeliveryEvent)))
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return lines
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
func oversLabelFromFields(overs string, balls string) string {
|
|
1246
|
+
overs = strings.TrimSpace(overs)
|
|
1247
|
+
if overs != "" && overs != "0" {
|
|
1248
|
+
return overs
|
|
1249
|
+
}
|
|
1250
|
+
balls = strings.TrimSpace(balls)
|
|
1251
|
+
if balls == "" || balls == "0" {
|
|
1252
|
+
return "0.0"
|
|
1253
|
+
}
|
|
1254
|
+
b, err := strconv.Atoi(balls)
|
|
1255
|
+
if err != nil || b < 0 {
|
|
1256
|
+
return "0.0"
|
|
1257
|
+
}
|
|
1258
|
+
return fmt.Sprintf("%d.%d", b/6, b%6)
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
func formatMatchPhases(entity map[string]any) []string {
|
|
1262
|
+
lines := make([]string, 0, 64)
|
|
1263
|
+
if matchID := firstNonEmpty(valueString(entity, "matchId"), valueString(entity, "competitionId")); matchID != "" {
|
|
1264
|
+
lines = append(lines, "Match: "+matchID)
|
|
1265
|
+
}
|
|
1266
|
+
if fixture := valueString(entity, "fixture"); fixture != "" {
|
|
1267
|
+
lines = append(lines, "Fixture: "+fixture)
|
|
1268
|
+
}
|
|
1269
|
+
if result := valueString(entity, "result"); result != "" {
|
|
1270
|
+
lines = append(lines, "Result: "+result)
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
for _, raw := range sliceValue(entity, "innings") {
|
|
1274
|
+
inn, ok := raw.(map[string]any)
|
|
1275
|
+
if !ok {
|
|
1276
|
+
continue
|
|
1277
|
+
}
|
|
1278
|
+
lines = append(lines, "")
|
|
1279
|
+
lines = append(lines, joinParts(
|
|
1280
|
+
firstNonEmpty(valueString(inn, "teamName"), valueString(inn, "teamId")),
|
|
1281
|
+
"innings "+valueString(inn, "inningsNumber")+"/"+valueString(inn, "period"),
|
|
1282
|
+
valueString(inn, "score"),
|
|
1283
|
+
))
|
|
1284
|
+
|
|
1285
|
+
lines = append(lines, " Phases")
|
|
1286
|
+
for _, key := range []string{"powerplay", "middle", "death"} {
|
|
1287
|
+
phaseMap, ok := inn[key].(map[string]any)
|
|
1288
|
+
if !ok {
|
|
1289
|
+
continue
|
|
1290
|
+
}
|
|
1291
|
+
name := firstNonEmpty(valueString(phaseMap, "name"), strings.Title(key))
|
|
1292
|
+
runs := defaultNumeric(valueString(phaseMap, "runs"))
|
|
1293
|
+
wickets := defaultNumeric(valueString(phaseMap, "wickets"))
|
|
1294
|
+
overs := defaultNumeric(valueString(phaseMap, "overs"))
|
|
1295
|
+
runRate := defaultNumeric(valueString(phaseMap, "runRate"))
|
|
1296
|
+
phaseLine := joinParts(
|
|
1297
|
+
name,
|
|
1298
|
+
"runs "+runs,
|
|
1299
|
+
"wkts "+wickets,
|
|
1300
|
+
"ov "+overs,
|
|
1301
|
+
"rr "+runRate,
|
|
1302
|
+
)
|
|
1303
|
+
lines = append(lines, " - "+phaseLine)
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
bestOver := valueString(inn, "bestScoringOver")
|
|
1307
|
+
bestRuns := valueString(inn, "bestScoringOverRuns")
|
|
1308
|
+
if bestOver != "" && bestRuns != "" {
|
|
1309
|
+
lines = append(lines, " Best Over: over "+bestOver+" ("+bestRuns+" runs)")
|
|
1310
|
+
}
|
|
1311
|
+
collapseOver := valueString(inn, "collapseOver")
|
|
1312
|
+
collapseWickets := valueString(inn, "collapseWickets")
|
|
1313
|
+
if collapseOver != "" && collapseWickets != "" && collapseWickets != "0" {
|
|
1314
|
+
lines = append(lines, " Pressure Over: over "+collapseOver+" ("+collapseWickets+" wickets)")
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
return lines
|
|
1319
|
+
}
|
|
1320
|
+
|
|
870
1321
|
func formatPlayerStatistics(entity map[string]any) []string {
|
|
871
1322
|
lines := make([]string, 0, 64)
|
|
872
1323
|
|
|
@@ -1081,6 +1532,149 @@ func formatTeamLeaders(entity map[string]any) []string {
|
|
|
1081
1532
|
return lines
|
|
1082
1533
|
}
|
|
1083
1534
|
|
|
1535
|
+
func formatStatCategoryList(items []any) []string {
|
|
1536
|
+
lines := make([]string, 0, len(items)*4)
|
|
1537
|
+
for i, rawCategory := range items {
|
|
1538
|
+
category := mapFromAny(rawCategory)
|
|
1539
|
+
if category == nil {
|
|
1540
|
+
continue
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
categoryName := firstNonEmpty(valueString(category, "displayName"), valueString(category, "name"), "Category")
|
|
1544
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, categoryName))
|
|
1545
|
+
|
|
1546
|
+
stats := sliceValue(category, "stats")
|
|
1547
|
+
if len(stats) == 0 {
|
|
1548
|
+
continue
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
limit := len(stats)
|
|
1552
|
+
if limit > 16 {
|
|
1553
|
+
limit = 16
|
|
1554
|
+
}
|
|
1555
|
+
for j := 0; j < limit; j++ {
|
|
1556
|
+
statMap, ok := stats[j].(map[string]any)
|
|
1557
|
+
if !ok {
|
|
1558
|
+
continue
|
|
1559
|
+
}
|
|
1560
|
+
label := firstNonEmpty(valueString(statMap, "displayName"), valueString(statMap, "name"), valueString(statMap, "abbreviation"))
|
|
1561
|
+
value := firstNonEmpty(valueString(statMap, "displayValue"), valueString(statMap, "value"))
|
|
1562
|
+
row := joinParts(label, value, bracket(valueString(statMap, "abbreviation")))
|
|
1563
|
+
if row == "" {
|
|
1564
|
+
continue
|
|
1565
|
+
}
|
|
1566
|
+
lines = append(lines, fmt.Sprintf(" - %s", row))
|
|
1567
|
+
}
|
|
1568
|
+
if len(stats) > limit {
|
|
1569
|
+
lines = append(lines, fmt.Sprintf(" - ... %d more", len(stats)-limit))
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return lines
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
func formatStandingsGroupList(items []any) []string {
|
|
1576
|
+
lines := make([]string, 0, len(items)*8+1)
|
|
1577
|
+
lines = append(lines, fmt.Sprintf("Standings Groups (%d)", len(items)))
|
|
1578
|
+
|
|
1579
|
+
for i, rawGroup := range items {
|
|
1580
|
+
group := mapFromAny(rawGroup)
|
|
1581
|
+
if group == nil {
|
|
1582
|
+
continue
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
groupID := firstNonEmpty(valueString(group, "groupId"), valueString(group, "id"), fmt.Sprintf("%d", i+1))
|
|
1586
|
+
seasonID := valueString(group, "seasonId")
|
|
1587
|
+
header := "Group " + groupID
|
|
1588
|
+
if seasonID != "" {
|
|
1589
|
+
header = joinParts(header, "Season "+seasonID)
|
|
1590
|
+
}
|
|
1591
|
+
lines = append(lines, header)
|
|
1592
|
+
|
|
1593
|
+
entries := sliceValue(group, "entries")
|
|
1594
|
+
if len(entries) == 0 {
|
|
1595
|
+
lines = append(lines, " No standings entries available.")
|
|
1596
|
+
continue
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
for entryIndex, rawEntry := range entries {
|
|
1600
|
+
team := mapFromAny(rawEntry)
|
|
1601
|
+
if team == nil {
|
|
1602
|
+
continue
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
rank := standingsStatValueFromTeam(team, "rank", "position")
|
|
1606
|
+
if rank == "" {
|
|
1607
|
+
rank = fmt.Sprintf("%d", entryIndex+1)
|
|
1608
|
+
}
|
|
1609
|
+
teamName := firstNonEmpty(valueString(team, "shortName"), valueString(team, "name"), valueString(team, "id"))
|
|
1610
|
+
played := standingsStatValueFromTeam(team, "matchesplayed", "played", "matches")
|
|
1611
|
+
won := standingsStatValueFromTeam(team, "wins", "won")
|
|
1612
|
+
lost := standingsStatValueFromTeam(team, "losses", "lost")
|
|
1613
|
+
points := standingsStatValueFromTeam(team, "matchpoints", "points", "pts")
|
|
1614
|
+
nrr := standingsStatValueFromTeam(team, "netrunrate", "nrr", "runrate")
|
|
1615
|
+
|
|
1616
|
+
row := joinParts(
|
|
1617
|
+
fmt.Sprintf("#%s %s", rank, teamName),
|
|
1618
|
+
nonEmptyLabel("P", played),
|
|
1619
|
+
nonEmptyLabel("W", won),
|
|
1620
|
+
nonEmptyLabel("L", lost),
|
|
1621
|
+
nonEmptyLabel("Pts", points),
|
|
1622
|
+
nonEmptyLabel("NRR", nrr),
|
|
1623
|
+
)
|
|
1624
|
+
if strings.TrimSpace(row) == "" {
|
|
1625
|
+
row = joinParts(fmt.Sprintf("#%s %s", rank, teamName), valueString(team, "scoreSummary"))
|
|
1626
|
+
}
|
|
1627
|
+
lines = append(lines, " "+row)
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return lines
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
func standingsStatValueFromTeam(team map[string]any, names ...string) string {
|
|
1634
|
+
if len(names) == 0 || team == nil {
|
|
1635
|
+
return ""
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
targets := map[string]struct{}{}
|
|
1639
|
+
for _, name := range names {
|
|
1640
|
+
key := normalizeStatName(name)
|
|
1641
|
+
if key != "" {
|
|
1642
|
+
targets[key] = struct{}{}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
extensions, ok := team["extensions"].(map[string]any)
|
|
1647
|
+
if !ok || extensions == nil {
|
|
1648
|
+
return ""
|
|
1649
|
+
}
|
|
1650
|
+
records, ok := extensions["records"].([]any)
|
|
1651
|
+
if !ok || len(records) == 0 {
|
|
1652
|
+
return ""
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
for _, rawRecord := range records {
|
|
1656
|
+
record := mapFromAny(rawRecord)
|
|
1657
|
+
if record == nil {
|
|
1658
|
+
continue
|
|
1659
|
+
}
|
|
1660
|
+
for _, rawStat := range sliceValue(record, "stats") {
|
|
1661
|
+
stat := mapFromAny(rawStat)
|
|
1662
|
+
if stat == nil {
|
|
1663
|
+
continue
|
|
1664
|
+
}
|
|
1665
|
+
nameKey := normalizeStatName(firstNonEmpty(valueString(stat, "name"), valueString(stat, "type")))
|
|
1666
|
+
if _, ok := targets[nameKey]; !ok {
|
|
1667
|
+
continue
|
|
1668
|
+
}
|
|
1669
|
+
value := firstNonEmpty(valueString(stat, "displayValue"), valueString(stat, "value"))
|
|
1670
|
+
if value != "" {
|
|
1671
|
+
return value
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return ""
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1084
1678
|
func formatAnalysisView(entity map[string]any) []string {
|
|
1085
1679
|
lines := make([]string, 0, 64)
|
|
1086
1680
|
if command := valueString(entity, "command"); command != "" {
|
|
@@ -1350,9 +1944,9 @@ func overBallLabel(entity map[string]any) string {
|
|
|
1350
1944
|
return ""
|
|
1351
1945
|
}
|
|
1352
1946
|
if ball == "" {
|
|
1353
|
-
return
|
|
1947
|
+
return over
|
|
1354
1948
|
}
|
|
1355
|
-
return
|
|
1949
|
+
return over + "." + ball
|
|
1356
1950
|
}
|
|
1357
1951
|
|
|
1358
1952
|
func involvementLabel(entity map[string]any) string {
|