diff --git a/bot/api.py b/bot/api.py index 58593df..742b9f8 100644 --- a/bot/api.py +++ b/bot/api.py @@ -4,6 +4,7 @@ # To revert: cp api_monolith_backup.py api.py from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from utils.logger import get_logger from utils.log_config import load_config as load_log_config @@ -23,7 +24,7 @@ app = FastAPI() async def global_exception_handler(request: Request, exc: Exception): """Catch all unhandled exceptions and log them properly.""" logger.error(f"Unhandled exception on {request.method} {request.url.path}: {exc}", exc_info=True) - return {"success": False, "error": "Internal server error"} + return JSONResponse(status_code=500, content={"success": False, "error": "Internal server error"}) # ========== Logging Middleware ========== @app.middleware("http") diff --git a/bot/routes/autonomous.py b/bot/routes/autonomous.py index 0e76b3c..be4c5e4 100644 --- a/bot/routes/autonomous.py +++ b/bot/routes/autonomous.py @@ -1,6 +1,7 @@ """Autonomous action routes: V1, V2, per-server autonomous.""" from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from server_manager import server_manager from routes.models import CustomPromptRequest @@ -25,7 +26,7 @@ async def trigger_autonomous_general(guild_id: int = None): globals.client.loop.create_task(miku_say_something_general()) return {"status": "ok", "message": "Autonomous general message queued for all servers"} else: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) @router.post("/autonomous/engage") @@ -65,7 +66,7 @@ async def trigger_autonomous_engage_user( return {"status": "ok", "message": " ".join(msg_parts)} else: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) @router.post("/autonomous/tweet") @@ -86,7 +87,7 @@ async def trigger_autonomous_tweet(guild_id: int = None, tweet_url: str = None): msg += f" with URL {tweet_url}" return {"status": "ok", "message": msg} else: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) @router.post("/autonomous/custom") @@ -101,7 +102,7 @@ async def custom_autonomous_message(req: CustomPromptRequest, guild_id: int = No globals.client.loop.create_task(handle_custom_prompt(req.prompt)) return {"status": "ok", "message": "Custom autonomous message queued for all servers"} else: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) @router.post("/autonomous/reaction") @@ -116,7 +117,7 @@ async def trigger_autonomous_reaction(guild_id: int = None): globals.client.loop.create_task(miku_autonomous_reaction(force=True)) return {"status": "ok", "message": "Autonomous reaction queued for all servers"} else: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) @router.post("/autonomous/join-conversation") @@ -135,7 +136,7 @@ async def trigger_detect_and_join_conversation(guild_id: int = None): return {"status": "ok", "message": "Detect and join conversation queued for all servers"} else: logger.error(f"Bot not ready: client={globals.client}, loop={globals.client.loop if globals.client else None}") - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) # ========== Per-Server Autonomous ========== @@ -148,7 +149,7 @@ async def trigger_autonomous_general_for_server(guild_id: int): await miku_say_something_general_for_server(guild_id) return {"status": "ok", "message": f"Autonomous general message triggered for server {guild_id}"} except Exception as e: - return {"status": "error", "message": f"Failed to trigger autonomous message: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to trigger autonomous message: {e}"}) @router.post("/servers/{guild_id}/autonomous/engage") @@ -175,7 +176,7 @@ async def trigger_autonomous_engage_for_server( return {"status": "ok", "message": " ".join(msg_parts)} except Exception as e: - return {"status": "error", "message": f"Failed to trigger user engagement: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to trigger user engagement: {e}"}) @router.post("/servers/{guild_id}/autonomous/custom") @@ -187,9 +188,9 @@ async def custom_autonomous_message_for_server(guild_id: int, req: CustomPromptR if success: return {"status": "ok", "message": f"Custom autonomous message sent to server {guild_id}"} else: - return {"status": "error", "message": f"Failed to send custom message to server {guild_id}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to send custom message to server {guild_id}"}) except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.post("/servers/{guild_id}/autonomous/tweet") @@ -200,7 +201,7 @@ async def trigger_autonomous_tweet_for_server(guild_id: int): await share_miku_tweet_for_server(guild_id) return {"status": "ok", "message": f"Autonomous tweet sharing triggered for server {guild_id}"} except Exception as e: - return {"status": "error", "message": f"Failed to trigger tweet sharing: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to trigger tweet sharing: {e}"}) # ========== Autonomous V2 ========== @@ -213,7 +214,7 @@ async def get_v2_stats(guild_id: int): stats = get_v2_stats_for_server(guild_id) return {"status": "ok", "guild_id": guild_id, "stats": stats} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.get("/autonomous/v2/check/{guild_id}") @@ -223,16 +224,16 @@ async def manual_v2_check(guild_id: int): from utils.autonomous_v2_integration import manual_trigger_v2_check if not globals.client: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) result = await manual_trigger_v2_check(guild_id, globals.client) if isinstance(result, str): - return {"status": "error", "message": result} + return JSONResponse(status_code=500, content={"status": "error", "message": result}) return {"status": "ok", "guild_id": guild_id, "analysis": result} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.get("/autonomous/v2/status") @@ -258,4 +259,4 @@ async def get_v2_status(): return {"status": "ok", "servers": status} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) diff --git a/bot/routes/bipolar_mode.py b/bot/routes/bipolar_mode.py index aa1708e..dc1a406 100644 --- a/bot/routes/bipolar_mode.py +++ b/bot/routes/bipolar_mode.py @@ -2,6 +2,7 @@ import asyncio from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from routes.models import BipolarTriggerRequest from utils.logger import get_logger @@ -106,23 +107,23 @@ def trigger_argument(data: BipolarTriggerRequest): try: channel_id = int(data.channel_id) except ValueError: - return {"status": "error", "message": "Invalid channel ID format"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid channel ID format"}) message_id = None if data.message_id: try: message_id = int(data.message_id) except ValueError: - return {"status": "error", "message": "Invalid message ID format"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid message ID format"}) if not is_bipolar_mode(): - return {"status": "error", "message": "Bipolar mode is not enabled"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Bipolar mode is not enabled"}) if is_argument_in_progress(channel_id): - return {"status": "error", "message": "An argument is already in progress in this channel"} + return JSONResponse(status_code=409, content={"status": "error", "message": "An argument is already in progress in this channel"}) if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Discord client not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"}) # If message_id is provided, use the message-based trigger if message_id: @@ -145,7 +146,7 @@ def trigger_argument(data: BipolarTriggerRequest): # Otherwise, find the channel and trigger normally channel = globals.client.get_channel(channel_id) if not channel: - return {"status": "error", "message": f"Channel {channel_id} not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"Channel {channel_id} not found"}) # Trigger the argument globals.client.loop.create_task(force_trigger_argument(channel, globals.client, data.context)) @@ -168,19 +169,19 @@ def trigger_dialogue(data: dict): message_id_str = data.get("message_id") if not message_id_str: - return {"status": "error", "message": "Message ID is required"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Message ID is required"}) # Parse message ID try: message_id = int(message_id_str) except ValueError: - return {"status": "error", "message": "Invalid message ID format"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid message ID format"}) if not is_bipolar_mode(): - return {"status": "error", "message": "Bipolar mode is not enabled"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Bipolar mode is not enabled"}) if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Discord client not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"}) async def trigger_dialogue_task(): try: @@ -191,7 +192,7 @@ def trigger_dialogue(data: dict): try: message = await channel.fetch_message(message_id) break - except: + except Exception: continue if not message: @@ -272,7 +273,7 @@ def cleanup_bipolar_webhooks(): from utils.bipolar_mode import cleanup_webhooks if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Discord client not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"}) globals.client.loop.create_task(cleanup_webhooks(globals.client)) return {"status": "ok", "message": "Webhook cleanup started"} diff --git a/bot/routes/bot_actions.py b/bot/routes/bot_actions.py index c384027..f1f4307 100644 --- a/bot/routes/bot_actions.py +++ b/bot/routes/bot_actions.py @@ -1,6 +1,7 @@ """Core bot action routes: conversation reset, sleep, wake, bedtime.""" from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from commands.actions import ( force_sleep, @@ -49,4 +50,4 @@ async def bedtime_endpoint(guild_id: int = None): globals.client.loop.create_task(send_bedtime_now()) return {"status": "ok", "message": "Bedtime reminder queued for all servers"} else: - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) diff --git a/bot/routes/config.py b/bot/routes/config.py index 4f4bd72..35cbda2 100644 --- a/bot/routes/config.py +++ b/bot/routes/config.py @@ -1,6 +1,7 @@ """Configuration management routes: get/set/reset/validate config.""" from fastapi import APIRouter, Request +from fastapi.responses import JSONResponse import globals from utils.logger import get_logger @@ -24,7 +25,7 @@ async def get_full_config(): } except Exception as e: logger.error(f"Failed to get config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.get("/config/static") @@ -41,7 +42,7 @@ async def get_static_config(): } except Exception as e: logger.error(f"Failed to get static config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.get("/config/runtime") @@ -59,7 +60,7 @@ async def get_runtime_config(): } except Exception as e: logger.error(f"Failed to get runtime config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/config/set") @@ -80,7 +81,7 @@ async def set_config_value(request: Request): persist = data.get("persist", True) if not key_path: - return {"success": False, "error": "key_path is required"} + return JSONResponse(status_code=400, content={"success": False, "error": "key_path is required"}) from config_manager import config_manager config_manager.set(key_path, value, persist=persist) @@ -114,7 +115,7 @@ async def set_config_value(request: Request): } except Exception as e: logger.error(f"Failed to set config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/config/reset") @@ -143,7 +144,7 @@ async def reset_config(request: Request): } except Exception as e: logger.error(f"Failed to reset config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/config/validate") @@ -163,7 +164,7 @@ async def validate_config_endpoint(): } except Exception as e: logger.error(f"Failed to validate config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.get("/config/state") @@ -180,4 +181,4 @@ async def get_config_state(): } except Exception as e: logger.error(f"Failed to get config state: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) diff --git a/bot/routes/core.py b/bot/routes/core.py index 11a1448..b0a3299 100644 --- a/bot/routes/core.py +++ b/bot/routes/core.py @@ -1,7 +1,7 @@ """Core routes: index, logs, prompts, status, conversation.""" from fastapi import APIRouter -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, JSONResponse import globals from server_manager import server_manager from utils.conversation_history import conversation_history @@ -26,7 +26,7 @@ def get_logs(): last_100 = lines[-100:] if len(lines) >= 100 else lines return "".join(last_100) except Exception as e: - return f"Error reading log file: {e}" + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error reading log file: {e}"}) @router.get("/prompt") diff --git a/bot/routes/dms.py b/bot/routes/dms.py index d04161b..6ade7aa 100644 --- a/bot/routes/dms.py +++ b/bot/routes/dms.py @@ -5,6 +5,7 @@ import os import json from typing import List from fastapi import APIRouter, UploadFile, File, Form +from fastapi.responses import JSONResponse import discord import globals from routes.models import CustomPromptRequest @@ -25,7 +26,7 @@ async def send_custom_prompt_dm(user_id: str, req: CustomPromptRequest): user_id_int = int(user_id) user = globals.client.get_user(user_id_int) if not user: - return {"status": "error", "message": f"User {user_id} not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"User {user_id} not found"}) # Use the LLM query function for DM context from utils.llm import query_llama @@ -47,9 +48,9 @@ async def send_custom_prompt_dm(user_id: str, req: CustomPromptRequest): return {"status": "ok", "message": f"Custom DM prompt queued for user {user_id}"} except ValueError: - return {"status": "error", "message": "Invalid user ID format"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID format"}) except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.post("/dm/{user_id}/manual") @@ -65,7 +66,7 @@ async def send_manual_message_dm( user_id_int = int(user_id) user = globals.client.get_user(user_id_int) if not user: - return {"status": "error", "message": f"User {user_id} not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"User {user_id} not found"}) # Read file content immediately before the request closes file_data = [] @@ -78,7 +79,7 @@ async def send_manual_message_dm( }) except Exception as e: logger.error(f"Failed to read file {file.filename}: {e}") - return {"status": "error", "message": f"Failed to read file {file.filename}: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to read file {file.filename}: {e}"}) async def send_dm_message_and_files(): try: @@ -120,9 +121,9 @@ async def send_manual_message_dm( return {"status": "ok", "message": f"Manual DM message queued for user {user_id}"} except ValueError: - return {"status": "error", "message": "Invalid user ID format"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID format"}) except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) # ========== DM Logging Endpoints ========== @@ -134,7 +135,7 @@ def get_dm_users(): users = dm_logger.get_all_dm_users() return {"status": "ok", "users": users} except Exception as e: - return {"status": "error", "message": f"Failed to get DM users: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get DM users: {e}"}) @router.get("/dms/users/{user_id}") @@ -146,9 +147,9 @@ def get_dm_user_conversation(user_id: str): summary = dm_logger.get_user_conversation_summary(user_id_int) return {"status": "ok", "summary": summary} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: - return {"status": "error", "message": f"Failed to get user conversation: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get user conversation: {e}"}) @router.get("/dms/users/{user_id}/conversations") @@ -180,10 +181,10 @@ def get_dm_conversations(user_id: str, limit: int = 50): return {"status": "ok", "conversations": conversations} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to get conversations for user {user_id}: {e}") - return {"status": "error", "message": f"Failed to get conversations: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get conversations: {e}"}) @router.get("/dms/users/{user_id}/search") @@ -195,9 +196,9 @@ def search_dm_conversations(user_id: str, query: str, limit: int = 10): results = dm_logger.search_user_conversations(user_id_int, query, limit) return {"status": "ok", "results": results} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: - return {"status": "error", "message": f"Failed to search conversations: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to search conversations: {e}"}) @router.get("/dms/users/{user_id}/export") @@ -209,9 +210,9 @@ def export_dm_conversation(user_id: str, format: str = "json"): export_path = dm_logger.export_user_conversation(user_id_int, format) return {"status": "ok", "export_path": export_path, "format": format} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: - return {"status": "error", "message": f"Failed to export conversation: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to export conversation: {e}"}) @router.delete("/dms/users/{user_id}") @@ -225,11 +226,11 @@ def delete_dm_user_logs(user_id: str): os.remove(log_file) return {"status": "ok", "message": f"Deleted DM logs for user {user_id}"} else: - return {"status": "error", "message": f"No DM logs found for user {user_id}"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"No DM logs found for user {user_id}"}) except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: - return {"status": "error", "message": f"Failed to delete DM logs: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete DM logs: {e}"}) # ========== User Blocking & DM Management ========== @@ -242,7 +243,7 @@ def get_blocked_users(): return {"status": "ok", "blocked_users": blocked_users} except Exception as e: logger.error(f"Failed to get blocked users: {e}") - return {"status": "error", "message": f"Failed to get blocked users: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get blocked users: {e}"}) @router.post("/dms/users/{user_id}/block") @@ -261,13 +262,13 @@ def block_user(user_id: str): logger.info(f"User {user_id} ({username}) blocked") return {"status": "ok", "message": f"User {username} has been blocked"} else: - return {"status": "error", "message": f"User {username} is already blocked"} + return JSONResponse(status_code=409, content={"status": "error", "message": f"User {username} is already blocked"}) except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to block user {user_id}: {e}") - return {"status": "error", "message": f"Failed to block user: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to block user: {e}"}) @router.post("/dms/users/{user_id}/unblock") @@ -281,13 +282,13 @@ def unblock_user(user_id: str): logger.info(f"User {user_id} unblocked") return {"status": "ok", "message": f"User has been unblocked"} else: - return {"status": "error", "message": f"User is not blocked"} + return JSONResponse(status_code=409, content={"status": "error", "message": f"User is not blocked"}) except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to unblock user {user_id}: {e}") - return {"status": "error", "message": f"Failed to unblock user: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to unblock user: {e}"}) @router.post("/dms/users/{user_id}/conversations/{conversation_id}/delete") @@ -308,10 +309,10 @@ def delete_conversation(user_id: str, conversation_id: str): return {"status": "ok", "message": "Message deletion queued (will delete from both Discord and logs)"} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to queue conversation deletion {conversation_id}: {e}") - return {"status": "error", "message": f"Failed to delete conversation: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete conversation: {e}"}) @router.post("/dms/users/{user_id}/conversations/delete-all") @@ -331,10 +332,10 @@ def delete_all_conversations(user_id: str): return {"status": "ok", "message": "Bulk deletion queued (will delete all Miku messages from Discord and clear logs)"} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to queue bulk conversation deletion for user {user_id}: {e}") - return {"status": "error", "message": f"Failed to delete conversations: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete conversations: {e}"}) @router.post("/dms/users/{user_id}/delete-completely") @@ -348,13 +349,13 @@ def delete_user_completely(user_id: str): logger.info(f"Completely deleted user {user_id}") return {"status": "ok", "message": "User data deleted completely"} else: - return {"status": "error", "message": "No user data found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "No user data found"}) except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to completely delete user {user_id}: {e}") - return {"status": "error", "message": f"Failed to delete user: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete user: {e}"}) # ========== DM Interaction Analysis Endpoints ========== @@ -366,7 +367,7 @@ def run_dm_analysis(): from utils.dm_interaction_analyzer import dm_analyzer if dm_analyzer is None: - return {"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."} + return JSONResponse(status_code=503, content={"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."}) # Schedule analysis in Discord's event loop async def run_analysis(): @@ -377,7 +378,7 @@ def run_dm_analysis(): return {"status": "ok", "message": "DM analysis started"} except Exception as e: logger.error(f"Failed to run DM analysis: {e}") - return {"status": "error", "message": f"Failed to run DM analysis: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to run DM analysis: {e}"}) @router.post("/dms/users/{user_id}/analyze") @@ -387,7 +388,7 @@ def analyze_user_interaction(user_id: str): from utils.dm_interaction_analyzer import dm_analyzer if dm_analyzer is None: - return {"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."} + return JSONResponse(status_code=503, content={"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."}) user_id_int = int(user_id) @@ -401,10 +402,10 @@ def analyze_user_interaction(user_id: str): return {"status": "ok", "message": f"Analysis started for user {user_id}", "reported": True} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to analyze user {user_id}: {e}") - return {"status": "error", "message": f"Failed to analyze user: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to analyze user: {e}"}) @router.get("/dms/analysis/reports") @@ -432,7 +433,7 @@ def get_analysis_reports(limit: int = 20): return {"status": "ok", "reports": reports} except Exception as e: logger.error(f"Failed to get reports: {e}") - return {"status": "error", "message": f"Failed to get reports: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get reports: {e}"}) @router.get("/dms/analysis/reports/{user_id}") @@ -461,7 +462,7 @@ def get_user_reports(user_id: str, limit: int = 10): return {"status": "ok", "reports": reports} except ValueError: - return {"status": "error", "message": f"Invalid user ID format: {user_id}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"}) except Exception as e: logger.error(f"Failed to get user reports: {e}") - return {"status": "error", "message": f"Failed to get user reports: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get user reports: {e}"}) diff --git a/bot/routes/evil_mode.py b/bot/routes/evil_mode.py index 0ff0a82..b006e96 100644 --- a/bot/routes/evil_mode.py +++ b/bot/routes/evil_mode.py @@ -1,6 +1,7 @@ """Evil mode routes.""" from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from routes.models import EvilMoodSetRequest from utils.logger import get_logger @@ -43,7 +44,7 @@ def enable_evil_mode(): globals.client.loop.create_task(apply_evil_mode_changes(globals.client)) return {"status": "ok", "message": "Evil mode enabled", "evil_mode": True} else: - return {"status": "error", "message": "Discord client not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"}) @router.post("/evil-mode/disable") @@ -58,7 +59,7 @@ def disable_evil_mode(): globals.client.loop.create_task(revert_evil_mode_changes(globals.client)) return {"status": "ok", "message": "Evil mode disabled", "evil_mode": False} else: - return {"status": "error", "message": "Discord client not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"}) @router.post("/evil-mode/toggle") @@ -67,7 +68,7 @@ def toggle_evil_mode(): from utils.evil_mode import apply_evil_mode_changes, revert_evil_mode_changes if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Discord client not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"}) if globals.EVIL_MODE: globals.client.loop.create_task(revert_evil_mode_changes(globals.client)) @@ -95,10 +96,10 @@ def set_evil_mood_endpoint(data: EvilMoodSetRequest): from utils.evil_mode import set_evil_mood, is_valid_evil_mood, update_all_evil_nicknames if not is_valid_evil_mood(data.mood): - return { + return JSONResponse(status_code=400, content={ "status": "error", "message": f"Mood '{data.mood}' not recognized. Available evil moods: {', '.join(globals.EVIL_AVAILABLE_MOODS)}" - } + }) success = set_evil_mood(data.mood) if success: @@ -107,4 +108,4 @@ def set_evil_mood_endpoint(data: EvilMoodSetRequest): globals.client.loop.create_task(update_all_evil_nicknames(globals.client)) return {"status": "ok", "new_mood": data.mood} - return {"status": "error", "message": "Failed to set evil mood"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to set evil mood"}) diff --git a/bot/routes/figurines.py b/bot/routes/figurines.py index 40df1f4..ac2b78f 100644 --- a/bot/routes/figurines.py +++ b/bot/routes/figurines.py @@ -1,6 +1,7 @@ """Figurine subscriber and send routes.""" from fastapi import APIRouter, Form +from fastapi.responses import JSONResponse import globals from utils.figurine_notifier import ( load_subscribers as figurine_load_subscribers, @@ -29,7 +30,7 @@ async def add_figurine_subscriber(user_id: str = Form(...)): ok = figurine_add_subscriber(uid) return {"status": "ok", "added": ok} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.delete("/figurines/subscribers/{user_id}") @@ -39,7 +40,7 @@ async def delete_figurine_subscriber(user_id: str): ok = figurine_remove_subscriber(uid) return {"status": "ok", "removed": ok} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/figurines/send_now") @@ -49,7 +50,7 @@ async def figurines_send_now(tweet_url: str = Form(None)): logger.info(f"Sending figurine DMs to all subscribers, tweet_url: {tweet_url}") globals.client.loop.create_task(send_figurine_dm_to_all_subscribers(globals.client, tweet_url=tweet_url)) return {"status": "ok", "message": "Figurine DMs queued"} - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) @router.post("/figurines/send_to_user") @@ -59,14 +60,14 @@ async def figurines_send_to_user(user_id: str = Form(...), tweet_url: str = Form if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): logger.error("Bot not ready") - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: user_id_int = int(user_id) logger.debug(f"Parsed user_id as {user_id_int}") except ValueError: logger.error(f"Invalid user ID: '{user_id}'") - return {"status": "error", "message": "Invalid user ID"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID"}) # Clean up tweet URL if it's empty string if tweet_url == "": diff --git a/bot/routes/gpu.py b/bot/routes/gpu.py index 15c460c..b98e4b7 100644 --- a/bot/routes/gpu.py +++ b/bot/routes/gpu.py @@ -1,6 +1,7 @@ """GPU selection routes.""" from fastapi import APIRouter, Request +from fastapi.responses import JSONResponse from utils.logger import get_logger logger = get_logger('api') @@ -22,7 +23,7 @@ async def select_gpu(request: Request): gpu = data.get("gpu", "nvidia").lower() if gpu not in ["nvidia", "amd"]: - return {"status": "error", "message": "Invalid GPU selection. Must be 'nvidia' or 'amd'"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid GPU selection. Must be 'nvidia' or 'amd'"}) try: from config_manager import config_manager @@ -32,7 +33,7 @@ async def select_gpu(request: Request): logger.info(f"GPU Selection: Switched to {gpu.upper()} GPU") return {"status": "ok", "message": f"Switched to {gpu.upper()} GPU", "gpu": gpu} else: - return {"status": "error", "message": "Failed to save GPU state"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to save GPU state"}) except Exception as e: logger.error(f"GPU Selection Error: {e}") - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) diff --git a/bot/routes/image_generation.py b/bot/routes/image_generation.py index 097386d..a143cad 100644 --- a/bot/routes/image_generation.py +++ b/bot/routes/image_generation.py @@ -2,7 +2,7 @@ import os from fastapi import APIRouter -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, JSONResponse from utils.logger import get_logger logger = get_logger('api') @@ -16,7 +16,7 @@ async def manual_image_generation(req: dict): try: prompt = req.get("prompt", "").strip() if not prompt: - return {"status": "error", "message": "Prompt is required"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Prompt is required"}) from utils.image_generation import generate_image_with_comfyui image_path = await generate_image_with_comfyui(prompt) @@ -24,10 +24,10 @@ async def manual_image_generation(req: dict): if image_path: return {"status": "ok", "message": f"Image generated successfully", "image_path": image_path} else: - return {"status": "error", "message": "Failed to generate image"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to generate image"}) except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.get("/image/status") @@ -39,7 +39,7 @@ async def get_image_generation_status(): return {"status": "ok", **status} except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.post("/image/test-detection") @@ -48,7 +48,7 @@ async def test_image_detection(req: dict): try: message = req.get("message", "").strip() if not message: - return {"status": "error", "message": "Message is required"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Message is required"}) from utils.image_generation import detect_image_request is_image_request, extracted_prompt = await detect_image_request(message) @@ -61,7 +61,7 @@ async def test_image_detection(req: dict): } except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.get("/image/view/{filename}") @@ -88,7 +88,7 @@ async def view_generated_image(filename: str): if not image_path: logger.warning(f"Image not found anywhere: {filename}") - return {"status": "error", "message": f"Image not found: {filename}"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"Image not found: {filename}"}) # Determine content type based on file extension ext = filename.lower().split('.')[-1] @@ -105,4 +105,4 @@ async def view_generated_image(filename: str): except Exception as e: logger.error(f"Error serving image: {e}") - return {"status": "error", "message": f"Error serving image: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error serving image: {e}"}) diff --git a/bot/routes/language.py b/bot/routes/language.py index b4715d6..bac6ebe 100644 --- a/bot/routes/language.py +++ b/bot/routes/language.py @@ -1,6 +1,7 @@ """Language mode routes.""" from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from utils.logger import get_logger @@ -53,7 +54,7 @@ def toggle_language_mode(): def set_language_mode(language: str = "english"): """Set language mode to either 'english' or 'japanese'""" if language.lower() not in ["english", "japanese"]: - return {"error": f"Invalid language mode '{language}'. Use 'english' or 'japanese'."}, 400 + return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid language mode '{language}'. Use 'english' or 'japanese'."}) globals.LANGUAGE_MODE = language.lower() model_used = globals.JAPANESE_TEXT_MODEL if language.lower() == "japanese" else globals.TEXT_MODEL diff --git a/bot/routes/logging_config.py b/bot/routes/logging_config.py index 9226254..3cbbefa 100644 --- a/bot/routes/logging_config.py +++ b/bot/routes/logging_config.py @@ -1,6 +1,7 @@ """Logging configuration routes: get/set log levels, filters, components.""" from fastapi import APIRouter +from fastapi.responses import JSONResponse from routes.models import LogConfigUpdateRequest, LogFilterUpdateRequest from utils.logger import get_logger, list_components, get_component_stats from utils.log_config import ( @@ -23,7 +24,7 @@ async def get_log_config(): return {"success": True, "config": config} except Exception as e: logger.error(f"Failed to get log config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/api/log/config") @@ -37,13 +38,13 @@ async def update_log_config(request: LogConfigUpdateRequest): enabled_levels=request.enabled_levels ) if not success: - return {"success": False, "error": f"Failed to update component {request.component}"} + return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to update component {request.component}"}) logger.info(f"Log config updated: component={request.component}, enabled_levels={request.enabled_levels}, enabled={request.enabled}") return {"success": True, "message": "Configuration updated"} except Exception as e: logger.error(f"Failed to update log config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.get("/api/log/components") @@ -60,7 +61,7 @@ async def get_log_components(): } except Exception as e: logger.error(f"Failed to get log components: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/api/log/reload") @@ -72,10 +73,10 @@ async def reload_log_config(): logger.info("Log configuration reloaded") return {"success": True, "message": "Configuration reloaded"} else: - return {"success": False, "error": "Failed to reload configuration"} + return JSONResponse(status_code=500, content={"success": False, "error": "Failed to reload configuration"}) except Exception as e: logger.error(f"Failed to reload log config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/api/log/filters") @@ -93,10 +94,10 @@ async def update_log_filters(request: LogFilterUpdateRequest): logger.info(f"API filters updated: {request.dict(exclude_none=True)}") return {"success": True, "message": "Filters updated"} else: - return {"success": False, "error": "Failed to update filters"} + return JSONResponse(status_code=500, content={"success": False, "error": "Failed to update filters"}) except Exception as e: logger.error(f"Failed to update filters: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/api/log/reset") @@ -108,10 +109,10 @@ async def reset_log_config(): logger.info("Log configuration reset to defaults") return {"success": True, "message": "Configuration reset to defaults"} else: - return {"success": False, "error": "Failed to reset configuration"} + return JSONResponse(status_code=500, content={"success": False, "error": "Failed to reset configuration"}) except Exception as e: logger.error(f"Failed to reset log config: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/api/log/global-level") @@ -125,10 +126,10 @@ async def update_global_level_endpoint(level: str, enabled: bool): logger.info(f"Global level {level} {action} across all components") return {"success": True, "message": f"Level {level} {action} globally"} else: - return {"success": False, "error": f"Failed to update global level {level}"} + return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to update global level {level}"}) except Exception as e: logger.error(f"Failed to update global level: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.post("/api/log/timestamp-format") @@ -140,10 +141,10 @@ async def update_timestamp_format_endpoint(format_type: str): logger.info(f"Timestamp format updated to: {format_type}") return {"success": True, "message": f"Timestamp format set to: {format_type}"} else: - return {"success": False, "error": f"Invalid timestamp format: {format_type}"} + return JSONResponse(status_code=400, content={"success": False, "error": f"Invalid timestamp format: {format_type}"}) except Exception as e: logger.error(f"Failed to update timestamp format: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) @router.get("/api/log/files/{component}") @@ -155,7 +156,7 @@ async def get_log_file(component: str, lines: int = 100): log_file = log_dir / f'{component.replace(".", "_")}.log' if not log_file.exists(): - return {"success": False, "error": "Log file not found"} + return JSONResponse(status_code=404, content={"success": False, "error": "Log file not found"}) with open(log_file, 'r', encoding='utf-8') as f: all_lines = f.readlines() @@ -170,4 +171,4 @@ async def get_log_file(component: str, lines: int = 100): } except Exception as e: logger.error(f"Failed to read log file for {component}: {e}") - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) diff --git a/bot/routes/manual_send.py b/bot/routes/manual_send.py index 2cfc0d9..41504e9 100644 --- a/bot/routes/manual_send.py +++ b/bot/routes/manual_send.py @@ -3,6 +3,7 @@ import io from typing import List from fastapi import APIRouter, UploadFile, File, Form +from fastapi.responses import JSONResponse import discord import globals from utils.logger import get_logger @@ -23,7 +24,7 @@ async def manual_send( try: channel = globals.client.get_channel(int(channel_id)) if not channel: - return {"status": "error", "message": "Channel not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Channel not found"}) # Read file content immediately before the request closes file_data = [] @@ -36,7 +37,7 @@ async def manual_send( }) except Exception as e: logger.error(f"Failed to read file {file.filename}: {e}") - return {"status": "error", "message": f"Failed to read file {file.filename}: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to read file {file.filename}: {e}"}) async def send_message_and_files(): try: @@ -70,7 +71,7 @@ async def manual_send( return {"status": "ok", "message": "Message and files queued for sending"} except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.post("/manual/send-webhook") @@ -88,10 +89,10 @@ async def manual_send_webhook( channel = globals.client.get_channel(int(channel_id)) if not channel: - return {"status": "error", "message": "Channel not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Channel not found"}) if persona not in ["miku", "evil"]: - return {"status": "error", "message": "Invalid persona. Must be 'miku' or 'evil'"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid persona. Must be 'miku' or 'evil'"}) file_data = [] for file in files: @@ -103,7 +104,7 @@ async def manual_send_webhook( }) except Exception as e: logger.error(f"Failed to read file {file.filename}: {e}") - return {"status": "error", "message": f"Failed to read file {file.filename}: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to read file {file.filename}: {e}"}) async def send_webhook_message(): try: @@ -146,7 +147,7 @@ async def manual_send_webhook( return {"status": "ok", "message": f"Webhook message queued for sending as {persona}"} except Exception as e: - return {"status": "error", "message": f"Error: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"}) @router.post("/messages/react") @@ -158,17 +159,17 @@ async def add_reaction_to_message( """Add a reaction to a specific message""" try: if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: msg_id = int(message_id) chan_id = int(channel_id) except ValueError: - return {"status": "error", "message": "Invalid message ID or channel ID format"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid message ID or channel ID format"}) channel = globals.client.get_channel(chan_id) if not channel: - return {"status": "error", "message": f"Channel {channel_id} not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"Channel {channel_id} not found"}) async def add_reaction_task(): try: @@ -193,4 +194,4 @@ async def add_reaction_to_message( except Exception as e: logger.error(f"Failed to add reaction: {e}") - return {"status": "error", "message": f"Failed to add reaction: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to add reaction: {e}"}) diff --git a/bot/routes/memory.py b/bot/routes/memory.py index f036c61..45d95f3 100644 --- a/bot/routes/memory.py +++ b/bot/routes/memory.py @@ -2,6 +2,7 @@ from typing import Optional from fastapi import APIRouter, Form +from fastapi.responses import JSONResponse import globals from routes.models import MemoryDeleteRequest, MemoryEditRequest, MemoryCreateRequest from utils.logger import get_logger @@ -51,7 +52,7 @@ async def get_memory_stats(): from utils.cat_client import cat_adapter stats = await cat_adapter.get_memory_stats() if stats is None: - return {"success": False, "error": "Could not reach Cheshire Cat"} + return JSONResponse(status_code=502, content={"success": False, "error": "Could not reach Cheshire Cat"}) return {"success": True, "collections": stats.get("collections", [])} @@ -69,7 +70,7 @@ async def get_episodic_memories(): from utils.cat_client import cat_adapter result = await cat_adapter.get_memory_points(collection="episodic", limit=100) if result is None: - return {"success": False, "error": "Could not reach Cheshire Cat"} + return JSONResponse(status_code=502, content={"success": False, "error": "Could not reach Cheshire Cat"}) memories = [] for point in result.get("points", []): @@ -90,7 +91,7 @@ async def trigger_memory_consolidation(): logger.info("🌙 Manual memory consolidation triggered via API") result = await cat_adapter.trigger_consolidation() if result is None: - return {"success": False, "error": "Consolidation failed or timed out"} + return JSONResponse(status_code=500, content={"success": False, "error": "Consolidation failed or timed out"}) return {"success": True, "result": result} @@ -108,11 +109,11 @@ async def delete_all_memories(request: MemoryDeleteRequest): if request.confirmation != REQUIRED_CONFIRMATION: logger.warning(f"Memory deletion rejected: wrong confirmation string") - return { + return JSONResponse(status_code=400, content={ "success": False, "error": "Confirmation string does not match. " f"Expected exactly: \"{REQUIRED_CONFIRMATION}\"" - } + }) from utils.cat_client import cat_adapter logger.warning("⚠️ MEMORY DELETION CONFIRMED — wiping all memories!") @@ -132,10 +133,10 @@ async def delete_all_memories(request: MemoryDeleteRequest): "conversation_history_cleared": history_success } else: - return { + return JSONResponse(status_code=500, content={ "success": False, "error": "Failed to wipe memory collections. Check Cat connection." - } + }) @router.delete("/memory/point/{collection}/{point_id}") @@ -146,7 +147,7 @@ async def delete_single_memory_point(collection: str, point_id: str): if success: return {"success": True, "deleted": point_id} else: - return {"success": False, "error": f"Failed to delete point {point_id}"} + return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to delete point {point_id}"}) @router.put("/memory/point/{collection}/{point_id}") @@ -162,7 +163,7 @@ async def edit_memory_point(collection: str, point_id: str, request: MemoryEditR if success: return {"success": True, "updated": point_id} else: - return {"success": False, "error": f"Failed to update point {point_id}"} + return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to update point {point_id}"}) @router.post("/memory/create") @@ -176,7 +177,7 @@ async def create_memory_point(request: MemoryCreateRequest): from utils.cat_client import cat_adapter if request.collection not in ['declarative', 'episodic']: - return {"success": False, "error": "Collection must be 'declarative' or 'episodic'"} + return JSONResponse(status_code=400, content={"success": False, "error": "Collection must be 'declarative' or 'episodic'"}) # Create the memory point result = await cat_adapter.create_memory_point( @@ -190,4 +191,4 @@ async def create_memory_point(request: MemoryCreateRequest): if result: return {"success": True, "point_id": result, "collection": request.collection} else: - return {"success": False, "error": "Failed to create memory point"} + return JSONResponse(status_code=500, content={"success": False, "error": "Failed to create memory point"}) diff --git a/bot/routes/mood.py b/bot/routes/mood.py index 2011970..3a147c7 100644 --- a/bot/routes/mood.py +++ b/bot/routes/mood.py @@ -1,6 +1,7 @@ """Mood management routes: DM mood, per-server mood, available moods, test mood.""" from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from server_manager import server_manager from routes.models import MoodSetRequest @@ -23,7 +24,7 @@ async def set_mood_endpoint(data: MoodSetRequest): # This endpoint now operates on DM_MOOD from utils.moods import MOOD_EMOJIS if data.mood not in MOOD_EMOJIS: - return {"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"}) # Update DM mood (DMs don't have nicknames, so no nickname update needed) globals.DM_MOOD = data.mood @@ -94,13 +95,13 @@ async def set_server_mood_endpoint(guild_id: int, data: MoodSetRequest): # Check if server exists if guild_id not in server_manager.servers: logger.warning(f"Server {guild_id} not found in server_manager.servers") - return {"status": "error", "message": "Server not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Server not found"}) # Check if mood is valid from utils.moods import MOOD_EMOJIS if data.mood not in MOOD_EMOJIS: logger.warning(f"Mood '{data.mood}' not found in MOOD_EMOJIS. Available moods: {list(MOOD_EMOJIS.keys())}") - return {"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"}) success = server_manager.set_server_mood(guild_id, data.mood) logger.debug(f"Server mood set result: {success}") @@ -113,7 +114,7 @@ async def set_server_mood_endpoint(guild_id: int, data: MoodSetRequest): return {"status": "ok", "new_mood": data.mood, "guild_id": guild_id} logger.warning(f"set_server_mood returned False for unknown reason") - return {"status": "error", "message": "Failed to set server mood"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to set server mood"}) @router.post("/servers/{guild_id}/mood/reset") @@ -124,7 +125,7 @@ async def reset_server_mood_endpoint(guild_id: int): # Check if server exists if guild_id not in server_manager.servers: logger.warning(f"Server {guild_id} not found in server_manager.servers") - return {"status": "error", "message": "Server not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Server not found"}) logger.debug(f"Server validation passed, calling set_server_mood") success = server_manager.set_server_mood(guild_id, "neutral") @@ -138,7 +139,7 @@ async def reset_server_mood_endpoint(guild_id: int): return {"status": "ok", "new_mood": "neutral", "guild_id": guild_id} logger.warning(f"set_server_mood returned False for unknown reason") - return {"status": "error", "message": "Failed to reset server mood"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to reset server mood"}) @router.get("/servers/{guild_id}/mood/state") @@ -147,7 +148,7 @@ def get_server_mood_state(guild_id: int): mood_state = server_manager.get_server_mood_state(guild_id) if mood_state: return {"status": "ok", "guild_id": guild_id, "mood_state": mood_state} - return {"status": "error", "message": "Server not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Server not found"}) # ========== Misc Mood ========== @@ -166,7 +167,7 @@ async def test_mood_change(guild_id: int, data: MoodSetRequest): # Check if server exists if guild_id not in server_manager.servers: - return {"status": "error", "message": f"Server {guild_id} not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"Server {guild_id} not found"}) server_config = server_manager.get_server_config(guild_id) logger.debug(f"TEST: Server config found: {server_config.guild_name if server_config else 'None'}") @@ -189,4 +190,4 @@ async def test_mood_change(guild_id: int, data: MoodSetRequest): return {"status": "ok", "message": f"Test mood change completed", "success": success} - return {"status": "error", "message": "Mood change failed"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Mood change failed"}) diff --git a/bot/routes/profile_picture.py b/bot/routes/profile_picture.py index ed57101..f4b99fd 100644 --- a/bot/routes/profile_picture.py +++ b/bot/routes/profile_picture.py @@ -3,7 +3,7 @@ import os from typing import List from fastapi import APIRouter, UploadFile, File, Form -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, JSONResponse import globals from routes.models import ( ManualCropRequest, DescriptionUpdateRequest, @@ -25,7 +25,7 @@ async def trigger_profile_picture_change( ): """Change Miku's profile picture. If a file is provided, use it. Otherwise, search Danbooru.""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -54,17 +54,17 @@ async def trigger_profile_picture_change( "metadata": result.get("metadata", {}) } else: - return { + return JSONResponse(status_code=500, content={ "status": "error", "message": result.get("error", "Unknown error"), "source": result["source"] - } + }) except Exception as e: logger.error(f"Error in profile picture API: {e}") import traceback traceback.print_exc() - return {"status": "error", "message": f"Unexpected error: {str(e)}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Unexpected error: {str(e)}"}) @router.get("/profile-picture/metadata") @@ -78,14 +78,14 @@ async def get_profile_picture_metadata(): else: return {"status": "ok", "metadata": None, "message": "No metadata found"} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/restore-fallback") async def restore_fallback_profile_picture(): """Restore the original fallback profile picture""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -93,16 +93,16 @@ async def restore_fallback_profile_picture(): if success: return {"status": "ok", "message": "Fallback profile picture restored"} else: - return {"status": "error", "message": "Failed to restore fallback"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to restore fallback"}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/role-color/custom") async def set_custom_role_color(hex_color: str = Form(...)): """Set a custom role color across all servers""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -114,16 +114,16 @@ async def set_custom_role_color(hex_color: str = Form(...)): "color": result["color"] } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/role-color/reset-fallback") async def reset_role_color_to_fallback(): """Reset role color to fallback (#86cecb)""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -135,9 +135,9 @@ async def reset_role_color_to_fallback(): "color": result["color"] } else: - return {"status": "error", "message": "Failed to reset color"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to reset color"}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) # ========== Profile Picture — Image Serving ========== @@ -148,7 +148,7 @@ async def serve_original_profile_picture(): from utils.profile_picture_manager import profile_picture_manager path = profile_picture_manager.ORIGINAL_PATH if not os.path.exists(path): - return {"status": "error", "message": "No original image found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "No original image found"}) return FileResponse(path, media_type="image/png", headers={"Cache-Control": "no-cache"}) @@ -158,7 +158,7 @@ async def serve_current_profile_picture(): from utils.profile_picture_manager import profile_picture_manager path = profile_picture_manager.CURRENT_PATH if not os.path.exists(path): - return {"status": "error", "message": "No current image found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "No current image found"}) return FileResponse(path, media_type="image/png", headers={"Cache-Control": "no-cache"}) @@ -171,7 +171,7 @@ async def trigger_profile_picture_change_no_crop( ): """Change Miku's profile picture but skip auto-cropping.""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -200,23 +200,23 @@ async def trigger_profile_picture_change_no_crop( "metadata": result.get("metadata", {}) } else: - return { + return JSONResponse(status_code=500, content={ "status": "error", "message": result.get("error", "Unknown error"), "source": result.get("source") - } + }) except Exception as e: logger.error(f"Error in change-no-crop API: {e}") import traceback traceback.print_exc() - return {"status": "error", "message": f"Unexpected error: {str(e)}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Unexpected error: {str(e)}"}) @router.post("/profile-picture/manual-crop") async def apply_manual_crop(req: ManualCropRequest): """Apply a manual crop to the stored original image""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -230,16 +230,16 @@ async def apply_manual_crop(req: ManualCropRequest): "metadata": result.get("metadata", {}) } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/auto-crop") async def apply_auto_crop(): """Run intelligent auto-crop on the stored original image""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager @@ -251,9 +251,9 @@ async def apply_auto_crop(): "metadata": result.get("metadata", {}) } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/description") @@ -267,9 +267,9 @@ async def update_profile_picture_description(req: DescriptionUpdateRequest): if result["success"]: return {"status": "ok", "message": "Description updated successfully"} else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/regenerate-description") @@ -285,9 +285,9 @@ async def regenerate_profile_picture_description(): "description": result["description"] } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.get("/profile-picture/description") @@ -298,7 +298,7 @@ async def get_profile_picture_description(): description = profile_picture_manager.get_current_description() return {"status": "ok", "description": description or ""} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) # ========== Profile Picture — Album / Gallery ========== @@ -311,7 +311,7 @@ async def list_album_entries(): entries = profile_picture_manager.get_album_entries() return {"status": "ok", "entries": entries, "count": len(entries)} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.get("/profile-picture/album/disk-usage") @@ -322,7 +322,7 @@ async def get_album_disk_usage(): usage = profile_picture_manager.get_album_disk_usage() return {"status": "ok", **usage} except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.get("/profile-picture/album/{entry_id}") @@ -334,25 +334,25 @@ async def get_album_entry(entry_id: str): if meta: return {"status": "ok", "entry": meta} else: - return {"status": "error", "message": "Album entry not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Album entry not found"}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.get("/profile-picture/album/{entry_id}/image/{image_type}") async def serve_album_image(entry_id: str, image_type: str): """Serve an album entry's image (original or cropped)""" if image_type not in ("original", "cropped"): - return {"status": "error", "message": "image_type must be 'original' or 'cropped'"} + return JSONResponse(status_code=400, content={"status": "error", "message": "image_type must be 'original' or 'cropped'"}) try: from utils.profile_picture_manager import profile_picture_manager path = profile_picture_manager.get_album_image_path(entry_id, image_type) if path: return FileResponse(path, media_type="image/png", headers={"Cache-Control": "no-cache"}) else: - return {"status": "error", "message": f"No {image_type} image for this entry"} + return JSONResponse(status_code=404, content={"status": "error", "message": f"No {image_type} image for this entry"}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/add") @@ -373,10 +373,10 @@ async def add_to_album(file: UploadFile = File(...)): "metadata": result.get("metadata", {}) } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: logger.error(f"Error adding to album: {e}") - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/add-batch") @@ -403,14 +403,14 @@ async def add_batch_to_album(files: List[UploadFile] = File(...)): } except Exception as e: logger.error(f"Error in batch album add: {e}") - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/{entry_id}/set-current") async def set_album_entry_as_current(entry_id: str): """Set an album entry as the current Discord profile picture""" if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"status": "error", "message": "Bot not ready"} + return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"}) try: from utils.profile_picture_manager import profile_picture_manager result = await profile_picture_manager.set_album_entry_as_current( @@ -423,9 +423,9 @@ async def set_album_entry_as_current(entry_id: str): "archived_entry_id": result.get("archived_entry_id") } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/{entry_id}/manual-crop") @@ -444,9 +444,9 @@ async def manual_crop_album_entry(entry_id: str, req: AlbumCropRequest): "metadata": result.get("metadata", {}) } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/{entry_id}/auto-crop") @@ -464,9 +464,9 @@ async def auto_crop_album_entry(entry_id: str): "metadata": result.get("metadata", {}) } else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/{entry_id}/description") @@ -480,9 +480,9 @@ async def update_album_entry_description(entry_id: str, req: AlbumDescriptionReq if result["success"]: return {"status": "ok", "message": "Description updated"} else: - return {"status": "error", "message": result.get("error", "Unknown error")} + return JSONResponse(status_code=500, content={"status": "error", "message": result.get("error", "Unknown error")}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.delete("/profile-picture/album/{entry_id}") @@ -493,9 +493,9 @@ async def delete_album_entry(entry_id: str): if profile_picture_manager.delete_album_entry(entry_id): return {"status": "ok", "message": "Album entry deleted"} else: - return {"status": "error", "message": "Album entry not found"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Album entry not found"}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/delete-bulk") @@ -510,7 +510,7 @@ async def bulk_delete_album_entries(req: BulkDeleteRequest): **result } except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @router.post("/profile-picture/album/add-current") @@ -522,6 +522,6 @@ async def add_current_to_album(): if entry_id: return {"status": "ok", "message": "Current PFP archived to album", "entry_id": entry_id} else: - return {"status": "error", "message": "No current PFP to archive"} + return JSONResponse(status_code=404, content={"status": "error", "message": "No current PFP to archive"}) except Exception as e: - return {"status": "error", "message": str(e)} + return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) diff --git a/bot/routes/servers.py b/bot/routes/servers.py index 5c2b3ba..b0daf0a 100644 --- a/bot/routes/servers.py +++ b/bot/routes/servers.py @@ -3,6 +3,7 @@ import os import json from fastapi import APIRouter +from fastapi.responses import JSONResponse import globals from server_manager import server_manager from routes.models import ServerConfigRequest @@ -63,7 +64,7 @@ def add_server(data: ServerConfigRequest): server_manager.start_all_schedulers(globals.client) return {"status": "ok", "message": f"Server {data.guild_name} added successfully"} else: - return {"status": "error", "message": "Failed to add server"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to add server"}) @router.delete("/servers/{guild_id}") @@ -73,7 +74,7 @@ def remove_server(guild_id: int): if success: return {"status": "ok", "message": "Server removed successfully"} else: - return {"status": "error", "message": "Failed to remove server"} + return JSONResponse(status_code=404, content={"status": "error", "message": "Failed to remove server"}) @router.put("/servers/{guild_id}") @@ -85,7 +86,7 @@ def update_server(guild_id: int, data: dict): server_manager.start_all_schedulers(globals.client) return {"status": "ok", "message": "Server configuration updated"} else: - return {"status": "error", "message": "Failed to update server configuration"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to update server configuration"}) @router.post("/servers/{guild_id}/bedtime-range") @@ -96,7 +97,7 @@ def update_server_bedtime_range(guild_id: int, data: dict): required_fields = ['bedtime_hour', 'bedtime_minute', 'bedtime_hour_end', 'bedtime_minute_end'] for field in required_fields: if field not in data: - return {"status": "error", "message": f"Missing required field: {field}"} + return JSONResponse(status_code=400, content={"status": "error", "message": f"Missing required field: {field}"}) try: bedtime_hour = int(data['bedtime_hour']) @@ -105,12 +106,12 @@ def update_server_bedtime_range(guild_id: int, data: dict): bedtime_minute_end = int(data['bedtime_minute_end']) if not (0 <= bedtime_hour <= 23) or not (0 <= bedtime_hour_end <= 23): - return {"status": "error", "message": "Hours must be between 0 and 23"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Hours must be between 0 and 23"}) if not (0 <= bedtime_minute <= 59) or not (0 <= bedtime_minute_end <= 59): - return {"status": "error", "message": "Minutes must be between 0 and 59"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Minutes must be between 0 and 59"}) except (ValueError, TypeError): - return {"status": "error", "message": "Invalid time values provided"} + return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid time values provided"}) success = server_manager.update_server_config(guild_id, **data) if success: @@ -122,9 +123,9 @@ def update_server_bedtime_range(guild_id: int, data: dict): "message": f"Bedtime range updated: {bedtime_hour:02d}:{bedtime_minute:02d} - {bedtime_hour_end:02d}:{bedtime_minute_end:02d}" } else: - return {"status": "error", "message": "Updated config but failed to update scheduler"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Updated config but failed to update scheduler"}) else: - return {"status": "error", "message": "Failed to update bedtime range"} + return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to update bedtime range"}) @router.post("/servers/repair") @@ -134,4 +135,4 @@ def repair_server_config(): server_manager.repair_config() return {"status": "ok", "message": "Server configuration repaired and saved"} except Exception as e: - return {"status": "error", "message": f"Failed to repair configuration: {e}"} + return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to repair configuration: {e}"}) diff --git a/bot/routes/voice.py b/bot/routes/voice.py index 733d29a..263588e 100644 --- a/bot/routes/voice.py +++ b/bot/routes/voice.py @@ -2,6 +2,7 @@ import asyncio from fastapi import APIRouter, Form +from fastapi.responses import JSONResponse import discord import globals from utils.dm_logger import dm_logger @@ -29,7 +30,7 @@ async def initiate_voice_call(user_id: str = Form(...), voice_channel_id: str = # Check if bot is running if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): - return {"success": False, "error": "Bot is not running"} + return JSONResponse(status_code=503, content={"success": False, "error": "Bot is not running"}) # Run the voice call setup in the bot's event loop try: @@ -41,7 +42,7 @@ async def initiate_voice_call(user_id: str = Form(...), voice_channel_id: str = return result except Exception as e: logger.error(f"Error initiating voice call: {e}", exc_info=True) - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str): @@ -57,11 +58,11 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str): # Get user and channel user = await globals.client.fetch_user(user_id_int) if not user: - return {"success": False, "error": "User not found"} + return JSONResponse(status_code=404, content={"success": False, "error": "User not found"}) channel = globals.client.get_channel(channel_id_int) if not channel or not isinstance(channel, discord.VoiceChannel): - return {"success": False, "error": "Voice channel not found"} + return JSONResponse(status_code=404, content={"success": False, "error": "Voice channel not found"}) # Get a text channel for voice operations (use first text channel in guild) text_channel = None @@ -71,14 +72,14 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str): break if not text_channel: - return {"success": False, "error": "No accessible text channel found"} + return JSONResponse(status_code=404, content={"success": False, "error": "No accessible text channel found"}) # Start containers logger.info("Starting voice containers...") containers_started = await ContainerManager.start_voice_containers() if not containers_started: - return {"success": False, "error": "Failed to start voice containers"} + return JSONResponse(status_code=500, content={"success": False, "error": "Failed to start voice containers"}) # Start voice session logger.info(f"Starting voice session in {channel.name}") @@ -88,7 +89,7 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str): await session_manager.start_session(channel.guild.id, channel, text_channel) except Exception as e: await ContainerManager.stop_voice_containers() - return {"success": False, "error": f"Failed to start voice session: {str(e)}"} + return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to start voice session: {str(e)}"}) # Set up voice call tracking (use integer ID) session_manager.active_session.call_user_id = user_id_int @@ -143,7 +144,7 @@ Keep it brief (1-2 sentences). Make it feel personal and enthusiastic!""" except Exception as e: logger.error(f"Error in voice call implementation: {e}", exc_info=True) - return {"success": False, "error": str(e)} + return JSONResponse(status_code=500, content={"success": False, "error": str(e)}) async def _voice_call_timeout_handler(voice_session, user: discord.User, channel: discord.VoiceChannel): @@ -171,7 +172,7 @@ async def _voice_call_timeout_handler(voice_session, user: discord.User, channel # Log to DM logger dm_logger.log_user_message(user, sent_message, is_bot_message=True) - except: + except Exception: pass except asyncio.CancelledError: