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:
@@ -25,7 +25,7 @@ from plugins.urltitle import ( # noqa: E402, I001
|
||||
_extract_urls,
|
||||
_fetch_title,
|
||||
_is_ignored_url,
|
||||
_seen,
|
||||
_ps,
|
||||
on_privmsg,
|
||||
)
|
||||
|
||||
@@ -40,6 +40,7 @@ class _FakeBot:
|
||||
self.sent: list[tuple[str, str]] = []
|
||||
self.nick = "derp"
|
||||
self.prefix = "!"
|
||||
self._pstate: dict = {}
|
||||
self.config = {
|
||||
"flaskpaste": {"url": "https://paste.mymx.me"},
|
||||
"urltitle": {},
|
||||
@@ -334,26 +335,28 @@ class TestFetchTitle:
|
||||
|
||||
class TestCooldown:
|
||||
def setup_method(self):
|
||||
_seen.clear()
|
||||
self.bot = _FakeBot()
|
||||
|
||||
def test_first_access_not_cooled(self):
|
||||
assert _check_cooldown("https://a.com", 300) is False
|
||||
assert _check_cooldown(self.bot, "https://a.com", 300) is False
|
||||
|
||||
def test_second_access_within_window(self):
|
||||
_check_cooldown("https://b.com", 300)
|
||||
assert _check_cooldown("https://b.com", 300) is True
|
||||
_check_cooldown(self.bot, "https://b.com", 300)
|
||||
assert _check_cooldown(self.bot, "https://b.com", 300) is True
|
||||
|
||||
def test_after_cooldown_expires(self):
|
||||
_seen["https://c.com"] = time.monotonic() - 400
|
||||
assert _check_cooldown("https://c.com", 300) is False
|
||||
seen = _ps(self.bot)["seen"]
|
||||
seen["https://c.com"] = time.monotonic() - 400
|
||||
assert _check_cooldown(self.bot, "https://c.com", 300) is False
|
||||
|
||||
def test_pruning(self):
|
||||
"""Cache is pruned when it exceeds max size."""
|
||||
seen = _ps(self.bot)["seen"]
|
||||
old = time.monotonic() - 600
|
||||
for i in range(600):
|
||||
_seen[f"https://stale-{i}.com"] = old
|
||||
_check_cooldown("https://new.com", 300)
|
||||
assert len(_seen) < 600
|
||||
seen[f"https://stale-{i}.com"] = old
|
||||
_check_cooldown(self.bot, "https://new.com", 300)
|
||||
assert len(seen) < 600
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -361,8 +364,6 @@ class TestCooldown:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestOnPrivmsg:
|
||||
def setup_method(self):
|
||||
_seen.clear()
|
||||
|
||||
def test_channel_url_previewed(self):
|
||||
bot = _FakeBot()
|
||||
|
||||
Reference in New Issue
Block a user