Files
derp/plugins/chanmgmt.py
user f86cd1ad49 feat: add IRCv3 cap negotiation, channel management, state persistence
Implement CAP LS 302 flow with configurable ircv3_caps list, replacing
the minimal SASL-only registration. Parse IRCv3 message tags (@key=value)
with proper value unescaping. Add channel management plugin (kick, ban,
unban, topic, mode) and bot API methods. Add SQLite key-value StateStore
for plugin state persistence with !state inspection command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 03:07:06 +01:00

82 lines
2.7 KiB
Python

"""Channel management: kick, ban, unban, topic, mode."""
from derp.plugin import command
def _require_channel(message):
"""Return True if the message originated in a channel."""
return message.is_channel
@command("kick", help="Kick a user: !kick <nick> [reason]", admin=True)
async def cmd_kick(bot, message):
"""Kick a user from the current channel."""
if not _require_channel(message):
await bot.reply(message, "Must be used in a channel")
return
parts = message.text.split(None, 2)
if len(parts) < 2:
await bot.reply(message, "Usage: !kick <nick> [reason]")
return
nick = parts[1]
reason = parts[2] if len(parts) > 2 else ""
await bot.kick(message.target, nick, reason)
@command("ban", help="Ban a hostmask: !ban <mask>", admin=True)
async def cmd_ban(bot, message):
"""Set +b on a hostmask in the current channel."""
if not _require_channel(message):
await bot.reply(message, "Must be used in a channel")
return
parts = message.text.split(None, 1)
if len(parts) < 2:
await bot.reply(message, "Usage: !ban <mask>")
return
mask = parts[1]
await bot.mode(message.target, "+b", mask)
@command("unban", help="Remove a ban: !unban <mask>", admin=True)
async def cmd_unban(bot, message):
"""Remove +b from a hostmask in the current channel."""
if not _require_channel(message):
await bot.reply(message, "Must be used in a channel")
return
parts = message.text.split(None, 1)
if len(parts) < 2:
await bot.reply(message, "Usage: !unban <mask>")
return
mask = parts[1]
await bot.mode(message.target, "-b", mask)
@command("topic", help="Set or query channel topic: !topic [text]", admin=True)
async def cmd_topic(bot, message):
"""Set or query the channel topic."""
if not _require_channel(message):
await bot.reply(message, "Must be used in a channel")
return
parts = message.text.split(None, 1)
if len(parts) < 2:
# Query current topic
from derp.irc import format_msg
await bot.conn.send(format_msg("TOPIC", message.target))
else:
await bot.set_topic(message.target, parts[1])
@command("mode", help="Set channel mode: !mode <mode> [args]", admin=True)
async def cmd_mode(bot, message):
"""Set a mode on the current channel."""
if not _require_channel(message):
await bot.reply(message, "Must be used in a channel")
return
parts = message.text.split(None)
if len(parts) < 2:
await bot.reply(message, "Usage: !mode <mode> [args]")
return
mode_str = parts[1]
args = parts[2:]
await bot.mode(message.target, mode_str, *args)