Three bugs silenced. Sixteen features summoned. Two new DB tables conjured from thin air. The Marauderβs Map now shows trivia hall-of-fame rankings, karma sparklines, persistent notes, and enriched channel intel β all backed by a proper schema migration.
βWingardium Leviosa! Every feature lifted into place. Every bug sent back to the dungeons where it belongs.β π°β‘β¨
channelUnban_ctx β Silent DB crash on execute failureBoth if/else branches called $sth->execute(...) with no error check.
A DB error would let the code fall through to fetchrow_hashref on a
broken statement handle β silent crash or corrupted data.
# Before β bare execute, no test
$sth->execute($id_channel, $selector);
# After β B1/fix
unless ($sth && $sth->execute($id_channel, $selector)) {
_channelban_reply($ctx, 'DB error (by id).');
$self->{logger}->log(1, "channelUnban_ctx: execute error: $DBI::errstr");
return;
}
Both branches fixed. Pattern consistent with the rest of the codebase (B26).
checkTriviaAnswer β hint_given never reset between questions$trivia->{hint_given} was set to 1 when the hint fired, but never
cleared when a new question started in mbTrivia_ctx. In multi-round
mode (!trivia start N), the hint stopped appearing from round 2 onward.
# B2/fix: added to trivia init hash
hint_given => 0,
!ai summary β Callback passed a reference instead of a value# Before β \$_[0] is a scalar ref β botNotice received "SCALAR(0x...)"
sub { botNotice($self, $nick, \$_[0]) }
# After β B3/fix
sub { botNotice($self, $nick, $_[0]) }
The summary response was computed correctly by Claude but never displayed. One backslash, maximum damage.
!triviatop β Persistent Trivia Hall of FameEvery correct trivia answer is now upserted into the TRIVIA_SCORES table:
INSERT INTO TRIVIA_SCORES (id_channel, nick, score, last_correct)
VALUES (?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE score = score + 1, last_correct = NOW()
New command !triviatop [n] (max 15) shows the all-time leaderboard:
<mediabotv3> Trivia hall of fame (#boulets): #1 teuk: 42 #2 Boole: 31 #3 Poyan: 19
Score survives bot restarts. Errors are caught and logged
(mediabot_trivia_db_saves_total Prometheus counter).
!ai summary with Nick Filter<teuk> m ai summary 15 boole
<mediabotv3> -teuk- Boole spent the last 15 messages debating pizza toppings...
The nick filter is optional β !ai summary 10 still summarises all speakers.
SQL filters with LOWER(cl.nick) = ? and the Claude prompt is adjusted:
βSummarise this IRC conversation by boole in 2-3 sentences.β
.channels β Enriched View.channels now shows five columns instead of three:
Channel Status IRC DB Hailo Owner
----------------------------------------------------------------------
#boulets joined 14 8 on teuk
#testchan joined 3 2 off teuk
#lurk parted 0 1 - none
Hailo flag fetched from CHANNEL_SET. DB user count from USER_CHANNEL.
!karmgraph [nick] β ASCII Sparkline (7 days)<teuk> m karmgraph boole
<mediabotv3> karma graph boole (7d) βββββ
Β·β net: +12
Each bucket = one day. Block characters (βββββ
βββ) for positive deltas,
βΌ for negative, Β· for no activity. Data sourced from _karma_log
ring buffer β zero extra DB queries.
alarm(5) Timeoutlearn_reply can block the IO loop indefinitely on a large brain.
# AA5: wrap with SIG{ALRM}
local $SIG{ALRM} = sub { die "Hailo timeout\n" };
alarm(5);
my $r = $hailo->learn_reply($what);
alarm(0);
Timeout β log at level 1 + mediabot_hailo_timeout_total counter.
alarm(0) is called unconditionally after the eval to prevent leaks.
mediabot_trivia_rounds_total mediabot_trivia_timeouts_total
mediabot_trivia_db_saves_total mediabot_poll_created_total
mediabot_poll_closed_total mediabot_poll_votes_total
mediabot_poll_duration_seconds mediabot_karma_votes_total
mediabot_karma_selfvote_blocked mediabot_karma_cooldown_blocked
mediabot_hailo_learn_reply_total mediabot_hailo_timeout_total
All declared via _declare() in Metrics.pm. Incremented at the correct
callsite in UserCommands.pm and Mediabot.pm.
!note Persistent in DBNotes now survive bot restarts. Three operations wired to the NOTE table:
!note <message> β INSERT INTO NOTE (nick, text) VALUES (?, ?)
!notes β SELECT id_note, text FROM NOTE WHERE nick = ? (loaded if memory empty)
!notes del <id> β DELETE FROM NOTE WHERE nick = ? AND id_note = ?
Memory cache is still used when warm. DB is the fallback after restart.
!note search and !note export continue to work on the cached list.
!seen Now DispatchedmbSeen_ctx was already fully implemented (252 lines, USER_SEEN +
CHANNEL_LOG fallback) but was missing from command_map. One line added:
seen => sub { mbSeen_ctx($ctx) },
.logs <#chan> [n].logs #boulets 15
Last 15 lines on #boulets:
[21:08] <teuk> m trivia start 5
[21:08] <mediabotv3> Trivia: starting 5-round game!
...
Queries CHANNEL_LOG ordered by id DESC LIMIT n (default 10, max 50).
Output format: [HH:MM] <nick> message.
A new remind_daily_digest task runs every 24 hours. For each nick with
undelivered reminders, it sends a NOTICE digest:
-mediabotv3- [Daily digest] You have 2 pending reminder(s):
-mediabotv3- From teuk on #boulets: apporte les logs
-mediabotv3- From Boole on #boulets: rΓ©union Γ 21h
!karmainfo <nick><teuk> m karmainfo boole
<mediabotv3> karmainfo boole: received +14/-2 | given: +8/-1 | top voter: teuk
Reads _karma_log ring buffer. No extra DB queries.
Shows: votes received (+/-), votes given, most active voter.
!poll weighted β Weighted Options<teuk> m poll weighted Best pizza? | 4 fromages:3 | Regina:1
The weighted keyword enables option:N parsing (weight 1β10).
Weights are stored in $poll->{weights} alongside options. The tally
display and !pollstatus show the raw vote counts (not weighted totals β
weighting affects future vote probability display, not current counting).
!ai models<teuk> m ai models
<mediabotv3> -teuk- Available Claude models: claude-opus-4-6 | claude-sonnet-4-6 (current) | claude-haiku-4-5-20251001
Lists known models and marks the one currently set in anthropic.MODEL.
!triviareset <nick> (Master)<teuk> m triviareset boole
<mediabotv3> teuk reset trivia score for boole.
Requires Master level. Deletes the row from TRIVIA_SCORES for this
nick on the current channel.
TRIVIA_SCORESCREATE TABLE `TRIVIA_SCORES` (
`id_channel` BIGINT UNSIGNED NOT NULL,
`nick` VARCHAR(64) ... NOT NULL,
`score` BIGINT UNSIGNED NOT NULL DEFAULT 0,
`last_correct` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id_channel`, `nick`),
KEY `idx_trivia_scores_channel_score` (`id_channel`, `score`),
CONSTRAINT `fk_trivia_scores_channel` FOREIGN KEY ...
);
NOTECREATE TABLE `NOTE` (
`id_note` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`nick` VARCHAR(64) ... NOT NULL,
`text` VARCHAR(256) ... NOT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_note`),
KEY `idx_note_nick` (`nick`)
);
No FK on NOTE.nick β notes are nick-scoped only, not linked to USER.
| # | Feature |
|---|---|
| 317 | B1: execute guard both branches, $DBI::errstr |
| 318 | B2: hint_given => 0 in init hash |
| 319 | B3: callback uses $_[0] not \$_[0] |
| 320 | AA1: mbTriviaTop_ctx, TRIVIA_SCORES, ON DUPLICATE KEY |
| 321 | AA2: filter_nick, LOWER(cl.nick), who_str in prompt |
| 322 | AA3: hailo_flags, db_users, Hailo column |
| 323 | AA4: mbKarmaGraph_ctx, buckets, block chars |
| 324 | AA5: alarm(5), SIG{ALRM}, timeout counter |
| 325 | AA6: all 12 new metric names |
| 326 | BB1: INSERT INTO NOTE, load from DB, DELETE FROM NOTE |
| 327 | BB2: !seen in dispatch + help |
| 328 | BB3: _cmd_chanlog, CHANNEL_LOG query, [HH:MM] <nick> |
| 329 | BB4: remind_daily_digest, 86400, Daily digest |
| 330 | BB5: mbKarmaInfo_ctx, received/given, top voter |
| 331 | BB7: poll_weighted, weights => array |
| 332 | BB8: known models, (current) marker |
| 333 | BB10: mbTriviaReset_ctx, DELETE FROM TRIVIA_SCORES, Master |
Total test suite: 251 cases.
| File | Changes |
|---|---|
ChannelCommands.pm |
B1: execute guard in channelUnban_ctx |
UserCommands.pm |
B2, AA1/4, BB1/5/7/10 + !triviatop, !karmgraph, !karmainfo, !triviareset |
External.pm |
B3, AA2, BB8 |
Partyline.pm |
AA3, BB3 |
Mediabot.pm |
AA5/6, BB2/5/10 dispatchers + help |
Metrics.pm |
AA6: 12 new metric declarations |
mediabot.pl |
AA5 timeout, BB4 remind digest scheduler |
install/mediabot.sql |
TRIVIA_SCORES (improved) + NOTE (new) |
install/migrations/20260521_trivia_scores_note.sql |
New migration |
install/migrations/README.md |
Updated migration order + table list |
t/cases/317β333 |
17 test cases |
You must be logged in to reply.