1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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>")
|