The previous chapter ended with Trivia finally returning questions instead of disappearing into a silent worker timeout.
The next twenty rounds were quieter on IRC, but no less important. They dealt with the kind of details that decide whether a long-lived bot remains understandable and efficient, or slowly accumulates contradictory rules, hidden database traffic and edge cases that only appear at the worst possible hour.
This pass covered configuration completeness, YouTube durations, birthdays around daylight-saving time, achievement-file integrity, Trivia hints, startup ordering, URL parsing, channel-name normalisation, database query reduction and a more useful Claude summary command.
A final independent audit then found three gaps before the commit and closed them as MB416.
The project remains on the 3.2dev line. This is not the 3.3 release.
As always:
No database schema was changed.
Mediabot read four settings that were absent from mediabot.sample.conf:
anthropic.TEMPERATURE
main.ACHIEVEMENTS_PATH
main.BOT_NICKS
radio.ENABLED
The runtime defaults worked, but a fresh installer could not discover or document those choices.
MB397 added the missing entries and, more importantly, introduced a coverage test that compares literal configuration reads in the code with the keys present in the sample file.
A future developer can no longer add a hidden configuration key without the test suite noticing.
YouTube uses ISO-8601 durations such as:
P1DT2H3M4S
The old parser understood hours, minutes and seconds, but ignored the day component. Long streams and archives could therefore lose exactly 86,400 seconds per day.
MB398 introduced one shared duration parser used by both display formatting and numeric duration calculations.
Examples now behave correctly:
P1DT2H3M4S β 1d 2h 3mn 4s
P3D β 3d
It also keeps the ISO-8601 distinction between months before T and minutes after T.
The automatic birthday announcement used local time, while birthday next calculated from UTC.
Around midnight in Europe/Paris, the same birthday could therefore be announced as βtodayβ and displayed by the command as βin 1dβ.
MB399 moved the calculation to the local civil calendar and uses rounded day differences. This matters on the 23-hour and 25-hour days created by daylight-saving transitions.
Leap-day birthdays remain supported and point to the next valid 29 February.
Achievement data is stored in a JSON file. Two failure paths could turn a recoverable incident into data loss:
MB400 preserves a corrupt source for investigation and requires the complete temporary-file write to succeed before replacing the live file.
The castle now refuses to burn the old ledger merely because it cannot read one page.
The old expires_sql_from_seconds() helper had no caller after the ban-expiry path moved to MariaDBβs clock.
MB401 removed the dead code and its unused strftime import. A regression test prevents the obsolete helper from quietly returning.
This round changed no behaviour. It removed a misleading alternative implementation that no longer belonged to the runtime.
The previous hint revealed the first character and replaced everything else with underscores, including spaces:
emile zola β e_________
That hid useful structure and made multi-word answers unnecessarily opaque.
MB402 masks only answer characters while keeping separators visible:
emile zola β e____ ____
jean-paul sartre β j___-____ ______
rock 'n' roll β r___ '_' ____
The hint still gives no extra letters, but it now looks like the answer it describes.
Two metrics assignments lived before the Metrics object was created. They were unreachable code: harmless at runtime, but misleading to anyone reading the startup sequence.
MB403 removed those dead writes and kept the real initial gauges after the Metrics server starts.
MB404 then documented and mechanically locked the required startup order:
DB
ChannelBan
dbCheckTables
Auth
logout stale users
populate channels
IO::Async loop
Metrics
Metrics HTTP
Partyline
IRC login
loop run
A careless future reordering will now fail a test instead of failing during a real boot.
The URL extractor used to strip a final parenthesis unconditionally. A valid Wikipedia URL such as:
https://fr.wikipedia.org/wiki/Talos_(mythologie)
could become a different, broken URL.
MB405 keeps balanced parentheses and brackets while still removing punctuation belonging to the surrounding IRC sentence.
Several specialised URL handlers matched service names anywhere inside the URL. An unrelated page containing instagram.com in its query string could launch the Instagram path, and a nested YouTube URL could be mistaken for the site actually being visited.
MB406 anchored Instagram, Spotify, Apple Music and YouTube routing to the beginning and host of the real URL.
The final audit later discovered that Facebook and X/Twitter still used their older unanchored forms. MB416 completed the rule for every specialised dispatcher.
A door is now selected by the name above the entrance, not by a service name scribbled on a note in the corridor.
The in-memory channel registry had two conventions:
boot population β database spelling
live chanadd β lowercase
Because IRC channel names are case-insensitive, the same channel could occupy different keys depending on whether it existed before or after a restart.
MB407 makes lowercase the single canonical key and updates lookups across the tree. The object still retains the display name.
The regression test scans the source tree and rejects future non-normalised channel lookups.
Generic URL titles longer than 300 characters were cut in the middle of a word without indicating that text had been removed.
MB408 trims at the last available space and adds an ellipsis. A title with no spaces still receives a safe hard cut and ellipsis.
The same round aligned the anti-repeat URL cache with the lowercase channel-key convention from MB407.
checkAntiFlood() caches channel parameters, but only when a CHANNEL_FLOOD row exists.
For the common case of a channel with no anti-flood configuration, every outgoing message triggered another SQL lookup:
10 messages β 10 SELECTs
MB409 adds a negative cache entry with the same TTL:
10 messages β 1 SELECT per TTL
A later database configuration still becomes visible after the normal refresh interval.
Many commands performed this query repeatedly:
SELECT id_channel FROM CHANNEL WHERE name = ?
The same information already exists in the in-memory channel registry.
The migration proceeded carefully over five rounds:
channel_id_cached() helper and migrated three handlers;The SQL lookup remains only as a compatibility fallback when a channel is genuinely absent from memory.
The pre-commit audit found one important path outside the migration count: logBotAction() still selected the channel ID before every channel-log insert.
That function handles public messages, actions, joins, parts and other frequent events. The leftover query therefore mattered more than several command-only sites combined.
MB416 routes it through channel_id_cached() and makes the helper ask the Mediabot::DB wrapper for the current handle after a reconnect instead of trusting a potentially stale compatibility handle.
The central helper now owns the sole fallback query in Helpers.pm.
The summary command gained a richer but backward-compatible syntax:
ai summary [last|today|yesterday|week|<N>d] [<N>] [<N>l] [public] [nick]
New options include:
public / pub β feedback and summary on the current channel
5l β request five output lines, clamped from 1 to 10
help β detailed usage, periods, options and examples
A plain numeric argument still means the number of messages analysed, and the historical command without options still returns notices with the old default length.
The help for m help ai was updated as well.
MB415 initially considered only channels beginning with # eligible for public output.
Mediabot already recognises the standard IRC channel prefixes:
# & ! +
MB416 replaced the local ^# check with the shared channel-target predicate. Public summaries now follow the same routing rules as the rest of the bot.
The fresh snapshot was not committed blindly.
The audit found and fixed three concrete gaps:
1. Facebook and X/Twitter routes were still not anchored to the real URL host.
2. ai summary public accepted only # channels instead of all standard prefixes.
3. logBotAction still performed a channel-id SELECT on a very hot event path.
It also hardened the shared channel-ID fallback against a stale database handle after reconnection.
The new test 631_mb416_precommit_audit_contracts.t protects all three contracts, while the earlier MB406, MB411 and MB415 tests were strengthened to describe the completed behaviour.
Claude reported the following baseline before the independent audit:
full suite through MB415: 8646/8646
The MB416 installer was tested on a fresh copy of the supplied snapshot and is checksum-guarded, idempotent and rollback-safe.
The independent focused selection covering every round in this article reports:
MB397βMB416 focused tests: 210/210
Affected-area regression: 915/915
Helpers.pm syntax: OK
External/URL.pm syntax: OK
External/Claude.pm syntax: OK
The installer repeats those syntax checks with the real Perl runtime on teuk.org before preserving any change. It restores the original files automatically on failure.
The focused, affected-area and full-suite totals overlap and must not be added together.
0 new tables
0 altered columns
0 migrations
0 schema changes
0 release to 3.3
The work changes runtime code, tests and sample documentation only.
Mediabot remains on:
3.2dev-*
Mediabot now:
#, &, ! and + channels consistently.No grand new tower was added in these rounds.
Instead, every door received the correct sign, every hall received one canonical name, the librarians stopped being summoned for facts already held by the keeper, and the public summary spell learned where a channel actually begins.
That is exactly the kind of work that makes a future stable release less dramatic β which, for production software, is the best kind of magic.
You must be logged in to reply.