Skip to content

[tennis] create tennis.pl — live tennis scores, rankings and head-to-head - #918

Open
bensynapse wants to merge 1 commit into
irssi:masterfrom
bensynapse:tennis-script
Open

bensynapse wants to merge 1 commit into
irssi:masterfrom
bensynapse:tennis-script

Conversation

@bensynapse

Copy link
Copy Markdown

Vendor disclosure, first: I am the operator of the Live Tennis API, the commercial service this script reads. The free tier is genuinely free, no card, 100 requests a day, but you should weigh this knowing the author benefits from it. /help tennis names the service, says a key is required, and names which plan each command needs. Happy to add stronger wording, move the script, or drop it if a vendor-authored script is not something the catalogue wants. AI tooling was used while writing it; the code and the checks below are mine.

This would be the catalogue's first sports-data script. I checked all 561 and there is no scores script of any kind for any sport.

What it does

/tennis live [n] for matches in play, /tennis match <id> for one match, /tennis rank atp|wta [n] for a ranking table, and /tennis h2h <a> vs <b> for a head-to-head. Output is shaped for an IRC window: surnames, set scores, current points, * on the server, BP on a break point.

Notes for review

  • Non-blocking. The request runs in a forked child answering over a pipe with Irssi::pidwait_add, following omdb.pl. The child leaves through POSIX::_exit so a die in the renderer cannot unwind back into the copy of irssi that fork() left it holding. exit() would be no escape either, since _testing/_irssi_test.pl turns it into a croak. Payloads are clamped to 50 short lines, far under a pipe buffer, so the fd_retrieve in the pidwait handler cannot block.
  • Never on a timer. Command-driven only. The free tier is 100 requests a day and a background poll would spend it on nobody's behalf.
  • Dependencies are all core and all have precedent here: HTTP::Tiny in two other scripts, JSON::PP in six, Storable in seven, POSIX::_exit in twenty-three. modules is empty accordingly, which your own harness independently computes and agrees with.
  • Tier limits are visible. The rankings command needs a PRO plan and head-to-head needs BASIC. /help tennis says so, and a key without them gets one line naming the plan rather than a score or a trace. Non-2xx, invalid JSON, rate limiting in all three of its documented forms, and a merged match id each reduce to a single readable line.
  • selfcheckcmd runs 33 offline cases covering break-point derivation and score formatting, then probes a health endpoint that needs no key, so it works on a bare install.

Verification

Your exact perlcritic invocation from _testing/run-test.zsh reports source OK with zero violations. For calibration, the same command reports one violation on omdb.pl.

The script loads warning-free under $^W = 1 in irssi 1.4.5, with your harness reporting LOAD, HDR and CRIT all green and a perlcritic score of 0. stderr.log is absent, which is what a warning-free load looks like given the startup file sets warnings on.

The pure logic is covered by 337 assertions in a harness I deliberately kept out of this pull request, since the catalogue is one file per script and expects no test files. It walks all 36 legal point pairs against both servers, all 169 tiebreak counts, ten null-safety shapes, and the full documented payload shapes. It also asserts the child-exit guard by installing an END block, forking five children and killing four of them inside the renderer: the sentinel prints exactly once, which it would not if any child had unwound through Perl's normal exit path.

Please load it in a real client before merging

I could not. There is no irssi on the host I built this on, and no API key in that environment, so there has been no live call and no interactive session. I did get a real irssi to load it warning-free in a container, but nobody typed a command and watched a window. The column widths in particular deserve a human eye at a real terminal width, and the help output deserves one /help in anger.

One more thing worth knowing: selfcheckcmd is not exercised by your CI, since there is no self-check helper in the catalogue and run-test.zsh never runs it. So the live half of /tennis check is untested end to end. I designed it so the offline half is authoritative and an unreachable health probe reports as a network condition rather than a defect in the file.

@dgl

dgl commented Sep 12, 2026

Copy link
Copy Markdown
Member

Dependencies are all core and all have precedent here: HTTP::Tiny in two other scripts, JSON::PP in six, Storable in seven, POSIX::_exit in twenty-three. modules is empty accordingly, which your own harness independently computes and agrees with.

Not quite, this uses a https URL so it will need IO::Socket::SSL.

Please load it in a real client before merging
I could not

So while I personally currently have no problem with script authors using LLMs (although the Irssi project as a whole does not yet have an LLM policy), we aren't going to be approving scripts which you can't even test yourself.

A human needs to at least test and use the script I feel.

Live tennis scores from the Live Tennis API: matches in play, one match in
detail, the ATP/WTA ranking table and a head-to-head record.

The request runs in a forked child that answers over a pipe, so a slow or
unreachable server never stalls irssi's main loop. The child leaves through
POSIX::_exit so a die in the renderer cannot unwind back into the copy of
irssi that fork() left it holding.

Command driven only, never on a timer: the free tier allows 100 requests a
day and a poll would spend that on nobody's behalf. /tennis rank needs a PRO
plan and /tennis h2h a BASIC one; /help tennis says so, and a key without
them gets one line saying so rather than a score.
@bensynapse

Copy link
Copy Markdown
Author

You were right on both counts, and the second point found a bug that nothing else would have.

IO::Socket::SSL. Confirmed and fixed: modules now declares it. I checked rather than argued — Module::CoreList->first_release says HTTP::Tiny, JSON::PP, Storable and POSIX are all core, while IO::Socket::SSL and Net::SSLeay are not. My earlier claim was wrong, and so was the harness's computed modules value, since Perl::PrereqScanner only sees what the file names and the SSL requirement is implicit in the URL scheme.

On testing it myself. That is a fair line and I would rather meet it than argue it, so I went and did it: irssi 1.4.5 in a Debian container, the script loaded with $^W = 1, then driven through a pty with real keystrokes.

It immediately failed in a way no static check had caught:

tennis: self check: ok - the API health probe did not answer (could not reach
api.livetennisapi.com: Couldn't find a CA bundle with which to verify the SSL
certificate. Try installing Mozilla::CA from CPAN)

HTTP::Tiny verifies certificates but has to be told where the trust store is. It looks for $ENV{SSL_CERT_FILE} and then Mozilla::CA, neither of which is guaranteed on a box that got IO::Socket::SSL from its package manager. So on a machine with SSL support but no Mozilla::CA, every single request failed. That is precisely the class of bug that only shows up when someone actually runs the thing.

Fixed by resolving the system bundle before falling back to whatever HTTP::Tiny can find on its own: $ENV{SSL_CERT_FILE}, then the Debian/Ubuntu/Arch/Alpine path, the Fedora/RHEL path, the openSUSE path, and the BSD and macOS paths. Proven both ways in the container:

without ca-certificates:  resolved (none)   -> 599, and the message tells you to install Mozilla::CA
with ca-certificates:     resolved /etc/ssl/certs/ca-certificates.crt
                          -> 200 {"status":"ok","version":"v1"}

So the network path is now verified against the live service, not just reasoned about. The health endpoint needs no key, which is why the self-check uses it.

Back in the client, after the fix:

-!- Irssi: Loaded script tennis
tennis: self check: 33/33 offline cases pass
tennis: self check: ok
tennis: no API key yet - /set tennis_apikey <key>, and a free key (no card) is at https://livetennisapi.com

/help tennis renders correctly, and stderr is empty, so it is a warning-free load under $^W = 1. perlcritic --theme certrule --exclude RequireEndWithOne -2 --verbose 5 still reports source OK.

What is still not done, and I am not going to claim otherwise. I have exercised the load, the self-check, the help, the no-key path and the live TLS handshake, but I have not yet run /tennis live against a key and watched real scores land in a window, because the key sits with my colleague rather than on the build machine. Given what you said, that is exactly the step that should happen before you spend review time here.

So please treat this as not ready, and do not review it yet. I will come back when a person has used it against a live key for a while, ideally with the column widths checked at a real terminal width. If you would rather I closed it in the meantime and reopened when that is done, say so and I will.

For what it is worth on the policy question: I would rather your project ended up with a rule you are comfortable with than have this merged under an unsettled one.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants