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>
This commit is contained in:
user
2026-02-15 03:07:06 +01:00
parent 4a2960b288
commit f86cd1ad49
14 changed files with 614 additions and 49 deletions

View File

@@ -1,6 +1,6 @@
"""Tests for IRC message parsing and formatting."""
from derp.irc import format_msg, parse
from derp.irc import _parse_tags, _unescape_tag_value, format_msg, parse
class TestParse:
@@ -75,6 +75,36 @@ class TestParse:
assert msg.target == "#channel"
assert msg.text == "leaving"
def test_no_tags_default(self):
msg = parse(":nick!u@h PRIVMSG #ch :hello")
assert msg.tags == {}
def test_tags_parsed(self):
msg = parse("@time=2026-02-15T12:00:00Z;account=alice "
":nick!user@host PRIVMSG #chan :hello")
assert msg.tags == {"time": "2026-02-15T12:00:00Z", "account": "alice"}
assert msg.nick == "nick"
assert msg.command == "PRIVMSG"
assert msg.text == "hello"
def test_tags_value_unescaping(self):
msg = parse(r"@key=hello\sworld\:end :nick!u@h PRIVMSG #ch :test")
assert msg.tags["key"] == "hello world;end"
def test_tags_no_value(self):
msg = parse("@draft/feature :nick!u@h PRIVMSG #ch :test")
assert msg.tags == {"draft/feature": ""}
def test_tags_backslash_escapes(self):
assert _unescape_tag_value(r"a\\b\r\n") == "a\\b\r\n"
def test_tags_mixed_keys(self):
tags = _parse_tags("a=1;b;c=three")
assert tags == {"a": "1", "b": "", "c": "three"}
def test_tags_empty_string(self):
assert _parse_tags("") == {}
class TestFormat:
"""IRC message formatting tests."""