Skip to content

The first handler

The MPP side is the same handshake → pullinvocation loop as the Python adapter tutorial; only the handler is new.

One rule before the code: viewer text is about to be embedded in a console command, and the Zandronum console treats ; as a command separator and " as a string delimiter. Unsanitized, a viewer could redeem hello"; quit and execute arbitrary console commands on the server. Strip both (plus \ and non-printable characters) before quoting:

def sanitize_chat(message):
cleaned = "".join(
ch for ch in message
if 0x20 <= ord(ch) < 0x7F and ch not in ('"', ';', '\\')
)
return cleaned.strip()
def handle_chat_queue(rcon):
def handler(sock, inv_id, event, params):
if event != "say":
failed(sock, inv_id, f"unknown event: {event}")
return
message = sanitize_chat(params.get("message", ""))
if not message:
failed(sock, inv_id, "message empty after sanitization")
return
try:
rcon.command(f'say "{message}"')
except RconError as e:
failed(sock, inv_id, str(e))
return
applied_done(sock, inv_id, {"message": message})
return handler

(failed and applied_done are the small frame helpers from the Python adapter tutorial: failed sends a failed frame with a reason, applied_done sends applied then done.)

The failed path matters as much as the happy path: when the game server is down, the Invocation fails with a reason and the viewer can be refunded — the Pack must never silently swallow a paid redeem.

Start the bridge with your Pack, set DOOM_RCON_PASSWORD in its environment, redeem a say Cue, and watch the message land in the game’s chat.

One event, end to end. Next: a second event that earns its own queue.