Mediabot 3.6dev · Development journal · MB959–MB1000 · 28 October 2026
The previous article, Alohomora: Opening Mediabot to Its Next Contributors, closed at MB958. It gave a growing codebase clearer rooms: games, statistics, calendars and user commands acquired source owners that a contributor could find and understand.
The next chapter puts those boundaries to work. A substantial Perl plugin turns the dictionary into a competitive word game. Trivia gains a French question source. Everyday commands receive a sustained round of corrections, from weighted polls to reminder races and database failures.
That work brings us to MB1000, the latest numbered development checkpoint. Mediabot continues along the 3.6dev line, with a stronger demonstration of its plugin API and considerably more attention paid to what happens when an operation fails halfway through.
The castle has a new game room. Its doors, clocks and scorekeepers have also received some careful maintenance. 🏰
Le mot le plus long, introduced in MB959, gives the Perl API v3 a substantial showcase. Its rules and entry point live in plugins/longest-word-v3/, while the core provides a deliberately narrow dictionary service.
The supplied dictionary contains 369,085 distinct words. Its MariaDB table, DICT.DICT, stores each word alongside a signature made from its letters in sorted order. The solver uses indexed, parameterized signature lookups to find words that can be built from a rack.
That lets the plugin announce the longest possible word length as a round begins, while keeping the actual solutions hidden until the closing result.
The gameplay evolved through direct testing on dev:
For example, if the best possible word has seven letters, finding POUFFAS wins that round for 7 + 3 = 10 points. There is no reason to keep the remaining clock running after someone has reached the maximum.
The default match uses five rounds, ten letters per draw and up to fifty seconds per round. These remain channel settings rather than hard-coded requirements.
Scoring now extends beyond one match: personal totals, all-time rankings and ISO weekly rankings are available, with pagination and tied ranks. Weekly accounting uses the configured score timezone, Europe/Paris by default.
Saving matters as much as adding. Completed rounds carry a receipt that prevents a retried save from awarding their points twice. A save that cannot be confirmed pauses progression. The week belongs to the actual round outcome, including an early winning word, rather than a later retry.
The game also exercises the infrastructure that makes a useful plugin possible.
Dictionary work runs in a child worker with its own read-only MariaDB connection. The IRC event loop stays available while the query runs. The core bounds the work, validates returned words and refuses to present an incomplete result as an authoritative maximum.
The plugin receives a dictionary result through a capability-controlled facade. It does not receive the bot’s SQL handle or private configuration. Its per-channel score collections use validated private storage with locking, atomic replacement and revision checks.
Activation and channel policy remain explicit. Policy changes, stopped games and old queued guesses cannot bring an abandoned round back to life.
MB964 strengthens that last boundary further: dictionary callbacks retain the correct command, event or job identity for failure accounting and quarantine. A late word-event result cannot bypass a quarantine that was applied while its query was running.
This is a useful foundation for future Perl plugins: asynchronous data access, scoped events, scheduled work and durable channel state, all exercised by an actual game.
French Trivia arrives alongside the existing English Open Trivia DB integration.
The French catalog contains 6,044 OpenQuizzDB questions, across 14 categories and three difficulty levels. It ships with attribution, provenance, the CC BY-SA 4.0 license and a reproducible catalog builder.
It is a pinned 12 February 2022 archival selection. Its source wording and answers are retained after the documented filtering; the catalog’s age remains explicit.
Language selection is straightforward. With the instance’s usual command prefix, request:
trivia fr
trivia fr histoire difficile
trivia fr informatique facile
trivia en
A new game without a language keeps the historical English default. The original Open Trivia DB categories, request parsing and retry path remain available. French questions run locally inside the existing asynchronous worker and need no remote question service.
French answer matching accepts omitted accents, case differences, ordinary spacing variations and supported ligatures. The original accented answer remains available for hints and results. Matching still respects word boundaries.
Both languages use the existing channel/nickname score storage. Switching sources preserves those scores. The French selector also avoids the channel’s last 64 French question identifiers while fresh matching choices remain.
The game’s lifecycle receives attention too. Stopping Trivia now clears pending and between-question series as well as a live question, under the existing Master authorization. Late callbacks stay cancelled, and a later plain trivia command cannot silently resume an abandoned series. Multi-round games retain their existing manual next-question progression.
Several fixes are immediately visible to anyone playing with the bot.
| Area | Corrected behaviour |
|---|---|
playful-v3 random draws |
Coin flips no longer alternate mechanically because of the private generator’s parity. Coins, dice and choices use the corrected random-index helper. |
roll |
Omitting the dice token keeps the default 1d6 while correctly applying options such as +2, adv or dis, in both native and plugin paths. |
choose last |
A previous result of 0 is a real result and can be recalled. |
| Weighted choices | Repeated weighted slots must still represent at least two case-insensitively distinct outcomes. Valid weights keep their intended influence. |
| Poll creation | Blank questions or options are rejected before an existing poll and its ballots can be replaced. |
| Weighted polls | The documented timeout-first form, such as `poll 60 weighted Question |
Duels receive a deeper consistency fix. The winner’s karma point and the loser’s deduction are written together in one statement against the canonical InnoDB table. A failure on the losing side cannot leave only the winner’s point applied.
Wins, losses, streaks and success bookkeeping advance after the pair is confirmed. An uncertain acknowledgement is reported without automatically replaying the write.
Nickname parsing is more precise as well. A karma target such as Alice-Bob remains one nickname; it cannot accidentally turn into a vote for an unrelated Bob.
A large part of the bug hunt follows one rule: a failed database read must remain distinguishable from a successful empty result.
A question returning no factoid is ordinary. A database failing to answer is a different event. The same distinction matters for quote searches, counts, rankings, birthday lookups and reminder lists.
The corrected paths check the result of the complete read before publishing it. A failed fetch cannot become a fabricated zero count, a false “not found” answer or a plausible-looking partial list. Cleanup runs independently, after the outcome is known.
Factoid ownership and accounting become more precise. Account authorship requires an authenticated account identity. Deletion authorizes and removes the exact row that was observed, so a replacement created under the same keyword is preserved. Recall accounting follows that observed row too.
Personal notes receive a related fix at the storage/cache boundary. The bot confirms loading, insertion identifiers and deletion before treating its in-memory view as authoritative. After an uncertain write, it requires a fresh read before proceeding. A cache entry can no longer stand in for a note that was never confirmed as saved.
Quote commands receive corrections across native and API v3 paths: the native add alias reaches its proper context-aware handler, author statistics use the canonical user key, and incomplete lookups do not authorize an insertion or successful recall accounting.
These changes make familiar commands more dependable while keeping their established access rules, channel scope and successful output.
Reminder delivery now checks that the selected stored message still matches when it claims the reminder. A snooze completed after the original read remains pending for its new deadline. Conditional snooze updates also preserve another confirmed edit instead of overwriting it.
Daily, weekly and wall-clock reminders follow the server’s local calendar across daylight-saving changes. Relative delays retain their elapsed-time meaning. Scheduled message validation also accounts for the metadata added to the stored text.
Seen lookups keep the requested channel’s identity. Failing to resolve that channel cannot silently broaden the lookup to another channel’s history. Errors while reading saved presence information stay distinct from confirmed absence.
Recent work applies the same care to list output: remind show, remind list and remindlist confirm a complete read before displaying reminders or declaring the list empty.
Testing the word plugin exposed a partyline problem outside the game itself. A Unicode reply, including punctuation in an unknown-plugin message, could reach a byte-only asynchronous socket write and terminate the process.
The corrected boundary encodes text before queueing it, while retaining already encoded UTF-8 bytes and the existing Telnet negotiation behaviour. Regression checks include deferred and partial writes, so a successful immediate write is not the only case exercised.
Service-command logging receives another focused correction: credential masking recognizes leading spaces and tabs, with Unicode whitespace semantics scoped to the redaction helper. It changes the log copy while preserving the actual outgoing payload.
Numerical commands also lose a few surprising edge cases. round() preserves the intended rounding rule near fractional boundaries and for large exactly represented integers. Converting a temperature to the same canonical unit preserves its value, including very small Kelvin or Fahrenheit inputs.
Weather comparison now checks the age of each cached city separately. Entries older than fifteen minutes, missing a timestamp or dated in the future receive the existing refresh guidance. Comparison remains cache-only.
The bug hunt also prompted a review of the tests themselves.
MB986 reduces repeated assertion matrices in fourteen test files. Database failure coverage remains with the implementation; facade and alias checks keep responsibility for verifying the connection to it. Independent data variations no longer have to be multiplied through every error mode and entry point.
The measured full-suite count falls from 54,713 to 39,658 assertions, removing 15,055 repeated assertions, about 27.5%, while retaining all 1,203 test files in that checkpoint.
The measured runtime changes from 222 to 218 seconds. The useful result is less duplication and a clearer suite to maintain; that run does not establish a significant speed improvement.
Subsequent fixes add focused regressions again. Each starts with a reproducible failure, tests the relevant native or plugin path, and keeps the full unfiltered suite as the final acceptance check.
The native updater also gets a code correction of its own: it preserves the repository’s configured origin while allowing Git to apply temporary transport rewrites. Local repository and release-rotation tests cover that behaviour.
The thousandth checkpoint is fittingly about birthdays.
Birthday reads had already gained explicit failure handling in MB994. MB1000 extends that care to the UPDATE statements used when setting or clearing a date.
Prepare and execute exceptions are contained. An unconfirmed write receives a private database-error notice, with no success acknowledgement or success audit. Cleanup and failure logging cannot override the write outcome. Clearing a date through birthday add user <name> also stops interpolating an undefined value into its audit text.
A confirmed write keeps its acknowledgement even if statement cleanup fails. A lost acknowledgement remains an uncertain outcome: the UPDATE may already have happened, so the handler does not automatically retry it. The legitimate DBI true-zero result remains accepted when a date is already empty.
The 15 new assertions exercise the real registered command with SQLite, including failures before execution, a real write followed by a lost acknowledgement, cleanup faults, access guards and preservation of other users. Eight fail against the original implementation; all fifteen pass with the correction.
The latest evidence is recorded as a locally qualified MB1000 candidate:
| Check | Result |
|---|---|
| Targeted birthday selection | 81 / 81 assertions passed |
| Full unfiltered suite | 39,942 / 39,944 assertions passed, across 1,217 files |
| Security audit | GO — all 37 invariants |
| Startup integrity and command inventory | Passed |
The full suite records an isolated updater-rehearsal failure and a process-identity observation failure. Both are also reproduced against the unchanged snapshot in the local runner; the identity observation is intermittent across isolated runs. They remain visible in the qualification record, and the complete local run is not reported as green.
The supplied development snapshot contains the work through MB999. MB1000 is the prepared follow-up patch; applying it and completing acceptance on the development host remain to be confirmed at the time of this article.
That leaves plenty worth celebrating: a Perl plugin with real gameplay and durable scores, French Trivia alongside the original English source, fairer small games, and commands that handle difficult outcomes more honestly.
Raise a butterbeer to MB1000. Then bring the next reproducible bug to the workbench. 🪄
Explore Mediabot · Developer guide · Changelog
Prepared from the supplied development snapshots, source documentation and recorded qualification results. This journal covers Mediabot development from MB959 through the prepared MB1000 correction. Repository links follow master.
You must be logged in to reply.