Forum teuk.org

🪄 Protego Maxima! — Claude Hardened, History Tamed, Tests Locked (May 2026)

in Mediabot · started by TeuK · 23h ago

TeuK · 23h ago

Overview

Three rounds of refinement on the Claude integration — a clean architecture for output routing, per-nick rate limiting to prevent API abuse, and five new test cases to lock it all down. The bot is now production-grade on !ai.

“Protego Maxima! The shields are up. The bot is hardened.” 🛡️✨


🏗️ R1 — claudeAI() Output Callback Refactor

Before: _cmd_ai (Partyline) used local *Mediabot::Helpers::botPrivmsg to monkey-patch the output function. This is fragile, non-reentrant, and invisible to static analysis tools.

After: claudeAI() now accepts an optional $output_fn CODE reference as its first argument after $chan. When set, every output line calls $output_fn->($text) instead of botPrivmsg.

# Signature
claudeAI($self, $message, $nick, $chan, @args)           # IRC — botPrivmsg
claudeAI($self, $message, $nick, $chan, $fn, @args)      # callback mode

# Dispatcher inside claudeAI
my $_out = sub {
    my ($text) = @_;
    if ($output_fn) { $output_fn->($text); }
    else            { botPrivmsg($self, $chan, $text); }
};

_cmd_ai (Partyline) now passes a closure that writes to the stream:

my $output_fn = sub {
    my ($text) = @_;
    $text =~ s/[\r\n]+$//;
    $stream->write("[Claude] $text\r\n");
};
Mediabot::External::claudeAI($bot, undef, $pl_nick, $chan, $output_fn, split(/\s+/, $prompt));

Side effect: usleep($sleep_us) is skipped when $output_fn is set — no artificial delay in Partyline responses.

Result: _cmd_ai went from 55 lines to 34. No monkey-patching.


🚦 R2 — Per-Nick Rate Limiting for !ai

Max 5 requests per 60 seconds per nick+channel pair. Sliding window reset on expiry.

<teuk> m ai …  (×5 quickly)
<teuk> m ai one more
<mediabotv3> -teuk- Rate limit: please wait 47s before using !ai again.

Implementation:

my $rl = $self->{_claude_ratelimit}{$rl_key} //= { count => 0, window => $now };
if ($now - $rl->{window} >= 60) { $rl->{count} = 0; $rl->{window} = $now; }
if (++$rl->{count} > 5) {
    botNotice($self, $nick, "Rate limit: please wait ${wait}s ...");
    $self->{metrics}->inc('mediabot_claude_ratelimit_total') if $self->{metrics};
    return;
}

Rate limiting is skipped for Partyline sessions (already authenticated operators) — detected by checking unless ($output_fn).

New Prometheus counter: mediabot_claude_ratelimit_total.


🧪 R3 — Five New Test Cases (225–229)

# File What
225 225_external_claude_history_cmd.t !ai history subcommand: accesses _claude_history, uses botNotice, truncates at 120 chars
226 226_external_claude_ratelimit.t Rate limiter: _claude_ratelimit hash, 60s window, botNotice message, Partyline bypass, Prometheus counter
227 227_external_claude_callback.t $output_fn callback: CODE ref detection, $_out dispatcher, chunks via $_out, no monkey-patch in _cmd_ai
228 228_external_yt_search_meta.t !yt search metadata: contentDetails+statistics API call, ISO 8601 parsing, M/K view format
229 229_external_claude_prompt_cache.t Prompt cache: MD5 key, 60s TTL, 120s eviction, history updated on hit

All follow the project’s static-analysis pattern — no live server required. Total test suite: 224 cases.


📦 Files Changed

File Changes
External.pm R1 $output_fn callback + $_out dispatcher; R2 rate limiter + claude_ratelimit_total
Partyline.pm R1 _cmd_ai rewrite (55→34 lines, no monkey-patch)
t/cases/225…229 5 new test cases

You must be logged in to reply.