feat: add multi-server support

Connect to multiple IRC servers concurrently from a single config file.
Plugins are loaded once and shared; per-server state is isolated via
separate SQLite databases and per-bot runtime state (bot._pstate).

- Add build_server_configs() for [servers.*] config layout
- Bot.__init__ gains name parameter, _pstate dict for plugin isolation
- cli.py runs multiple bots via asyncio.gather
- 9 stateful plugins migrated from module-level dicts to _ps(bot) pattern
- Backward compatible: legacy [server] config works unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-21 19:04:20 +01:00
parent e9528bd879
commit 073659607e
27 changed files with 987 additions and 735 deletions

View File

@@ -384,7 +384,7 @@ class TestPrefixMatch:
async def _noop(bot, msg):
pass
registry.register_command(name, _noop, plugin="test")
return Bot(config, registry)
return Bot("test", config, registry)
def test_exact_match(self):
bot = self._make_bot(["ping", "pong", "plugins"])
@@ -438,7 +438,7 @@ class TestIsAdmin:
"bot": {"prefix": "!", "channels": [], "plugins_dir": "plugins",
"admins": admins or []},
}
bot = Bot(config, PluginRegistry())
bot = Bot("test", config, PluginRegistry())
if opers:
bot._opers = opers
return bot
@@ -565,7 +565,7 @@ def _make_test_bot() -> Bot:
"nick": "test", "user": "test", "realname": "test"},
"bot": {"prefix": "!", "channels": [], "plugins_dir": "plugins"},
}
bot = Bot(config, PluginRegistry())
bot = Bot("test", config, PluginRegistry())
bot.conn = _FakeConnection() # type: ignore[assignment]
return bot
@@ -637,7 +637,7 @@ class TestChannelFilter:
"bot": {"prefix": "!", "channels": [], "plugins_dir": "plugins"},
"channels": channels_cfg or {},
}
return Bot(config, PluginRegistry())
return Bot("test", config, PluginRegistry())
def test_core_always_allowed(self):
bot = self._make_bot({"#locked": {"plugins": ["core"]}})