Mediabot v3 has gained a new kind of magic: trusted external plugins can now be written in Perl, Python or Tcl, routed to selected IRC commands, validated through a strict JSON contract, and either observed safely in dry-run mode or applied through explicit security gates.
This work started as a small experimental bridge. It is now a documented, tested and guarded plugin runtime.
Mediabot already had a large collection of built-in IRC commands. The goal was not to replace them, nor to turn the bot into an unrestricted script launcher.
The new bridge adds a controlled extension point:
IRC command
↓
CommandRegistry / EventBus
↓
Mediabot::Plugin::ScriptDryRun
↓
Mediabot::ScriptRunner
↓
Perl / Python / Tcl script
↓
mediabot-script-v1 JSON response
↓
Mediabot::ScriptActionRunner
↓
validated reply / notice / log action
The historical module name remains:
Mediabot::Plugin::ScriptDryRun
Despite that name, it now supports both dry-run and explicitly gated apply modes.
External scripts are launched out of process with IPC::Open3 and an argv
array.
No shell command line is constructed.
The runner rejects:
plugins/scripts;Supported extensions are:
.pl Perl
.py Python
.tcl Tcl
These scripts are trusted project extensions. They are not a sandbox for untrusted code uploaded by IRC users.
mediabot-script-v1 protocolEvery script receives one JSON object on standard input.
A simplified request looks like this:
{
"protocol": "mediabot-script-v1",
"event": "public_command",
"data": {
"command": "proll",
"channel": "#teuk",
"nick": "Te[u]K",
"args": ["2d6"]
}
}
The script returns one JSON object on standard output:
{
"protocol": "mediabot-script-v1",
"ok": true,
"actions": [
{
"type": "reply",
"text": "🎲 2d6 → 9"
},
{
"type": "log",
"level": "info",
"text": "roll.py completed"
}
]
}
The response is decoded and validated before any action can be applied.
The current protocol understands four action types:
reply
notice
log
timer
reply, notice and log can be applied today.
timer is validated and reserved by the protocol, but deliberately remains
non-applied until a dedicated timer execution layer is implemented.
IRC actions are protected against:
An invalid plan is rejected as a whole before IRC output.
The sample configuration keeps the plugin system disabled:
#[plugins]
#AUTOLOAD=0
#ENABLED=Mediabot::Plugin::ScriptDryRun
A safe route-only dry-run example is:
#[plugins.ScriptDryRun]
#COMMANDS=hello
#ROUTES=hello=examples/hello_perl.pl
#ACTION_MODE=dry-run
#ALLOW_IRC=no
#APPLY_REQUIRE_SCOPE=yes
In this mode, scripts execute and their actions are validated, but nothing is sent to IRC.
Real IRC output requires both gates:
ACTION_MODE=apply
ALLOW_IRC=yes
Apply mode also keeps this protection enabled:
APPLY_REQUIRE_SCOPE=yes
That means the current command must be explicitly listed in COMMANDS or
ROUTES. A broad fallback script cannot silently take ownership of unrelated
commands.
The project now ships useful examples in all three languages:
| IRC command | Language | Script | Purpose |
|---|---|---|---|
hello |
Perl | hello_perl.pl |
Basic reply and log action |
pyhello |
Python | hello_python.py |
Python bridge example |
tclhello |
Tcl | hello_tcl.tcl |
Tcl bridge example |
proll |
Python | roll.py |
Dice expressions such as 2d6+1 |
p8ball |
Tcl | eightball.tcl |
Magic 8-Ball answers |
pchoose |
Perl | choose.pl |
Pick one option from a list |
The p aliases are intentional.
Mediabot already has richer built-in commands named roll, 8ball and
choose. The external examples therefore use proll, p8ball and pchoose
instead of shadowing the existing handlers.
Example configuration:
[plugins]
AUTOLOAD=1
ENABLED=Mediabot::Plugin::ScriptDryRun
[plugins.ScriptDryRun]
COMMANDS=hello,pyhello,tclhello,proll,p8ball,pchoose
ROUTES=hello=examples/hello_perl.pl, pyhello=examples/hello_python.py, tclhello=examples/hello_tcl.tcl, proll=examples/roll.py, p8ball=examples/eightball.tcl, pchoose=examples/choose.pl
ACTION_MODE=apply
ALLOW_IRC=yes
APPLY_REQUIRE_SCOPE=yes
The bridge can be inspected without executing or changing scripts:
.plugins
.plugins config
.scriptdryrun status
.scriptdryrun last
.scriptdryrun config
These views expose:
This makes the plugin runtime observable without exposing credentials or requiring database access.
The bridge went through a long defensive pass covering, among other things:
ok field validation;0, "0" and "".The repository also includes a complete pre-commit suite covering the plugin manager, EventBus, CommandRegistry, script runner, action runner, Partyline visibility and all reference scripts.
mediabot.sample.conf now documents:
SCRIPT fallback;The sample stays disabled and credential-free by default.
The project commit helper now refuses to stage live or development configuration files, private keys, environment files, credential stores and known token formats.
It protects files such as:
mediabot.conf
mediabot.conf_YYYYMMDD_HHMM
mediabot_dev_*.conf
.env
*.key
*.pem
Public templates such as mediabot.sample.conf remain committable, while their
content is scanned for accidental real credentials.
Generated MP3 files, caches, snapshots, logs and local follow-up documents also remain outside the commit.
This work does not require a database schema migration.
Existing built-in commands remain available, and the plugin bridge is disabled unless explicitly enabled.
The implementation preserves the historical dispatcher while allowing the CommandRegistry and EventBus architecture to grow progressively.
Mediabot v3 can now be extended with small, readable Perl, Python and Tcl programs without giving those scripts an unrestricted route to IRC.
The result is a bridge that is:
The castle doors are open — but every spell still passes through the wards.
You must be logged in to reply.