""" Sopel plugin: soju IRC bouncer management All commands are owner-only and work in channel or PM. !irc - show usage !irc user list - list all bouncer users !irc user add - create a new user !irc user remove - delete a user !irc user passwd - change a user's password !irc user enable - re-enable a disabled user !irc user disable - disable a user !irc net list - list networks for a user !irc net status - show network connection status !irc net add - add a network using a preset !irc net add - add a network with a custom address !irc net remove - remove a network !irc net quote - send raw IRC command to a network !irc net presets - list available network presets !irc bouncer - show bouncer-wide stats !irc notice - broadcast a notice to all users """ import subprocess from sopel import plugin from sopel.config.types import FilenameAttribute, StaticSection import os as _os, sys as _sys _alfred_dir = _os.path.dirname(_os.path.abspath(__file__)) if _alfred_dir not in _sys.path: _sys.path.insert(0, _alfred_dir) import helpstrings as h NETWORK_PRESETS = { "libera": "ircs://irc.libera.chat:6697", "hackint": "ircs://irc.hackint.org:6697", "oftc": "ircs://irc.oftc.net:6697", "rizon": "ircs://irc.rizon.net:6697", "efnet": "ircs://irc.efnet.org:6697", } class SojuSection(StaticSection): config_path = FilenameAttribute("config_path", relative=False) def setup(bot): bot.settings.define_section("soju", SojuSection) def _is_owner(bot, trigger): return trigger.nick == bot.settings.core.owner def _sojuctl(bot, *args, timeout=15): cmd = ["sojuctl"] cfg = bot.settings.soju.config_path if cfg: cmd += ["-config", cfg] cmd += list(args) try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) out = (result.stdout + result.stderr).strip() return out[:400] if out else "(no output)" except subprocess.TimeoutExpired: return "(timed out)" except Exception as e: return f"(error: {e})" def _send(bot, text): for line in text.splitlines(): if line.strip(): bot.say(line.strip()) # --------------------------------------------------------------------------- # !irc # --------------------------------------------------------------------------- @plugin.command("irc") def cmd_irc(bot, trigger): if not _is_owner(bot, trigger): return args = (trigger.group(2) or "").split() if not args: bot.say(h.IRC_TOPIC) return sub = args[0] if sub == "bouncer": _send(bot, _sojuctl(bot, "server", "status")) elif sub == "notice": if len(args) < 2: bot.say("usage: !irc notice ") return _send(bot, _sojuctl(bot, "server", "notice", " ".join(args[1:]))) elif sub == "user": _cmd_user(bot, args[1:]) elif sub == "net": _cmd_net(bot, args[1:]) else: bot.say("unknown subcommand - type !irc for usage") # --------------------------------------------------------------------------- # user subcommands # --------------------------------------------------------------------------- def _cmd_user(bot, args): if not args: bot.say("usage: !irc user |remove |passwd |enable |disable >") return sub = args[0] if sub == "list": _send(bot, _sojuctl(bot, "user", "status")) elif sub == "add" and len(args) == 3: _send(bot, _sojuctl(bot, "user", "create", "-username", args[1], "-password", args[2])) elif sub == "remove" and len(args) == 2: _send(bot, _sojuctl(bot, "user", "delete", args[1])) elif sub == "passwd" and len(args) == 3: _send(bot, _sojuctl(bot, "user", "update", args[1], "-password", args[2])) elif sub == "enable" and len(args) == 2: _send(bot, _sojuctl(bot, "user", "update", args[1], "-enabled", "true")) elif sub == "disable" and len(args) == 2: _send(bot, _sojuctl(bot, "user", "update", args[1], "-enabled", "false")) else: bot.say("usage: !irc user |remove |passwd |enable |disable >") # --------------------------------------------------------------------------- # net subcommands # --------------------------------------------------------------------------- def _cmd_net(bot, args): if not args: bot.say("usage: !irc net |status |add [addr]|remove |quote |presets>") return sub = args[0] if sub == "presets": bot.say("presets: " + ", ".join(f"{k} ({v})" for k, v in NETWORK_PRESETS.items())) elif sub == "list" and len(args) == 2: _send(bot, _sojuctl(bot, "user", "run", args[1], "network", "status")) elif sub == "status" and len(args) == 2: _send(bot, _sojuctl(bot, "user", "run", args[1], "network", "status")) elif sub == "add" and len(args) == 3: name = args[2] addr = NETWORK_PRESETS.get(name) if not addr: bot.say(f"unknown preset '{name}' - use !irc net presets or provide an address") return _send(bot, _sojuctl(bot, "user", "run", args[1], "network", "create", "-name", name, "-addr", addr)) elif sub == "add" and len(args) == 4: _send(bot, _sojuctl(bot, "user", "run", args[1], "network", "create", "-name", args[2], "-addr", args[3])) elif sub == "remove" and len(args) == 3: _send(bot, _sojuctl(bot, "user", "run", args[1], "network", "delete", args[2])) elif sub == "quote" and len(args) >= 4: _send(bot, _sojuctl(bot, "user", "run", args[1], "network", "quote", args[2], " ".join(args[3:]))) else: bot.say("usage: !irc net |status |add [addr]|remove |quote |presets>")