Mediabot v3 has been using Mediabot::Context and Mediabot::Command objects for a while, but
their adoption was partial and inconsistent. Two dispatch functions — mbCommandPublic and
mbCommandPrivate — were mixing three different coding styles simultaneously: legacy
($self, $message, $sNick, $sChannel, @tArgs) signatures, closures capturing variables from the
outer scope, and the new Context-based pattern. Authentication checks were duplicated: once via
require_level in Context, and again manually inside individual handlers using checkUserLevel,
getNickInfo, and local $auth / $iMatchingUser* variables.
This post documents the refactoring work done to bring the codebase to a consistent state.
Mediabot::Context — CompletedContext.pm was a skeleton with accessors but missing the reply and logging methods that
Mediabot::Command already referenced. Added:
reply($msg) — sends a PRIVMSG to the current channelreply_channel($msg) — explicit alias of replyreply_private($msg) — sends a NOTICE to the calling nickis_private() — true if the command came from a private messagechannel_obj() — returns the Mediabot::Channel object for the current channelcommand_obj() — returns the attached Mediabot::Command objectlog_info($msg), log_error($msg) — convenience logging wrappersMediabot::Command — Alignedrequire_auth_level was reimplementing auth logic that already existed in Context->require_level.
It now delegates directly:
sub require_auth_level {
my ($self, $level) = @_;
return $self->context->require_level($level);
}
mbCommandPublic — Cleaned Uplc($sCommand) stored in my $cmd once — no more repeated lc() callsexists $command_map{...} lookup reduced to a single if (my $handler = ...)Q entry removed (already covered by lc normalization)help inline closure extracted to mbHelp_ctx($ctx)botNickTriggered block extracted to mbHandleNickTriggered($ctx, $what)Mediabot::Command object instantiated and attached to Context via $ctx->{command_obj}mbCommandPrivate — Dual Path EliminatedThe most impactful change. The old implementation maintained two parallel structures:
my %command_table = ( ... ); # handler refs
my %ctx_commands = map { ... } qw( ... ); # list of which ones take Context
Any new command had to be registered in both — a silent bug waiting to happen. chanlist was
in %ctx_commands but missing from %command_table, making it unreachable in private.
The new implementation:
%command_table with closures, exactly like mbCommandPublicMediabot::Command attached to Contextpass, ident, topic, debug, radio) wrapped in thin _ctx shimsRadio commands and a few others still had the old signature. Rather than rewriting hundreds of lines of logic, thin wrappers were added:
sub playRadio_ctx {
my ($ctx) = @_;
playRadio($ctx->bot, $ctx->message, $ctx->nick, $ctx->channel, @{ $ctx->args });
}
Same pattern for rplayRadio_ctx, queueRadio_ctx, nextRadio_ctx, update_ctx,
radioPub_ctx, userPass_ctx, userIdent_ctx, userTopicChannel_ctx.
versionCheck — Migrated to Context# Before
sub versionCheck {
my ($self, $message, $sChannel, $sNick) = @_;
botPrivmsg($self, $sChannel, $sMsg);
}
# After
sub versionCheck {
my ($ctx) = @_;
$ctx->reply($sMsg);
}
getReplyTarget — RemovedThis helper existed to figure out whether to reply to a channel or a nick. It became obsolete
once mbCommandPrivate started building its Context with channel => $sNick directly.
channelList_ctx — Cleaned30 lines of manual auth checking replaced by one line:
return unless $ctx->require_level('Master');
Seven _ctx handlers were still doing manual authentication: resolving the user object with a
get_user_from_message fallback, checking $auth, and calling checkUserLevel. All replaced
with $ctx->require_level:
| Handler | Before | After |
|---|---|---|
radioNext_ctx |
15 lines + checkUserLevel |
require_level('Administrator') |
mbModUser_ctx |
20 lines + $auth check |
require_level('Master') |
setChannelAntiFloodParams_ctx |
25 lines + $auth + checkUserLevel |
require_level('Master') |
mbRehash_ctx |
80 lines incl. manual autologin attempt | require_level('Master') |
mp3_ctx |
15 lines + checkUserLevel |
require_level('User') |
mbQuotes_ctx |
checkUserLevel + $auth var |
has_level('User') (two-tier logic preserved) |
spike dispatcher entry |
botPrivmsg($self, $sChannel, ...) closure |
$ctx->reply(...) |
mbRehash_ctx deserves a special mention: it had 80 lines of manual autologin logic including
DB queries, mask matching, and multiple noticeConsoleChan debug calls. This was development
scaffolding that had never been cleaned up. require_level handles all of this transparently
through Context->user and _ensure_logged_in_state.
Not everything was migrated, by design:
playRadio, rplayRadio, queueRadio, nextRadio, update) —
hundreds of lines of domain logic. Wrapped, not rewritten.checkUserChannelLevel-based handlers (channelSet_ctx, userOpChannel_ctx, etc.) —
these use per-channel privilege levels, which are distinct from global require_level. Correct
as-is.mbRestart, mbJump — legacy signature, not in the dispatcher, out of scope.mbQuoteAdd, getNickInfoWhois — internal helpers called by other subs, not handlers.The live test framework was extended from 13 to 41 tests across 7 files, covering:
whoami confirming nickname and level in the responsechanlist in private (was broken due to P11 — missing from %command_table)users, chaninfo, seen with authenticated context!die as the last test, confirming the bot’s QUIT is received by the spyThe schema was extended with a second test user mboper (Master level, password-authenticated,
hostmask mbspy*!*@*) to enable authenticated command testing without relying on autologin.
🧹 Hermione cleaned the common room — Context.pm completed, Command.pm aligned,
all _ctx handlers migrated to require_level, radio wrappers extracted,
legacy auth blocks banished
You must be logged in to reply.