aboutsummaryrefslogtreecommitdiffstats
path: root/alfred/soju.py
diff options
context:
space:
mode:
authorAhmed <git@gumx.cc>2026-06-14 01:46:29 +0300
committerAhmed <git@gumx.cc>2026-06-14 01:46:29 +0300
commit5a8d568931d9b23ce0df1265d05259a7012081c9 (patch)
tree4ea19b8bb1763a38246f342f172c285f6579e09b /alfred/soju.py
init: mostly vibed
Diffstat (limited to 'alfred/soju.py')
-rw-r--r--alfred/soju.py193
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>")