fix: protect server config from truncation and recover from Discord guilds

- Save servers_config.json atomically via temp file + fsync + rename
- Keep .bak backup and auto-restore when main config is empty/corrupt
- Add /servers/recover endpoint for manual recovery
- Auto-recover basic server configs on startup when config is empty but bot is in guilds
This commit is contained in:
2026-06-11 20:37:04 +03:00
parent 486acb5c14
commit cfd5eb16f7
3 changed files with 222 additions and 20 deletions

View File

@@ -136,3 +136,96 @@ def repair_server_config():
return {"status": "ok", "message": "Server configuration repaired and saved"}
except Exception as e:
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to repair configuration: {e}"})
@router.post("/servers/recover")
def recover_servers_from_discord():
"""Auto-discover servers from Discord guilds and create config entries.
Use this when servers_config.json is lost/corrupted and you need to
quickly restore basic server configurations. Each discovered guild gets
a placeholder config using the first available text channel as the
autonomous channel. You can then adjust channels via the dashboard.
"""
if not globals.client or not globals.client.is_ready():
return JSONResponse(status_code=503, content={
"status": "error",
"message": "Discord client not ready — bot must be connected"
})
if not globals.client.guilds:
return JSONResponse(status_code=404, content={
"status": "error",
"message": "Bot is not in any Discord guilds"
})
recovered = []
skipped = []
failed = []
for guild in globals.client.guilds:
guild_id = guild.id
guild_name = guild.name
# Skip if already configured
if server_manager.get_server_config(guild_id):
skipped.append({"guild_id": str(guild_id), "guild_name": guild_name, "reason": "Already configured"})
continue
# Find the first text channel (prefer one named "general" or "chat")
text_channels = [ch for ch in guild.text_channels if ch.permissions_for(guild.me).send_messages]
if not text_channels:
# Try any text channel even without send permissions
text_channels = guild.text_channels
if not text_channels:
failed.append({"guild_id": str(guild_id), "guild_name": guild_name, "reason": "No text channels found"})
continue
# Prefer "general" or "chat" channel, otherwise use the first one
preferred = None
for ch in text_channels:
if ch.name.lower() in ("general", "chat", "main", "lounge", "general-chat"):
preferred = ch
break
channel = preferred or text_channels[0]
try:
success = server_manager.add_server(
guild_id=guild_id,
guild_name=guild_name,
autonomous_channel_id=channel.id,
autonomous_channel_name=f"#{channel.name}",
bedtime_channel_ids=[channel.id],
enabled_features={"autonomous", "bedtime", "monday_video"}
)
if success:
recovered.append({
"guild_id": str(guild_id),
"guild_name": guild_name,
"autonomous_channel": f"#{channel.name} ({channel.id})"
})
logger.info(f"Recovered server config: {guild_name} (ID: {guild_id}) → #{channel.name}")
else:
failed.append({"guild_id": str(guild_id), "guild_name": guild_name, "reason": "add_server returned False"})
except Exception as e:
failed.append({"guild_id": str(guild_id), "guild_name": guild_name, "reason": str(e)})
logger.error(f"Failed to recover server {guild_name}: {e}")
# Restart schedulers if we recovered any servers
if recovered:
try:
server_manager.stop_all_schedulers()
server_manager.start_all_schedulers(globals.client)
except Exception as e:
logger.error(f"Failed to restart schedulers after recovery: {e}")
return {
"status": "ok",
"recovered": recovered,
"skipped": skipped,
"failed": failed,
"total_guilds": len(globals.client.guilds),
"note": "Recovered servers use the first text channel as autonomous channel. "
"Use the Servers tab to adjust channel settings."
}