Forum teuk.org

πŸͺ„ Wingardium Leviosa! β€” Bugs Banished, 16 Spells Cast (May 2026)

in Mediabot Β· started by TeuK Β· 1mo ago

TeuK Β· 1mo ago

Overview

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.” 🏰⚑✨


πŸ› Bugs Fixed

B1 β€” channelUnban_ctx β€” Silent DB crash on execute failure

Both 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).


B2 β€” 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,

B3 β€” !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.


πŸ† AA1 β€” !triviatop β€” Persistent Trivia Hall of Fame

Every 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).


πŸ” AA2 β€” !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.”


πŸ“‹ AA3 β€” Partyline .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.


πŸ“ˆ AA4 β€” !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.


⏱️ AA5 β€” Hailo alarm(5) Timeout

learn_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.


πŸ“Š AA6 β€” 12 New Prometheus Metrics

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.


πŸ“ BB1 β€” !note Persistent in DB

Notes 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.


πŸ‘οΈ BB2 β€” !seen Now Dispatched

mbSeen_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) },

πŸ“œ BB3 β€” Partyline .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.


⏰ BB4 β€” Daily Reminder Digest (Scheduler)

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

πŸ“‰ BB5 β€” !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.


πŸ—³οΈ BB7 β€” !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).


πŸ€– BB8 β€” !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.


πŸ”„ BB10 β€” !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.


πŸ—„οΈ Schema β€” Two New Tables

TRIVIA_SCORES

CREATE 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 ...
);

NOTE

CREATE 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.


πŸ§ͺ Tests (317–333)

# 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.


πŸ“¦ Files Changed

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.