This pass continued the cleanup work on Mediabot v3 after the previous output-pagination and LIKE-escaping work.
The focus this time was more internal:
LIKE lookups with exact = lookups where the value is an identifier;execute() on a failed prepare();This was mostly invisible polish from a user point of view, but it makes the bot safer and more reliable as a long-running IRC service.
🛡️ Protego Maxima: harden DB lookups, exact-match spells, and runtime safety
Alternative shorter version:
🛡️ Protego Maxima: harden Mediabot DB lookup paths
Several commands and helpers were still using SQL LIKE ? for values that are not search patterns.
Examples:
For those values, SQL wildcard behavior is not wanted.
A value such as:
te_k
should not accidentally match:
teak
teuk
teXk
because _ has a special meaning in SQL LIKE.
This pass replaced those cases with exact = comparisons.
ChannelCommands.pmChannel lookups now use exact matches instead of SQL LIKE.
Affected areas include:
antifloodset;Before:
WHERE CHANNEL.name LIKE ?
After:
WHERE CHANNEL.name = ?
Files:
Mediabot/ChannelCommands.pm
t/cases/44_channel_name_exact_match.t
A real runtime bug appeared during IRC testing:
Undefined subroutine &Mediabot::External::getTMDBLangChannel
The TMDB command lived in Mediabot::External, but getTMDBLangChannel() is defined in Mediabot::ChannelCommands.
External.pm now imports it explicitly:
use Mediabot::ChannelCommands qw(getTMDBLangChannel);
The TMDB channel-language lookup also uses exact channel-name matching.
Files:
Mediabot/External.pm
Mediabot/ChannelCommands.pm
t/cases/45_tmdb_lang_lookup_import.t
Mediabot/Hailo.pm still had two LIKE lookups that should have been exact:
Before:
SELECT 1 FROM HAILO_EXCLUSION_NICK WHERE nick LIKE ?
WHERE CHANNEL.name LIKE ?
After:
SELECT 1 FROM HAILO_EXCLUSION_NICK WHERE nick = ?
WHERE CHANNEL.name = ?
Files:
Mediabot/Hailo.pm
t/cases/46_hailo_exact_match.t
Several user lookup paths still used SQL LIKE even though they expected one exact user.
Affected areas:
mediabot.pl;External.pm.Before:
USER.nickname LIKE ?
nickname LIKE ?
After:
USER.nickname = ?
nickname = ?
Files:
Mediabot/ChannelCommands.pm
Mediabot/External.pm
mediabot.pl
t/cases/47_user_lookup_exact_match.t
getCommandCategory() previously used:
WHERE description LIKE ?
Command categories are logical identifiers, not wildcard searches.
It now uses:
WHERE description = ?
The function was also cleaned up so it:
prepare();execute();Files:
Mediabot/DBCommands.pm
t/cases/48_command_category_exact_match.t
The mp3 search is intentionally a search command, so it still uses LIKE.
But user input must be escaped so %, _, and ! are treated literally.
The search now uses:
LIKE ? ESCAPE '!'
with escaping:
! -> !!
% -> !%
_ -> !_
The multi-token behavior is preserved:
queen bohemian
still becomes an ordered search like:
%queen%bohemian%
Files:
Mediabot/Helpers.pm
t/cases/49_mp3_like_escape.t
_tz_exists() now uses exact timezone lookup.
Before:
SELECT tz FROM TIMEZONE WHERE tz LIKE ?
After:
SELECT tz FROM TIMEZONE WHERE tz = ?
It also now:
prepare();execute();Files:
Mediabot/Helpers.pm
t/cases/50_timezone_exact_match.t
checknick exact matchingchecknick used to switch to LIKE if the input contained % or _.
That was misleading because checknick <nick> is not a wildcard search command.
It now always uses exact nick lookup:
WHERE nick = ?
Pagination from the previous pass remains intact.
Files:
Mediabot/Helpers.pm
t/cases/51_checknick_exact_match.t
getLevel()getLevel() in Helpers.pm now uses exact USER_LEVEL.description lookup.
Before:
WHERE description LIKE ?
After:
WHERE description = ?
It also now handles DB failures cleanly.
Files:
Mediabot/Helpers.pm
t/cases/52_user_level_exact_match.t
getIdUserLevel()getIdUserLevel() in UserCommands.pm received the same treatment.
Before:
WHERE description LIKE ?
After:
WHERE description = ?
It now also checks prepare() and execute() safely.
Files:
Mediabot/UserCommands.pm
t/cases/53_user_id_level_exact_match.t
Auth::_resolve_user()Mediabot/Auth.pm now uses a dedicated internal helper:
_fetch_user_row()
This avoids raw prepare() / execute() calls scattered directly inside _resolve_user().
The new helper:
prepare();execute();_fetch_hostmasks()The hostmask fetch path was also hardened:
prepare();execute();Files:
Mediabot/Auth.pm
t/cases/54_auth_resolve_user_db_safety.t
Three helper functions in Mediabot/UserCommands.pm were hardened:
getLevelUser()
getUserLevelDesc()
getUserChannelLevel()
They now:
prepare();execute();Files:
Mediabot/UserCommands.pm
t/cases/55_usercommands_lookup_db_safety.t
Several helper functions in Mediabot/Helpers.pm were hardened:
checkUserLevel()
checkUserChannelLevel()
getIdUserChannelLevel()
getUserChannelLevelByName()
Notable changes:
checkUserLevel() now uses exact level description matching;prepare();execute();Files:
Mediabot/Helpers.pm
t/cases/56_helpers_level_lookup_db_safety.t
Two WHOIS-related helpers were hardened:
get_user_from_whois()
getNickInfoWhois()
These paths matter because they are used around IRC identity/authentication logic.
They now:
prepare();execute();Files:
Mediabot/Helpers.pm
t/cases/57_whois_user_lookup_db_safety.t
getIdUser() DB safetygetIdUser() now:
prepare();execute();Files:
Mediabot/Helpers.pm
t/cases/58_getiduser_db_safety.t
The following channel-related paths were hardened:
get_channel_by_name()
logBotAction()
getIdChannelSet()
get_channel_by_name()Now checks prepare() and execute() before creating a Mediabot::Channel object.
logBotAction()Now handles both lookup and insert failures safely:
getIdChannelSet()Now handles missing parameters and DB failures more clearly.
Files:
Mediabot/ChannelCommands.pm
Mediabot/Helpers.pm
t/cases/59_channel_lookup_db_safety.t
getUserAutologin() now handles DB errors properly.
Old behavior could attempt execute() on an undefined statement handle if prepare() failed.
New behavior:
prepare();execute();0 safely on DB failure;Files:
Mediabot/LoginCommands.pm
t/cases/60_getuserautologin_db_safety.t
userCount() now:
prepare();execute();0 safely on failure;Files:
Mediabot/Helpers.pm
t/cases/61_usercount_db_safety.t
Two important UserCommands.pm runtime paths were hardened:
dbLogoutUsers()
userOnJoin()
dbLogoutUsers()Now handles:
userOnJoin()This path runs on every JOIN and may apply:
It now handles:
Files:
Mediabot/UserCommands.pm
t/cases/62_usercommands_onjoin_logout_db_safety.t
onStartTimers() in DBCommands.pm was hardened.
This is important because it runs during bot startup and restores DB-defined timers.
It now:
prepare();execute();Files:
Mediabot/DBCommands.pm
t/cases/63_onstarttimers_db_safety.t
This pass added or updated tests from:
t/cases/44_channel_name_exact_match.t
through:
t/cases/63_onstarttimers_db_safety.t
The tests mostly enforce structural safety:
LIKE where exact matching is required;prepare() checks;execute() checks;This gives a useful safety net against future regressions.
Run syntax checks:
perl -I. -c Mediabot/DBCommands.pm
perl -I. -c Mediabot/ChannelCommands.pm
perl -I. -c Mediabot/Helpers.pm
perl -I. -c Mediabot/UserCommands.pm
perl -I. -c Mediabot/LoginCommands.pm
perl -I. -c Mediabot/Auth.pm
perl -I. -c Mediabot/Hailo.pm
perl -I. -c Mediabot/External.pm
perl -I. -c mediabot.pl
Run the full regression suite:
env LANG=C.UTF-8 LC_ALL=C.UTF-8 perl t/test_commands.pl --verbose
Useful live checks:
m tmdb matrix
m hailo_status
m userinfo teuk
m whoami
m cstat
m access #teuk teuk
m mp3 queen
m mp3 %
m date Europe/Paris
m checknick Te[u]K
m checknick %
m status
m qlog #teuk test
Startup checks:
tail -f /home/mediabot/mediabot_v3/mediabot.log
Look for:
Checking timers to set at startup
X active timer(s) set at startup
and no unexpected DB errors.
Before committing:
cd /home/mediabot/mediabot_v3
git status --short
git diff --stat
Stage the intended files:
git add Mediabot/Auth.pm Mediabot/ChannelCommands.pm Mediabot/DBCommands.pm Mediabot/External.pm Mediabot/Hailo.pm Mediabot/Helpers.pm Mediabot/LoginCommands.pm Mediabot/UserCommands.pm mediabot.pl t/cases/
Check staged files:
git diff --cached --stat
git diff --cached --name-only
Commit:
git commit -m "🛡️ Protego Maxima: harden DB lookups, exact-match spells, and runtime safety"
Push:
git push
This was a defensive engineering pass.
Most users will not see a dramatic visible change, and that is exactly the point: the bot should behave the same when everything is healthy, but fail more gracefully when the database or runtime environment misbehaves.
The code now has fewer accidental SQL wildcard paths, better DB error handling, cleaner statement lifecycle management, and a stronger regression suite.
Small protections everywhere. A stronger Mediabot overall.
You must be logged in to reply.