From d0bb02a1ba72e552eb233d552fd006717c8af39c Mon Sep 17 00:00:00 2001 From: ThePeGaSuS <25697531+TehPeGaSuS@users.noreply.github.com> Date: Tue, 5 May 2026 13:39:16 +0200 Subject: [PATCH] Add join/part command to the bot --- bot.py | 31 +++++++++++++++++++++++-- commands.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index 5049f4a..eadba3f 100644 --- a/bot.py +++ b/bot.py @@ -134,7 +134,8 @@ class Bot: await commands.handle_pm( network, nick, prefix, text, - self._database, pm_reply, self.reload) + self._database, pm_reply, self.reload, + self.join_channel, self.part_channel) elif target.startswith("#"): # Channel message async def ch_reply(msg): @@ -142,13 +143,39 @@ class Bot: await commands.handle_channel( network, target, nick, prefix, text, - self._database, ch_reply, self.reload) + self._database, ch_reply, self.reload, + self.join_channel, self.part_channel) async def _on_connected(self, network: str): log.info("[%s] Connected and registered", network) # ── Hot reload ──────────────────────────────────────────────────────────── + async def join_channel(self, network: str, channel: str) -> str: + """Join a channel on a network. Called from IRC commands.""" + client = self._clients.get(network) + if not client: + nets = ", ".join(self._clients) or "none" + return f"Unknown network '{network}'. Connected networks: {nets}" + if client.in_channel(channel): + return f"Already in {channel} on {network}." + client.join(channel) + log.info("Joining %s on %s (via command)", channel, network) + return f"Joining {channel} on {network}." + + async def part_channel(self, network: str, channel: str) -> str: + """Part a channel on a network. Called from IRC commands.""" + client = self._clients.get(network) + if not client: + nets = ", ".join(self._clients) or "none" + return f"Unknown network '{network}'. Connected networks: {nets}" + if not client.in_channel(channel): + return f"Not in {channel} on {network}." + client.send(f"PART {channel} :Leaving") + db.purge_channel(self._database, network, channel) + log.info("Parting %s on %s (via command)", channel, network) + return f"Left {channel} on {network}." + async def reload(self) -> str: """ Re-read the config file and reconcile network connections: diff --git a/commands.py b/commands.py index f9255aa..bc5f436 100644 --- a/commands.py +++ b/commands.py @@ -25,7 +25,7 @@ def parse(text: str): async def handle_pm(network: str, nick: str, prefix: str, text: str, - database, reply, reload_fn=None): + database, reply, reload_fn=None, join_fn=None, part_fn=None): """ Handle a private message to the bot. prefix = "nick!user@host" @@ -88,11 +88,11 @@ async def handle_pm(network: str, nick: str, prefix: str, text: str, if cmd.startswith("!"): bare_cmd, bare_args = parse(text) bare_cmd = bare_cmd or cmd.lstrip("!") - await _shared(bare_cmd, bare_args, None, None, prefix, database, reply, reload_fn) + await _shared(bare_cmd, bare_args, None, None, prefix, database, reply, reload_fn, join_fn, part_fn) async def handle_channel(network: str, channel: str, nick: str, prefix: str, - text: str, database, reply, reload_fn=None): + text: str, database, reply, reload_fn=None, join_fn=None, part_fn=None): """ Handle a channel message. reply = async callable(message) — sends to the channel @@ -109,12 +109,12 @@ async def handle_channel(network: str, channel: str, nick: str, prefix: str, # Silently ignore — don't advertise admin commands to bystanders return - await _shared(cmd, args, network, channel, prefix, database, reply, reload_fn) + await _shared(cmd, args, network, channel, prefix, database, reply, reload_fn, join_fn, part_fn) # ── Shared commands (work in both PM and channel) ───────────────────────────── -async def _shared(cmd, args, network, channel, prefix, database, reply, reload_fn=None): +async def _shared(cmd, args, network, channel, prefix, database, reply, reload_fn=None, join_fn=None, part_fn=None): if cmd == "reload": if reload_fn is None: await reply("Reload not available.") @@ -131,6 +131,10 @@ async def _shared(cmd, args, network, channel, prefix, database, reply, reload_f await reply("!rss must be used in a channel.") return await _rss(network, channel, args, database, reply) + elif cmd == "join": + await _join(args, network, database, reply, join_fn) + elif cmd == "part": + await _part(args, network, channel, database, reply, part_fn) elif cmd in ("help", "bothelp"): await _channel_help(reply) @@ -359,6 +363,53 @@ async def _rss(network, channel, args, database, reply): await reply(RSS_HELP) + +# ── !join / !part ───────────────────────────────────────────────────────────── + +async def _join(args, network, database, reply, join_fn=None): + """join <#channel> [network] — make the bot join a channel.""" + if not args: + await reply("Usage: join <#channel> [network]") + return + channel = args[0] if args[0].startswith("#") else f"#{args[0]}" + target_network = args[1] if len(args) >= 2 else network + if not target_network: + await reply("You must specify a network: join <#channel> ") + return + if join_fn is None: + await reply("Join not available.") + return + result = await join_fn(target_network, channel) + await reply(result) + + +async def _part(args, network, channel, database, reply, part_fn=None): + """part [#channel] [network] — make the bot part a channel.""" + target_channel = channel # default: current channel (if used in-channel) + target_network = network + + if args: + if args[0].startswith("#"): + target_channel = args[0] + if len(args) >= 2: + target_network = args[1] + else: + # treat as network name, keep current channel + target_network = args[0] + + if not target_channel: + await reply("Usage: part <#channel> [network] or part [network] (in a channel)") + return + if not target_network: + await reply("You must specify a network: part <#channel> ") + return + if part_fn is None: + await reply("Part not available.") + return + result = await part_fn(target_network, target_channel) + await reply(result) + + # ── help ────────────────────────────────────────────────────────────────────── async def _pm_help(reply): @@ -374,6 +425,9 @@ async def _pm_help(reply): "── Channel commands ─────────────────────────", " !webhook list/add/remove/events/branches", " !rss list/add/remove", + "── Channel management ───────────────────────", + " join <#channel> [network] make the bot join a channel", + " part <#channel> [network] make the bot leave a channel", ] for line in lines: await reply(line) @@ -383,6 +437,6 @@ async def _channel_help(reply): await reply( "!webhook list/add/remove/events/branches | " "!rss list/add/remove/format | " - "!reload | " + "!reload | join/part <#channel> [network] | " "PM the bot: identify, logout, passwd, hostmask" )