feat: harden multi-account backend and add tests

This commit is contained in:
2026-06-27 21:47:45 +02:00
parent 7aa5c84e63
commit ff1ca13a03
4 changed files with 653 additions and 5 deletions
+41 -2
View File
@@ -760,6 +760,33 @@ class PerAccountContinuousScrapeManager:
}
store.update(mutate)
def refresh_config(self) -> None:
"""
Re-read account state from disk and sync the in-memory config.
Call this after external state changes (import, channel add/remove)
so the manager picks up new channels or config updates without a restart.
"""
disk_state = load_account(DATA_DIR, self.account_id)
disk_cfg = disk_state.get("continuous_scraping") or {}
with self.lock:
# Merge disk config into memory, preserving our current running state
self.config["enabled"] = bool(disk_cfg.get("enabled", False))
self.config["interval_minutes"] = max(1, int(disk_cfg.get("interval_minutes", 1) or 1))
self.config["channels"] = [
str(item).strip()
for item in disk_cfg.get("channels", [])
if str(item).strip()
]
self.config["run_all_tracked"] = bool(disk_cfg.get("run_all_tracked", True))
# Sync running state with desired enabled state
if self.config["enabled"] and not self.status["running"]:
pass # don't auto-start — user must call start()
elif not self.config["enabled"] and self.status["running"]:
self._log("Continuous disabled via external state change, stopping.", "warn")
self.stop_event.set()
self.status["running"] = False
self._log("Config refreshed from disk.", "debug")
def _log(self, message: str, level: str = "debug") -> None:
timestamp = utc_now_iso()
entry = {"timestamp": timestamp, "level": level, "message": message}
@@ -840,6 +867,9 @@ class PerAccountContinuousScrapeManager:
def _run_loop(self) -> None:
while not self.stop_event.is_set():
# Refresh config from disk so channel / setting changes take effect
self.refresh_config()
# Check auth — don't iterate if account isn't authorized
auth_info = auth_status_for(self.account_id)
if auth_info.get("status") not in ("ready", "authorized"):
@@ -936,6 +966,11 @@ class ContinuousScrapeOrchestrator:
result[account_id] = dict(mgr.snapshot())
return result
def refresh_account(self, account_id: str) -> None:
"""Re-read account state from disk and sync the continuous manager config."""
mgr = self._get_or_create(account_id)
mgr.refresh_config()
def update_for(
self,
account_id: str,
@@ -945,7 +980,9 @@ class ContinuousScrapeOrchestrator:
run_all_tracked: bool,
) -> Dict[str, Any]:
mgr = self._get_or_create(account_id)
return mgr.update(enabled, interval_minutes, channels, run_all_tracked)
result = mgr.update(enabled, interval_minutes, channels, run_all_tracked)
# After explicit update, sync state to disk is already done by mgr.update
return result
def add_account(self, account_id: str, auto_start: bool = True) -> None:
acc_state = load_account(DATA_DIR, account_id)
@@ -1825,7 +1862,6 @@ class TelegramScraperRequestHandler(BaseHTTPRequestHandler):
def _legacy_auth_snapshot(self) -> Dict[str, Any]:
state = load_state()
session_ready = (SESSION_DIR / "session.session").exists()
return {
"phase": "unknown",
"status": "unknown",
@@ -2273,6 +2309,7 @@ class TelegramScraperRequestHandler(BaseHTTPRequestHandler):
get_global_store(DATA_DIR).update(mutate_global)
get_account_store(DATA_DIR, account_id).save(imported_state)
self.app.continuous_orchestrator.add_account(account_id, auto_start=False)
self.app.continuous_orchestrator.refresh_account(account_id)
return self.send_json({"ok": True, "account_id": account_id})
def _handle_post_account(self, path: str, body: Dict[str, Any]) -> None:
@@ -2381,6 +2418,7 @@ class TelegramScraperRequestHandler(BaseHTTPRequestHandler):
if name:
state.setdefault("channel_names", {})[channel_id] = str(name).strip()
store.update(mutate)
self.app.continuous_orchestrator.refresh_account(account_id)
return self.send_json({"ok": True, "channel_id": channel_id})
def _handle_account_channel_remove(self, account_id: str, channel_id: str) -> None:
@@ -2395,6 +2433,7 @@ class TelegramScraperRequestHandler(BaseHTTPRequestHandler):
del chans[channel_id]
state.get("channel_names", {}).pop(channel_id, None)
store.update(mutate)
self.app.continuous_orchestrator.refresh_account(account_id)
return self.send_json({"ok": existed[0], "channel_id": channel_id})
def _handle_account_job_scrape(self, account_id: str, body: Dict[str, Any]) -> None: