Skip to content

Add tennis module (Live Tennis API) - #2158

Open
bensynapse wants to merge 1 commit into
wtfutil:trunkfrom
bensynapse:add-tennis-module
Open

Add tennis module (Live Tennis API)#2158
bensynapse wants to merge 1 commit into
wtfutil:trunkfrom
bensynapse:add-tennis-module

Conversation

@bensynapse

Copy link
Copy Markdown

What this adds

A new tennis module that displays live, upcoming, or completed tennis matches from the Live Tennis API, in the same shape as the existing football and nbascore sports modules.

Live matches render per-set games, the current game points (including tiebreaks, shown as TB 5-3), and a green * beside the player who is serving. Completed matches bold the winner. Upcoming matches show their scheduled start time.

┌───────────────────── Tennis ATP (live) 1 ─────────────────────┐
│Sinner (1) 6-3 4-6 2-1* (40-AD) vs Alcaraz (2) • Tampere QF    │
│Paolini (4) 6-6 (TB 5-3) vs Cocciaretto (38)* • Palermo SF     │
│Djokovic (7) vs Musetti (10) • Umag R16 • 🕙 2026-07-26 18:30  │
└───────────────────────────────────────────────────────────────┘

Configuration

tennis:
  apiKey: "..."          # or WTF_TENNIS_API_KEY, or `wtfutil save-secret tennis`
  enabled: true
  tour: "atp"            # optional: atp | wta | omit for all tours
  status: "live"         # optional: live | upcoming | completed (default: live)
  matchLimit: 8          # optional (default: 10)
  refreshInterval: 60s
  position: {top: 0, left: 0, height: 2, width: 2}
  title: "🎾"

A free API key (1,000 requests/day) is available at https://livetennisapi.com/subscribe/free, so the module is usable without payment. Key resolution follows modules/football: apiKeyapikey$WTF_TENNIS_API_KEYcfg.ModuleSecret. baseURL is also configurable (default https://api.livetennisapi.com/api/public/v1) for anyone proxying the API; it is what lets the test suite run end-to-end without a key.

Keyboard: / help, r refresh, h/ and l/ cycle live → upcoming → completed, mirroring nbascore.

Files

  • modules/tennis/{client,types,settings,widget,display,keyboard}.go
  • modules/tennis/{client,settings,widget,display,keyboard}_test.go
  • app/widget_maker.go — +4 lines (one import, one case), the single registration site

Nothing else in the tree is touched.

Testing

Table-driven tests throughout, all HTTP served by httptestno API key is required to run the suite, and none is embedded anywhere. Covered: happy path and JSON decoding; query-param construction and omission; 401, 429, 500, malformed JSON and connection-refused paths; the no-key / empty / error / rate-limited widget states; the matchLimit cap; Disabled(); YAML parsing, defaults and key-source precedence; and every rendering branch (tiebreak, winner-bold, missing player → TBD, uneven per-set arrays, sets-only fallback).

$ go build ./...                                    # exit 0
$ go vet ./...                                      # exit 0
$ gofmt -l .                                        # no output
$ golangci-lint run ./... --timeout=10m             # 0 issues (v2.11, per CI)
$ go test -race -cover ./modules/tennis/...
ok  github.com/wtfutil/wtf/modules/tennis  1.114s  coverage: 98.0% of statements

Also verified by running the built binary under a pty with the module enabled and rendering: the no-key setup hint, a full board of matches, and the 401 error state — no panics in any of them.

Two small things worth flagging

  1. tview parses square brackets in a widget title as style tags, so a title like Tennis [ATP] silently loses the [ATP]. The title format avoids brackets, and a test asserts it stays that way.
  2. Player and tournament names come from the API into a dynamic-colors TextView, so they are passed through tview.Escape() (as jira, subreddit, textfile and todo do).

Documentation

Per CONTRIBUTING.md, the static docs live in wtfutil/wtfdocs. A companion PR adding content/modules/sports/tennis.md is ready — settings.SetDocumentationPath("sports/tennis") here points at it. Happy to open it at the same time, or on request.

Disclosures

Vendor disclosure: I work on the Live Tennis API, the service this module consumes. I wrote this module because I wanted it in my own dashboard, but you should weigh it accordingly. Concretely: the module only calls GET /matches; there is no telemetry, no analytics, no affiliate parameter, and no default API key. Every account tier the module needs is free (1,000 req/day). If you would rather not carry a vendor-authored module, or would prefer it to sit behind a more generic interface, say the word and I'll adapt or withdraw it.

AI-assistance disclosure: this module was written with AI assistance (Claude). I have read every line, and the verification above — build, vet, gofmt, golangci-lint at the CI-pinned version, -race tests, and three live runs of the built binary — is real output from my machine, not model-generated text. CONTRIBUTING.md has no AI policy, so I am disclosing this proactively; if the project would prefer such contributions be handled differently, tell me and I'll follow that.

I've read CONTRIBUTING.md and the Code of Conduct. No CLA or DCO sign-off appears to be required — happy to comply if I've missed one.

@bensynapse

Copy link
Copy Markdown
Author

One correction to my own PR description, before a reviewer trips over it.

I wrote that a free API key makes the module usable without payment. That is true for status: live and status: upcoming, which is the module's default and its main use — but not for status: completed. Checking the API's authorisation code, the completed-match listing sits behind the paid History capability, so a free key gets 403 upgrade_required there.

That matters here because h/l cycle live → upcoming → completed, so a free-key user can arrow into a state that errors. The module surfaces it as an error line rather than misbehaving, but the docs shouldn't have implied otherwise.

I'd rather fix this than leave it ambiguous. Happy to do either:

  1. drop completed from the module entirely (live + upcoming only, which is the honest free-tier surface), or
  2. keep it and map 403 to an explicit "completed matches require a paid plan" line, plus a README note.

I lean toward (1) — it keeps the module fully usable on a free key with no upsell surface in your tree, which seems more in keeping with the other sports modules. Say which you prefer and I'll push it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant