"""Bridge monop binary to IRC via subprocess pipes.""" import sys sys.stdout.reconfigure(line_buffering=True) import socket import subprocess import threading import time import sys import os import re HOST = "127.0.0.1" PORT = 6667 NICK = "monop" CHANNEL = "#monop" PREFIX = "." MONOP_BIN = "/tmp/monop-irc/monop/monop" CARDS_FILE = "/tmp/monop-irc/monop/cards.pck" class IRCBridge: def __init__(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.proc = None self.buffer = "" def irc_send(self, line): self.sock.sendall((line + "\r\n").encode()) def irc_say(self, msg): self.irc_send(f"PRIVMSG {CHANNEL} :{msg}") def connect_irc(self): self.sock.connect((HOST, PORT)) self.irc_send(f"NICK {NICK}") self.irc_send(f"USER {NICK} 0 * :{NICK}") # Wait for registration while True: data = self.sock.recv(4096) if not data: raise ConnectionError("IRC connection closed during registration") self.buffer += data.decode("utf-8", errors="replace") for line in self.buffer.split("\r\n"): if line.startswith("PING"): self.irc_send("PONG" + line[4:]) if "ERROR" in line: print(f"[bridge] Server error: {line}") if " 001 " in self.buffer: break self.irc_send(f"JOIN {CHANNEL}") time.sleep(0.5) self.buffer = "" self.irc_say("Shall We Play A Game?") self.irc_say(f'Prefix your commands with a dot "."') print(f"[bridge] Connected to IRC as {NICK} in {CHANNEL}") def start_monop(self): env = os.environ.copy() self.proc = subprocess.Popen( [MONOP_BIN], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, cwd=os.path.dirname(MONOP_BIN), ) # Read monop stdout -> IRC t = threading.Thread(target=self._monop_reader, daemon=True) t.start() print("[bridge] monop process started") def _monop_reader(self): """Read lines from monop stdout and send to IRC.""" for raw in self.proc.stdout: line = raw.decode("utf-8", errors="replace").rstrip("\n\r") if line: self.irc_say(line) print(f"[monop -> irc] {line}") def _feed_monop(self, nick, text): """Feed input to monop stdin as 'nick text'.""" feed = f"{nick} {text}\n" print(f"[irc -> monop] {feed.rstrip()}") try: self.proc.stdin.write(feed.encode()) self.proc.stdin.flush() except BrokenPipeError: print("[bridge] monop process died, restarting...") self.start_monop() def _wait_for_players(self): """Wait until at least one other user joins the channel before starting.""" print(f"[bridge] Waiting for players to join {CHANNEL}...") while True: data = self.sock.recv(4096) if not data: raise ConnectionError("IRC connection lost while waiting") self.buffer += data.decode("utf-8", errors="replace") while "\r\n" in self.buffer: line, self.buffer = self.buffer.split("\r\n", 1) if line.startswith("PING"): self.irc_send("PONG" + line[4:]) # Detect JOIN from someone other than us m = re.match(r":(\S+?)!\S+ JOIN", line) if m and m.group(1) != NICK: print(f"[bridge] {m.group(1)} joined — starting game") return def run(self): self.connect_irc() self._wait_for_players() self.start_monop() while True: try: data = self.sock.recv(4096) if not data: break self.buffer += data.decode("utf-8", errors="replace") while "\r\n" in self.buffer: line, self.buffer = self.buffer.split("\r\n", 1) if line.startswith("PING"): self.irc_send("PONG" + line[4:]) continue # Parse PRIVMSG m = re.match( r":(\S+?)!(\S+?) PRIVMSG (\S+) :(.+)", line ) if m: sender_nick = m.group(1) target = m.group(3) msg = m.group(4) if target == CHANNEL and sender_nick != NICK: if msg.startswith(PREFIX): cmd = msg[len(PREFIX):] self._feed_monop(sender_nick, cmd) except Exception as e: print(f"[bridge] error: {e}") break if __name__ == "__main__": bridge = IRCBridge() bridge.run()