diff options
Diffstat (limited to 'alfred/soju.py')
| -rw-r--r-- | alfred/soju.py | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/alfred/soju.py b/alfred/soju.py new file mode 100644 index 0000000..02428a5 --- /dev/null +++ b/alfred/soju.py @@ -0,0 +1,193 @@ +""" +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 <nick> <pass> - create a new user + !irc user remove <nick> - delete a user + !irc user passwd <nick> <pass> - change a user's password + !irc user enable <nick> - re-enable a disabled user + !irc user disable <nick> - disable a user + + !irc net list <user> - list networks for a user + !irc net status <user> - show network connection status + !irc net add <user> <name> - add a network using a preset + !irc net add <user> <name> <addr> - add a network with a custom address + !irc net remove <user> <name> - remove a network + !irc net quote <user> <net> <cmd> - send raw IRC command to a network + !irc net presets - list available network presets + + !irc bouncer - show bouncer-wide stats + !irc notice <message> - 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 <message>") + 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 <list|add <nick> <pass>|remove <nick>|passwd <nick> <pass>|enable <nick>|disable <nick>>") + 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 <list|add <nick> <pass>|remove <nick>|passwd <nick> <pass>|enable <nick>|disable <nick>>") + + +# --------------------------------------------------------------------------- +# net subcommands +# --------------------------------------------------------------------------- + +def _cmd_net(bot, args): + if not args: + bot.say("usage: !irc net <list <user>|status <user>|add <user> <name> [addr]|remove <user> <name>|quote <user> <net> <cmd>|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 <list <user>|status <user>|add <user> <name> [addr]|remove <user> <name>|quote <user> <net> <cmd>|presets>") |
