This update was not a phoenix flight, not a TimeβTurner experiment, and not another great refactoring of the castle walls.
This was more like walking into the Potions classroom after midnight, finding every vial, label, ledger, and cauldron slightly out of place, and deciding that tonight was the night everything would become orderly.
The result is a very practical Mediabot v3 pass:
No grand speech from Dumbledore.
Just a lot of useful magic that makes the bot easier to use, easier to observe, and harder to break.
The important database-side change is the new KARMA_LOG table.
Until now, karma history could fall back to an in-memory ring buffer when the persistent table was missing. That was safe, but not ideal: after a restart, history could disappear.
The new migration:
install/migrations/20260603_karma_log.sql
creates the persistent karma history table used by !karmahist.
The fresh-install schema was also aligned:
install/mediabot.sql
now contains KARMA_LOG too.
That matters because a fresh install should not be weaker than a migrated install.
A new wizard should receive the same spellbook as an old one.
With KARMA_LOG, karma history is no longer just a memory charm floating around inside the bot.
It can now be persisted properly.
That gives commands like:
!karmahist teuk
a much better long-term foundation.
The bot can still behave gracefully if the table is absent, but the intended modern setup now has a real table behind it.
The cauldron keeps the receipts.
!karmadiff learned bigger moves!karmadiff was improved in two directions.
First, it accepts flexible windows:
!karmadiff teuk 3d
!karmadiff teuk 30d
!karmadiff teuk 2h
with sensible limits.
Then it gained an overview mode:
!karmadiff all
!karmadiff all 7d
This shows the top karma movers over the selected period:
Karma top movers (last 7d): menz: +12 | teuk: +8 | fantom: -4
That turns karma from a single-person lookup into a channel mood detector.
A little emotional seismograph for IRC.
!ai models now asks the sourceThe !ai models command can now query the Anthropic API dynamically when an API key is configured.
If the API responds, the bot shows live model data with a [live] badge.
If the API is unavailable, missing, or slow, it falls back to the static list with [static].
That is the right behavior:
Try the owl post.
If the owl is lost, use the noticeboard.
The command becomes more useful without making the bot fragile.
!ai summary now gives immediate feedbackAI summaries can take a few seconds.
That is normal, but on IRC silence feels like a broken command.
Now, before calling Claude, Mediabot tells the user how many messages are about to be summarized:
Summarising 42 message(s) on #boulets...
That small line changes the feeling of the command completely.
The user knows the spell has started.
No more staring at the wand wondering if it fizzled.
!ai summary today <nick> was checkedThe parsing for nick-filtered AI summaries was audited and confirmed functional.
That means forms like:
!ai summary today teuk
are already correctly handled.
No extra code was needed there.
Sometimes the best fix is noticing that the spell is already correct.
Trivia received a lot of love in this pass.
New command:
!trivia leaderboard
!trivia leaderboard 10
It shows the best players on the channel using the existing TRIVIA_SCORES table.
New command:
!trivia myscore
!trivia myscore menz
It displays score, rank, and last correct answer timestamp.
Trivia reset became easier to access:
!trivia reset menz
!trivia reset
The nick-specific reset remains compatible with the older !triviareset command.
Trivia now accepts difficulty:
!trivia easy
!trivia medium
!trivia hard
!trivia computers hard
The difficulty is passed to OpenTrivia DB.
Difficulty tags are now visible in all important trivia moments:
Example:
Trivia [HARD] (Science: Computers): ...
Correct, teuk! [HARD] The answer was: Radon
Time's up! [HARD] The answer was: Radon
This makes the game feel much more polished.
Not just a question scroll β a proper little Hogwarts quiz duel.
!active now can show the quiet people too!active now now does more than show who spoke recently.
It can also show people present in the channel nicklist who have not talked in the last hour:
Present but silent in last 60min: gwen, Bot3 (2 nick(s))
That is a lovely IRC-specific detail.
Because presence is not the same as speaking.
Sometimes the ghosts are in the room, just quiet.
!active month was added!active now supports:
!active month
This gives activity since the beginning of the current month.
Together with earlier modes like today, yesterday, week, and now, the command is becoming much more human-friendly.
You no longer need to think in raw hour windows for common questions.
!last <nick> [n]The !last command can now show more than one message:
!last teuk 3
The output is limited to avoid flood and displayed chronologically.
This is a very useful operator/community command: you can quickly see the last few things someone said without digging through logs manually.
A small Pensieve sip, not the whole lake.
.nickinfo and .who now use the real schemaTwo Partyline commands had old or incorrect SQL assumptions about the USER schema.
They referenced names such as:
u.nick
u.email
USER_HOST
USER_LOG
LEVEL
But the real schema uses:
USER.nickname
USER_LEVEL
USER_HOSTMASK
ACTIONS_LOG
The commands were rewritten against the real schema.
So .nickinfo <nick> and .who <#chan> should now stop pretending users do not exist.
The castle records were there.
The command was just looking in the wrong filing cabinet.
.seen remains improvedThe recent .seen improvements remain part of the overall direction:
.seen Te[u]K
.seen teu*
Exact match normalizes case, and wildcard lookup works.
That gives Partyline the same spirit as the IRC-side seen command.
Several race-condition style crashes were addressed around channel nicklists.
getRandomNickgetRandomNick($channel) now guards against unknown or uninitialized channels before dereferencing the nicklist.
This matters for dynamic commands using %r.
channelNicksRemovechannelNicksRemove now checks that the nicklist exists and is an array before trying to remove a nick.
on_message_NICKNick changes during startup or partial nicklist initialization no longer assume the channel nicklist is always ready.
These are not flashy changes, but they are exactly the kind of defensive code that keeps a long-running IRC bot alive.
No exploding cauldron just because a JOIN, QUIT, KICK or NICK arrives at an awkward moment.
The recent quote improvements are now part of the stable direction:
!q random avoids immediate repeat;!q <nick> also avoids immediate repeat;!q view truncates huge quotes;!q search ranks by relevance;!q stats includes the top contributor.These changes make the quote system feel much more mature.
Less random parchment avalanche.
More curated spell archive.
Several new Prometheus counters were added:
mediabot_urltitle_requests_total{type=...}
mediabot_nick_changes_total
mediabot_joins_total{channel=...}
mediabot_parts_total{channel=...}
That gives better observability over:
This is useful not only for dashboards, but also for understanding IRC network behavior.
If the castle suddenly fills with owls, the metrics should show it.
URL title handling now increments counters by URL family:
youtube
instagram
spotify
applemusic
facebook
x_twitter
generic
This will make Grafana much more interesting later.
Instead of just knowing that URL titles are happening, we can see what kind of links people actually share.
That is good dashboard material.
!remind cancel <id> now normalizes the caller nick with lc($nick) before matching against from_nick.
That fixes cases where IRC nick casing differs from the lowercase value stored in the database.
A reminder should not become impossible to cancel just because the nick has fancy casing.
Mediabot::UserMediabot::User received better POD documentation.
The accessors and level helpers are documented more clearly, including the user level hierarchy:
User < Administrator < Master < Owner
This is not glamorous, but it matters.
Future refactoring is safer when the objects explain themselves.
perl -I. -c Mediabot/UserCommands.pm
perl -I. -c Mediabot/Partyline.pm
perl -I. -c Mediabot/Quotes.pm
perl -I. -c Mediabot/Helpers.pm
perl -I. -c Mediabot/External/Claude.pm
perl -I. -c Mediabot/External/URL.pm
perl -I. -c mediabot.pl
LC_ALL=C grep -P '\x00' -n Mediabot/*.pm Mediabot/*/*.pm || true
grep -rn "cl\.text\b\|ORDER BY cl\.id\b" Mediabot/ || true
git diff --check
grep -n "CREATE TABLE .*KARMA_LOG" install/mediabot.sql
grep -n "fk_karma_log_channel" install/mediabot.sql
grep -n "20260603_karma_log.sql" install/migrations/README.md
perl tools/check_schema_drift.pl --conf=mediabot.conf --strict
m q random
m q view 1
m q search teuk trivia
m q stats
m trivia leaderboard
m trivia myscore
m trivia hard
m trivia reset teuk
m active now
m active month
m last teuk 3
m karmadiff all 7d
m karmahist teuk
m ai models
m ai summary today teuk
.nickinfo teuk
.who #teuk
.seen Te[u]K
.seen teu*
.top
.top #teuk 5
This was the Cauldron Ledger pass.
Mediabot now has:
The shelves are labeled.
The potion bottles are capped.
The ledger finally balances.
The ink is dry; send the owl.
You must be logged in to reply.