feat: add proper HTTP status codes to all API error responses

- 217 error returns across 18 route files + api.py now use JSONResponse
  with appropriate HTTP status codes instead of returning HTTP 200
- Status code distribution: 500 (121), 400 (39), 503 (28), 404 (24), 409 (3), 502 (2)
- Fixed language.py tuple-return bug (was serializing as JSON array)
- Fixed bare except clauses in bipolar_mode.py and voice.py
- Body-level error schemas preserved (status/error + success/error patterns)
  so web UI continues working without changes
- chat.py (SSE) unchanged: errors sent within stream protocol
- All 170 tests pass
This commit is contained in:
2026-04-15 15:43:18 +03:00
parent 33b2033cc3
commit edc9f27925
19 changed files with 243 additions and 227 deletions

View File

@@ -4,6 +4,7 @@
# To revert: cp api_monolith_backup.py api.py # To revert: cp api_monolith_backup.py api.py
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from utils.logger import get_logger from utils.logger import get_logger
from utils.log_config import load_config as load_log_config 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): async def global_exception_handler(request: Request, exc: Exception):
"""Catch all unhandled exceptions and log them properly.""" """Catch all unhandled exceptions and log them properly."""
logger.error(f"Unhandled exception on {request.method} {request.url.path}: {exc}", exc_info=True) 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 ========== # ========== Logging Middleware ==========
@app.middleware("http") @app.middleware("http")

View File

@@ -1,6 +1,7 @@
"""Autonomous action routes: V1, V2, per-server autonomous.""" """Autonomous action routes: V1, V2, per-server autonomous."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from server_manager import server_manager from server_manager import server_manager
from routes.models import CustomPromptRequest 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()) globals.client.loop.create_task(miku_say_something_general())
return {"status": "ok", "message": "Autonomous general message queued for all servers"} return {"status": "ok", "message": "Autonomous general message queued for all servers"}
else: else:
return {"status": "error", "message": "Bot not ready"} return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/engage") @router.post("/autonomous/engage")
@@ -65,7 +66,7 @@ async def trigger_autonomous_engage_user(
return {"status": "ok", "message": " ".join(msg_parts)} return {"status": "ok", "message": " ".join(msg_parts)}
else: else:
return {"status": "error", "message": "Bot not ready"} return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/tweet") @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}" msg += f" with URL {tweet_url}"
return {"status": "ok", "message": msg} return {"status": "ok", "message": msg}
else: else:
return {"status": "error", "message": "Bot not ready"} return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/custom") @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)) globals.client.loop.create_task(handle_custom_prompt(req.prompt))
return {"status": "ok", "message": "Custom autonomous message queued for all servers"} return {"status": "ok", "message": "Custom autonomous message queued for all servers"}
else: else:
return {"status": "error", "message": "Bot not ready"} return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/reaction") @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)) globals.client.loop.create_task(miku_autonomous_reaction(force=True))
return {"status": "ok", "message": "Autonomous reaction queued for all servers"} return {"status": "ok", "message": "Autonomous reaction queued for all servers"}
else: 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") @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"} return {"status": "ok", "message": "Detect and join conversation queued for all servers"}
else: else:
logger.error(f"Bot not ready: client={globals.client}, loop={globals.client.loop if globals.client else None}") 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 ========== # ========== 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) await miku_say_something_general_for_server(guild_id)
return {"status": "ok", "message": f"Autonomous general message triggered for server {guild_id}"} return {"status": "ok", "message": f"Autonomous general message triggered for server {guild_id}"}
except Exception as e: 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") @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)} return {"status": "ok", "message": " ".join(msg_parts)}
except Exception as e: 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") @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: if success:
return {"status": "ok", "message": f"Custom autonomous message sent to server {guild_id}"} return {"status": "ok", "message": f"Custom autonomous message sent to server {guild_id}"}
else: 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: 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") @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) await share_miku_tweet_for_server(guild_id)
return {"status": "ok", "message": f"Autonomous tweet sharing triggered for server {guild_id}"} return {"status": "ok", "message": f"Autonomous tweet sharing triggered for server {guild_id}"}
except Exception as e: 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 ========== # ========== Autonomous V2 ==========
@@ -213,7 +214,7 @@ async def get_v2_stats(guild_id: int):
stats = get_v2_stats_for_server(guild_id) stats = get_v2_stats_for_server(guild_id)
return {"status": "ok", "guild_id": guild_id, "stats": stats} return {"status": "ok", "guild_id": guild_id, "stats": stats}
except Exception as e: 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}") @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 from utils.autonomous_v2_integration import manual_trigger_v2_check
if not globals.client: 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) result = await manual_trigger_v2_check(guild_id, globals.client)
if isinstance(result, str): 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} return {"status": "ok", "guild_id": guild_id, "analysis": result}
except Exception as e: 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") @router.get("/autonomous/v2/status")
@@ -258,4 +259,4 @@ async def get_v2_status():
return {"status": "ok", "servers": status} return {"status": "ok", "servers": status}
except Exception as e: except Exception as e:
return {"status": "error", "message": str(e)} return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})

View File

@@ -2,6 +2,7 @@
import asyncio import asyncio
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from routes.models import BipolarTriggerRequest from routes.models import BipolarTriggerRequest
from utils.logger import get_logger from utils.logger import get_logger
@@ -106,23 +107,23 @@ def trigger_argument(data: BipolarTriggerRequest):
try: try:
channel_id = int(data.channel_id) channel_id = int(data.channel_id)
except ValueError: 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 message_id = None
if data.message_id: if data.message_id:
try: try:
message_id = int(data.message_id) message_id = int(data.message_id)
except ValueError: 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(): 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): 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(): 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 is provided, use the message-based trigger
if message_id: if message_id:
@@ -145,7 +146,7 @@ def trigger_argument(data: BipolarTriggerRequest):
# Otherwise, find the channel and trigger normally # Otherwise, find the channel and trigger normally
channel = globals.client.get_channel(channel_id) channel = globals.client.get_channel(channel_id)
if not channel: 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 # Trigger the argument
globals.client.loop.create_task(force_trigger_argument(channel, globals.client, data.context)) 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") message_id_str = data.get("message_id")
if not message_id_str: 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 # Parse message ID
try: try:
message_id = int(message_id_str) message_id = int(message_id_str)
except ValueError: 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(): 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(): 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(): async def trigger_dialogue_task():
try: try:
@@ -191,7 +192,7 @@ def trigger_dialogue(data: dict):
try: try:
message = await channel.fetch_message(message_id) message = await channel.fetch_message(message_id)
break break
except: except Exception:
continue continue
if not message: if not message:
@@ -272,7 +273,7 @@ def cleanup_bipolar_webhooks():
from utils.bipolar_mode import cleanup_webhooks from utils.bipolar_mode import cleanup_webhooks
if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): 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)) globals.client.loop.create_task(cleanup_webhooks(globals.client))
return {"status": "ok", "message": "Webhook cleanup started"} return {"status": "ok", "message": "Webhook cleanup started"}

View File

@@ -1,6 +1,7 @@
"""Core bot action routes: conversation reset, sleep, wake, bedtime.""" """Core bot action routes: conversation reset, sleep, wake, bedtime."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from commands.actions import ( from commands.actions import (
force_sleep, force_sleep,
@@ -49,4 +50,4 @@ async def bedtime_endpoint(guild_id: int = None):
globals.client.loop.create_task(send_bedtime_now()) globals.client.loop.create_task(send_bedtime_now())
return {"status": "ok", "message": "Bedtime reminder queued for all servers"} return {"status": "ok", "message": "Bedtime reminder queued for all servers"}
else: else:
return {"status": "error", "message": "Bot not ready"} return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})

View File

@@ -1,6 +1,7 @@
"""Configuration management routes: get/set/reset/validate config.""" """Configuration management routes: get/set/reset/validate config."""
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
import globals import globals
from utils.logger import get_logger from utils.logger import get_logger
@@ -24,7 +25,7 @@ async def get_full_config():
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to get config: {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") @router.get("/config/static")
@@ -41,7 +42,7 @@ async def get_static_config():
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to get static config: {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") @router.get("/config/runtime")
@@ -59,7 +60,7 @@ async def get_runtime_config():
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to get runtime config: {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") @router.post("/config/set")
@@ -80,7 +81,7 @@ async def set_config_value(request: Request):
persist = data.get("persist", True) persist = data.get("persist", True)
if not key_path: 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 from config_manager import config_manager
config_manager.set(key_path, value, persist=persist) config_manager.set(key_path, value, persist=persist)
@@ -114,7 +115,7 @@ async def set_config_value(request: Request):
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to set config: {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") @router.post("/config/reset")
@@ -143,7 +144,7 @@ async def reset_config(request: Request):
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to reset config: {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") @router.post("/config/validate")
@@ -163,7 +164,7 @@ async def validate_config_endpoint():
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to validate config: {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") @router.get("/config/state")
@@ -180,4 +181,4 @@ async def get_config_state():
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to get config state: {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)})

View File

@@ -1,7 +1,7 @@
"""Core routes: index, logs, prompts, status, conversation.""" """Core routes: index, logs, prompts, status, conversation."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import FileResponse from fastapi.responses import FileResponse, JSONResponse
import globals import globals
from server_manager import server_manager from server_manager import server_manager
from utils.conversation_history import conversation_history from utils.conversation_history import conversation_history
@@ -26,7 +26,7 @@ def get_logs():
last_100 = lines[-100:] if len(lines) >= 100 else lines last_100 = lines[-100:] if len(lines) >= 100 else lines
return "".join(last_100) return "".join(last_100)
except Exception as e: 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") @router.get("/prompt")

View File

@@ -5,6 +5,7 @@ import os
import json import json
from typing import List from typing import List
from fastapi import APIRouter, UploadFile, File, Form from fastapi import APIRouter, UploadFile, File, Form
from fastapi.responses import JSONResponse
import discord import discord
import globals import globals
from routes.models import CustomPromptRequest 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_id_int = int(user_id)
user = globals.client.get_user(user_id_int) user = globals.client.get_user(user_id_int)
if not user: 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 # Use the LLM query function for DM context
from utils.llm import query_llama 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}"} return {"status": "ok", "message": f"Custom DM prompt queued for user {user_id}"}
except ValueError: 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: 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") @router.post("/dm/{user_id}/manual")
@@ -65,7 +66,7 @@ async def send_manual_message_dm(
user_id_int = int(user_id) user_id_int = int(user_id)
user = globals.client.get_user(user_id_int) user = globals.client.get_user(user_id_int)
if not user: 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 # Read file content immediately before the request closes
file_data = [] file_data = []
@@ -78,7 +79,7 @@ async def send_manual_message_dm(
}) })
except Exception as e: except Exception as e:
logger.error(f"Failed to read file {file.filename}: {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(): async def send_dm_message_and_files():
try: try:
@@ -120,9 +121,9 @@ async def send_manual_message_dm(
return {"status": "ok", "message": f"Manual DM message queued for user {user_id}"} return {"status": "ok", "message": f"Manual DM message queued for user {user_id}"}
except ValueError: 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: 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 ========== # ========== DM Logging Endpoints ==========
@@ -134,7 +135,7 @@ def get_dm_users():
users = dm_logger.get_all_dm_users() users = dm_logger.get_all_dm_users()
return {"status": "ok", "users": users} return {"status": "ok", "users": users}
except Exception as e: 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}") @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) summary = dm_logger.get_user_conversation_summary(user_id_int)
return {"status": "ok", "summary": summary} return {"status": "ok", "summary": summary}
except ValueError: 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: 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") @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} return {"status": "ok", "conversations": conversations}
except ValueError: 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: except Exception as e:
logger.error(f"Failed to get conversations for user {user_id}: {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") @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) results = dm_logger.search_user_conversations(user_id_int, query, limit)
return {"status": "ok", "results": results} return {"status": "ok", "results": results}
except ValueError: 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: 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") @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) export_path = dm_logger.export_user_conversation(user_id_int, format)
return {"status": "ok", "export_path": export_path, "format": format} return {"status": "ok", "export_path": export_path, "format": format}
except ValueError: 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: 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}") @router.delete("/dms/users/{user_id}")
@@ -225,11 +226,11 @@ def delete_dm_user_logs(user_id: str):
os.remove(log_file) os.remove(log_file)
return {"status": "ok", "message": f"Deleted DM logs for user {user_id}"} return {"status": "ok", "message": f"Deleted DM logs for user {user_id}"}
else: 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: 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: 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 ========== # ========== User Blocking & DM Management ==========
@@ -242,7 +243,7 @@ def get_blocked_users():
return {"status": "ok", "blocked_users": blocked_users} return {"status": "ok", "blocked_users": blocked_users}
except Exception as e: except Exception as e:
logger.error(f"Failed to get blocked users: {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") @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") logger.info(f"User {user_id} ({username}) blocked")
return {"status": "ok", "message": f"User {username} has been blocked"} return {"status": "ok", "message": f"User {username} has been blocked"}
else: 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: 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: except Exception as e:
logger.error(f"Failed to block user {user_id}: {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") @router.post("/dms/users/{user_id}/unblock")
@@ -281,13 +282,13 @@ def unblock_user(user_id: str):
logger.info(f"User {user_id} unblocked") logger.info(f"User {user_id} unblocked")
return {"status": "ok", "message": f"User has been unblocked"} return {"status": "ok", "message": f"User has been unblocked"}
else: 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: 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: except Exception as e:
logger.error(f"Failed to unblock user {user_id}: {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") @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)"} return {"status": "ok", "message": "Message deletion queued (will delete from both Discord and logs)"}
except ValueError: 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: except Exception as e:
logger.error(f"Failed to queue conversation deletion {conversation_id}: {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") @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)"} return {"status": "ok", "message": "Bulk deletion queued (will delete all Miku messages from Discord and clear logs)"}
except ValueError: 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: except Exception as e:
logger.error(f"Failed to queue bulk conversation deletion for user {user_id}: {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") @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}") logger.info(f"Completely deleted user {user_id}")
return {"status": "ok", "message": "User data deleted completely"} return {"status": "ok", "message": "User data deleted completely"}
else: else:
return {"status": "error", "message": "No user data found"} return JSONResponse(status_code=404, content={"status": "error", "message": "No user data found"})
except ValueError: 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: except Exception as e:
logger.error(f"Failed to completely delete user {user_id}: {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 ========== # ========== DM Interaction Analysis Endpoints ==========
@@ -366,7 +367,7 @@ def run_dm_analysis():
from utils.dm_interaction_analyzer import dm_analyzer from utils.dm_interaction_analyzer import dm_analyzer
if dm_analyzer is None: 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 # Schedule analysis in Discord's event loop
async def run_analysis(): async def run_analysis():
@@ -377,7 +378,7 @@ def run_dm_analysis():
return {"status": "ok", "message": "DM analysis started"} return {"status": "ok", "message": "DM analysis started"}
except Exception as e: except Exception as e:
logger.error(f"Failed to run DM analysis: {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") @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 from utils.dm_interaction_analyzer import dm_analyzer
if dm_analyzer is None: 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) 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} return {"status": "ok", "message": f"Analysis started for user {user_id}", "reported": True}
except ValueError: 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: except Exception as e:
logger.error(f"Failed to analyze user {user_id}: {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") @router.get("/dms/analysis/reports")
@@ -432,7 +433,7 @@ def get_analysis_reports(limit: int = 20):
return {"status": "ok", "reports": reports} return {"status": "ok", "reports": reports}
except Exception as e: except Exception as e:
logger.error(f"Failed to get reports: {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}") @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} return {"status": "ok", "reports": reports}
except ValueError: 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: except Exception as e:
logger.error(f"Failed to get user reports: {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}"})

View File

@@ -1,6 +1,7 @@
"""Evil mode routes.""" """Evil mode routes."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from routes.models import EvilMoodSetRequest from routes.models import EvilMoodSetRequest
from utils.logger import get_logger 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)) globals.client.loop.create_task(apply_evil_mode_changes(globals.client))
return {"status": "ok", "message": "Evil mode enabled", "evil_mode": True} return {"status": "ok", "message": "Evil mode enabled", "evil_mode": True}
else: 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") @router.post("/evil-mode/disable")
@@ -58,7 +59,7 @@ def disable_evil_mode():
globals.client.loop.create_task(revert_evil_mode_changes(globals.client)) globals.client.loop.create_task(revert_evil_mode_changes(globals.client))
return {"status": "ok", "message": "Evil mode disabled", "evil_mode": False} return {"status": "ok", "message": "Evil mode disabled", "evil_mode": False}
else: 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") @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 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(): 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: if globals.EVIL_MODE:
globals.client.loop.create_task(revert_evil_mode_changes(globals.client)) 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 from utils.evil_mode import set_evil_mood, is_valid_evil_mood, update_all_evil_nicknames
if not is_valid_evil_mood(data.mood): if not is_valid_evil_mood(data.mood):
return { return JSONResponse(status_code=400, content={
"status": "error", "status": "error",
"message": f"Mood '{data.mood}' not recognized. Available evil moods: {', '.join(globals.EVIL_AVAILABLE_MOODS)}" "message": f"Mood '{data.mood}' not recognized. Available evil moods: {', '.join(globals.EVIL_AVAILABLE_MOODS)}"
} })
success = set_evil_mood(data.mood) success = set_evil_mood(data.mood)
if success: if success:
@@ -107,4 +108,4 @@ def set_evil_mood_endpoint(data: EvilMoodSetRequest):
globals.client.loop.create_task(update_all_evil_nicknames(globals.client)) globals.client.loop.create_task(update_all_evil_nicknames(globals.client))
return {"status": "ok", "new_mood": data.mood} 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"})

View File

@@ -1,6 +1,7 @@
"""Figurine subscriber and send routes.""" """Figurine subscriber and send routes."""
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
from fastapi.responses import JSONResponse
import globals import globals
from utils.figurine_notifier import ( from utils.figurine_notifier import (
load_subscribers as figurine_load_subscribers, load_subscribers as figurine_load_subscribers,
@@ -29,7 +30,7 @@ async def add_figurine_subscriber(user_id: str = Form(...)):
ok = figurine_add_subscriber(uid) ok = figurine_add_subscriber(uid)
return {"status": "ok", "added": ok} return {"status": "ok", "added": ok}
except Exception as e: 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}") @router.delete("/figurines/subscribers/{user_id}")
@@ -39,7 +40,7 @@ async def delete_figurine_subscriber(user_id: str):
ok = figurine_remove_subscriber(uid) ok = figurine_remove_subscriber(uid)
return {"status": "ok", "removed": ok} return {"status": "ok", "removed": ok}
except Exception as e: 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") @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}") 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)) 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": "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") @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(): if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
logger.error("Bot not ready") 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: try:
user_id_int = int(user_id) user_id_int = int(user_id)
logger.debug(f"Parsed user_id as {user_id_int}") logger.debug(f"Parsed user_id as {user_id_int}")
except ValueError: except ValueError:
logger.error(f"Invalid user ID: '{user_id}'") 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 # Clean up tweet URL if it's empty string
if tweet_url == "": if tweet_url == "":

View File

@@ -1,6 +1,7 @@
"""GPU selection routes.""" """GPU selection routes."""
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from utils.logger import get_logger from utils.logger import get_logger
logger = get_logger('api') logger = get_logger('api')
@@ -22,7 +23,7 @@ async def select_gpu(request: Request):
gpu = data.get("gpu", "nvidia").lower() gpu = data.get("gpu", "nvidia").lower()
if gpu not in ["nvidia", "amd"]: 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: try:
from config_manager import config_manager 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") logger.info(f"GPU Selection: Switched to {gpu.upper()} GPU")
return {"status": "ok", "message": f"Switched to {gpu.upper()} GPU", "gpu": gpu} return {"status": "ok", "message": f"Switched to {gpu.upper()} GPU", "gpu": gpu}
else: 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: except Exception as e:
logger.error(f"GPU Selection Error: {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)})

View File

@@ -2,7 +2,7 @@
import os import os
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import FileResponse from fastapi.responses import FileResponse, JSONResponse
from utils.logger import get_logger from utils.logger import get_logger
logger = get_logger('api') logger = get_logger('api')
@@ -16,7 +16,7 @@ async def manual_image_generation(req: dict):
try: try:
prompt = req.get("prompt", "").strip() prompt = req.get("prompt", "").strip()
if not prompt: 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 from utils.image_generation import generate_image_with_comfyui
image_path = await generate_image_with_comfyui(prompt) image_path = await generate_image_with_comfyui(prompt)
@@ -24,10 +24,10 @@ async def manual_image_generation(req: dict):
if image_path: if image_path:
return {"status": "ok", "message": f"Image generated successfully", "image_path": image_path} return {"status": "ok", "message": f"Image generated successfully", "image_path": image_path}
else: 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: 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") @router.get("/image/status")
@@ -39,7 +39,7 @@ async def get_image_generation_status():
return {"status": "ok", **status} return {"status": "ok", **status}
except Exception as e: 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") @router.post("/image/test-detection")
@@ -48,7 +48,7 @@ async def test_image_detection(req: dict):
try: try:
message = req.get("message", "").strip() message = req.get("message", "").strip()
if not message: 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 from utils.image_generation import detect_image_request
is_image_request, extracted_prompt = await detect_image_request(message) 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: 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}") @router.get("/image/view/{filename}")
@@ -88,7 +88,7 @@ async def view_generated_image(filename: str):
if not image_path: if not image_path:
logger.warning(f"Image not found anywhere: {filename}") 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 # Determine content type based on file extension
ext = filename.lower().split('.')[-1] ext = filename.lower().split('.')[-1]
@@ -105,4 +105,4 @@ async def view_generated_image(filename: str):
except Exception as e: except Exception as e:
logger.error(f"Error serving image: {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}"})

View File

@@ -1,6 +1,7 @@
"""Language mode routes.""" """Language mode routes."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from utils.logger import get_logger from utils.logger import get_logger
@@ -53,7 +54,7 @@ def toggle_language_mode():
def set_language_mode(language: str = "english"): def set_language_mode(language: str = "english"):
"""Set language mode to either 'english' or 'japanese'""" """Set language mode to either 'english' or 'japanese'"""
if language.lower() not in ["english", "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() globals.LANGUAGE_MODE = language.lower()
model_used = globals.JAPANESE_TEXT_MODEL if language.lower() == "japanese" else globals.TEXT_MODEL model_used = globals.JAPANESE_TEXT_MODEL if language.lower() == "japanese" else globals.TEXT_MODEL

View File

@@ -1,6 +1,7 @@
"""Logging configuration routes: get/set log levels, filters, components.""" """Logging configuration routes: get/set log levels, filters, components."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
from routes.models import LogConfigUpdateRequest, LogFilterUpdateRequest from routes.models import LogConfigUpdateRequest, LogFilterUpdateRequest
from utils.logger import get_logger, list_components, get_component_stats from utils.logger import get_logger, list_components, get_component_stats
from utils.log_config import ( from utils.log_config import (
@@ -23,7 +24,7 @@ async def get_log_config():
return {"success": True, "config": config} return {"success": True, "config": config}
except Exception as e: except Exception as e:
logger.error(f"Failed to get log config: {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") @router.post("/api/log/config")
@@ -37,13 +38,13 @@ async def update_log_config(request: LogConfigUpdateRequest):
enabled_levels=request.enabled_levels enabled_levels=request.enabled_levels
) )
if not success: 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}") logger.info(f"Log config updated: component={request.component}, enabled_levels={request.enabled_levels}, enabled={request.enabled}")
return {"success": True, "message": "Configuration updated"} return {"success": True, "message": "Configuration updated"}
except Exception as e: except Exception as e:
logger.error(f"Failed to update log config: {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") @router.get("/api/log/components")
@@ -60,7 +61,7 @@ async def get_log_components():
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to get log components: {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") @router.post("/api/log/reload")
@@ -72,10 +73,10 @@ async def reload_log_config():
logger.info("Log configuration reloaded") logger.info("Log configuration reloaded")
return {"success": True, "message": "Configuration reloaded"} return {"success": True, "message": "Configuration reloaded"}
else: 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: except Exception as e:
logger.error(f"Failed to reload log config: {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") @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)}") logger.info(f"API filters updated: {request.dict(exclude_none=True)}")
return {"success": True, "message": "Filters updated"} return {"success": True, "message": "Filters updated"}
else: 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: except Exception as e:
logger.error(f"Failed to update filters: {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") @router.post("/api/log/reset")
@@ -108,10 +109,10 @@ async def reset_log_config():
logger.info("Log configuration reset to defaults") logger.info("Log configuration reset to defaults")
return {"success": True, "message": "Configuration reset to defaults"} return {"success": True, "message": "Configuration reset to defaults"}
else: 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: except Exception as e:
logger.error(f"Failed to reset log config: {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") @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") logger.info(f"Global level {level} {action} across all components")
return {"success": True, "message": f"Level {level} {action} globally"} return {"success": True, "message": f"Level {level} {action} globally"}
else: 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: except Exception as e:
logger.error(f"Failed to update global level: {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") @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}") logger.info(f"Timestamp format updated to: {format_type}")
return {"success": True, "message": f"Timestamp format set to: {format_type}"} return {"success": True, "message": f"Timestamp format set to: {format_type}"}
else: 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: except Exception as e:
logger.error(f"Failed to update timestamp format: {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}") @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' log_file = log_dir / f'{component.replace(".", "_")}.log'
if not log_file.exists(): 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: with open(log_file, 'r', encoding='utf-8') as f:
all_lines = f.readlines() all_lines = f.readlines()
@@ -170,4 +171,4 @@ async def get_log_file(component: str, lines: int = 100):
} }
except Exception as e: except Exception as e:
logger.error(f"Failed to read log file for {component}: {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)})

View File

@@ -3,6 +3,7 @@
import io import io
from typing import List from typing import List
from fastapi import APIRouter, UploadFile, File, Form from fastapi import APIRouter, UploadFile, File, Form
from fastapi.responses import JSONResponse
import discord import discord
import globals import globals
from utils.logger import get_logger from utils.logger import get_logger
@@ -23,7 +24,7 @@ async def manual_send(
try: try:
channel = globals.client.get_channel(int(channel_id)) channel = globals.client.get_channel(int(channel_id))
if not channel: 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 # Read file content immediately before the request closes
file_data = [] file_data = []
@@ -36,7 +37,7 @@ async def manual_send(
}) })
except Exception as e: except Exception as e:
logger.error(f"Failed to read file {file.filename}: {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(): async def send_message_and_files():
try: try:
@@ -70,7 +71,7 @@ async def manual_send(
return {"status": "ok", "message": "Message and files queued for sending"} return {"status": "ok", "message": "Message and files queued for sending"}
except Exception as e: 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") @router.post("/manual/send-webhook")
@@ -88,10 +89,10 @@ async def manual_send_webhook(
channel = globals.client.get_channel(int(channel_id)) channel = globals.client.get_channel(int(channel_id))
if not channel: 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"]: 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 = [] file_data = []
for file in files: for file in files:
@@ -103,7 +104,7 @@ async def manual_send_webhook(
}) })
except Exception as e: except Exception as e:
logger.error(f"Failed to read file {file.filename}: {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(): async def send_webhook_message():
try: try:
@@ -146,7 +147,7 @@ async def manual_send_webhook(
return {"status": "ok", "message": f"Webhook message queued for sending as {persona}"} return {"status": "ok", "message": f"Webhook message queued for sending as {persona}"}
except Exception as e: 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") @router.post("/messages/react")
@@ -158,17 +159,17 @@ async def add_reaction_to_message(
"""Add a reaction to a specific message""" """Add a reaction to a specific message"""
try: try:
if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): 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: try:
msg_id = int(message_id) msg_id = int(message_id)
chan_id = int(channel_id) chan_id = int(channel_id)
except ValueError: 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) channel = globals.client.get_channel(chan_id)
if not channel: 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(): async def add_reaction_task():
try: try:
@@ -193,4 +194,4 @@ async def add_reaction_to_message(
except Exception as e: except Exception as e:
logger.error(f"Failed to add reaction: {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}"})

View File

@@ -2,6 +2,7 @@
from typing import Optional from typing import Optional
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
from fastapi.responses import JSONResponse
import globals import globals
from routes.models import MemoryDeleteRequest, MemoryEditRequest, MemoryCreateRequest from routes.models import MemoryDeleteRequest, MemoryEditRequest, MemoryCreateRequest
from utils.logger import get_logger from utils.logger import get_logger
@@ -51,7 +52,7 @@ async def get_memory_stats():
from utils.cat_client import cat_adapter from utils.cat_client import cat_adapter
stats = await cat_adapter.get_memory_stats() stats = await cat_adapter.get_memory_stats()
if stats is None: 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", [])} return {"success": True, "collections": stats.get("collections", [])}
@@ -69,7 +70,7 @@ async def get_episodic_memories():
from utils.cat_client import cat_adapter from utils.cat_client import cat_adapter
result = await cat_adapter.get_memory_points(collection="episodic", limit=100) result = await cat_adapter.get_memory_points(collection="episodic", limit=100)
if result is None: 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 = [] memories = []
for point in result.get("points", []): for point in result.get("points", []):
@@ -90,7 +91,7 @@ async def trigger_memory_consolidation():
logger.info("🌙 Manual memory consolidation triggered via API") logger.info("🌙 Manual memory consolidation triggered via API")
result = await cat_adapter.trigger_consolidation() result = await cat_adapter.trigger_consolidation()
if result is None: 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} return {"success": True, "result": result}
@@ -108,11 +109,11 @@ async def delete_all_memories(request: MemoryDeleteRequest):
if request.confirmation != REQUIRED_CONFIRMATION: if request.confirmation != REQUIRED_CONFIRMATION:
logger.warning(f"Memory deletion rejected: wrong confirmation string") logger.warning(f"Memory deletion rejected: wrong confirmation string")
return { return JSONResponse(status_code=400, content={
"success": False, "success": False,
"error": "Confirmation string does not match. " "error": "Confirmation string does not match. "
f"Expected exactly: \"{REQUIRED_CONFIRMATION}\"" f"Expected exactly: \"{REQUIRED_CONFIRMATION}\""
} })
from utils.cat_client import cat_adapter from utils.cat_client import cat_adapter
logger.warning("⚠️ MEMORY DELETION CONFIRMED — wiping all memories!") logger.warning("⚠️ MEMORY DELETION CONFIRMED — wiping all memories!")
@@ -132,10 +133,10 @@ async def delete_all_memories(request: MemoryDeleteRequest):
"conversation_history_cleared": history_success "conversation_history_cleared": history_success
} }
else: else:
return { return JSONResponse(status_code=500, content={
"success": False, "success": False,
"error": "Failed to wipe memory collections. Check Cat connection." "error": "Failed to wipe memory collections. Check Cat connection."
} })
@router.delete("/memory/point/{collection}/{point_id}") @router.delete("/memory/point/{collection}/{point_id}")
@@ -146,7 +147,7 @@ async def delete_single_memory_point(collection: str, point_id: str):
if success: if success:
return {"success": True, "deleted": point_id} return {"success": True, "deleted": point_id}
else: 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}") @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: if success:
return {"success": True, "updated": point_id} return {"success": True, "updated": point_id}
else: 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") @router.post("/memory/create")
@@ -176,7 +177,7 @@ async def create_memory_point(request: MemoryCreateRequest):
from utils.cat_client import cat_adapter from utils.cat_client import cat_adapter
if request.collection not in ['declarative', 'episodic']: 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 # Create the memory point
result = await cat_adapter.create_memory_point( result = await cat_adapter.create_memory_point(
@@ -190,4 +191,4 @@ async def create_memory_point(request: MemoryCreateRequest):
if result: if result:
return {"success": True, "point_id": result, "collection": request.collection} return {"success": True, "point_id": result, "collection": request.collection}
else: else:
return {"success": False, "error": "Failed to create memory point"} return JSONResponse(status_code=500, content={"success": False, "error": "Failed to create memory point"})

View File

@@ -1,6 +1,7 @@
"""Mood management routes: DM mood, per-server mood, available moods, test mood.""" """Mood management routes: DM mood, per-server mood, available moods, test mood."""
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from server_manager import server_manager from server_manager import server_manager
from routes.models import MoodSetRequest from routes.models import MoodSetRequest
@@ -23,7 +24,7 @@ async def set_mood_endpoint(data: MoodSetRequest):
# This endpoint now operates on DM_MOOD # This endpoint now operates on DM_MOOD
from utils.moods import MOOD_EMOJIS from utils.moods import MOOD_EMOJIS
if data.mood not in 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) # Update DM mood (DMs don't have nicknames, so no nickname update needed)
globals.DM_MOOD = data.mood globals.DM_MOOD = data.mood
@@ -94,13 +95,13 @@ async def set_server_mood_endpoint(guild_id: int, data: MoodSetRequest):
# Check if server exists # Check if server exists
if guild_id not in server_manager.servers: if guild_id not in server_manager.servers:
logger.warning(f"Server {guild_id} not found 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 # Check if mood is valid
from utils.moods import MOOD_EMOJIS from utils.moods import MOOD_EMOJIS
if data.mood not in 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())}") 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) success = server_manager.set_server_mood(guild_id, data.mood)
logger.debug(f"Server mood set result: {success}") 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} return {"status": "ok", "new_mood": data.mood, "guild_id": guild_id}
logger.warning(f"set_server_mood returned False for unknown reason") 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") @router.post("/servers/{guild_id}/mood/reset")
@@ -124,7 +125,7 @@ async def reset_server_mood_endpoint(guild_id: int):
# Check if server exists # Check if server exists
if guild_id not in server_manager.servers: if guild_id not in server_manager.servers:
logger.warning(f"Server {guild_id} not found 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") logger.debug(f"Server validation passed, calling set_server_mood")
success = server_manager.set_server_mood(guild_id, "neutral") 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} return {"status": "ok", "new_mood": "neutral", "guild_id": guild_id}
logger.warning(f"set_server_mood returned False for unknown reason") 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") @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) mood_state = server_manager.get_server_mood_state(guild_id)
if mood_state: if mood_state:
return {"status": "ok", "guild_id": guild_id, "mood_state": 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 ========== # ========== Misc Mood ==========
@@ -166,7 +167,7 @@ async def test_mood_change(guild_id: int, data: MoodSetRequest):
# Check if server exists # Check if server exists
if guild_id not in server_manager.servers: 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) 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'}") 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": "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"})

View File

@@ -3,7 +3,7 @@
import os import os
from typing import List from typing import List
from fastapi import APIRouter, UploadFile, File, Form from fastapi import APIRouter, UploadFile, File, Form
from fastapi.responses import FileResponse from fastapi.responses import FileResponse, JSONResponse
import globals import globals
from routes.models import ( from routes.models import (
ManualCropRequest, DescriptionUpdateRequest, 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.""" """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(): 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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
@@ -54,17 +54,17 @@ async def trigger_profile_picture_change(
"metadata": result.get("metadata", {}) "metadata": result.get("metadata", {})
} }
else: else:
return { return JSONResponse(status_code=500, content={
"status": "error", "status": "error",
"message": result.get("error", "Unknown error"), "message": result.get("error", "Unknown error"),
"source": result["source"] "source": result["source"]
} })
except Exception as e: except Exception as e:
logger.error(f"Error in profile picture API: {e}") logger.error(f"Error in profile picture API: {e}")
import traceback import traceback
traceback.print_exc() 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") @router.get("/profile-picture/metadata")
@@ -78,14 +78,14 @@ async def get_profile_picture_metadata():
else: else:
return {"status": "ok", "metadata": None, "message": "No metadata found"} return {"status": "ok", "metadata": None, "message": "No metadata found"}
except Exception as e: 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") @router.post("/profile-picture/restore-fallback")
async def restore_fallback_profile_picture(): async def restore_fallback_profile_picture():
"""Restore the original 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(): 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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
@@ -93,16 +93,16 @@ async def restore_fallback_profile_picture():
if success: if success:
return {"status": "ok", "message": "Fallback profile picture restored"} return {"status": "ok", "message": "Fallback profile picture restored"}
else: 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: 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") @router.post("/role-color/custom")
async def set_custom_role_color(hex_color: str = Form(...)): async def set_custom_role_color(hex_color: str = Form(...)):
"""Set a custom role color across all servers""" """Set a custom role color across all servers"""
if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): 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: try:
from utils.profile_picture_manager import profile_picture_manager 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"] "color": result["color"]
} }
else: 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: 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") @router.post("/role-color/reset-fallback")
async def reset_role_color_to_fallback(): async def reset_role_color_to_fallback():
"""Reset role color to fallback (#86cecb)""" """Reset role color to fallback (#86cecb)"""
if not globals.client or not globals.client.loop or not globals.client.loop.is_running(): 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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
@@ -135,9 +135,9 @@ async def reset_role_color_to_fallback():
"color": result["color"] "color": result["color"]
} }
else: 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: 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 ========== # ========== Profile Picture — Image Serving ==========
@@ -148,7 +148,7 @@ async def serve_original_profile_picture():
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
path = profile_picture_manager.ORIGINAL_PATH path = profile_picture_manager.ORIGINAL_PATH
if not os.path.exists(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"}) 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 from utils.profile_picture_manager import profile_picture_manager
path = profile_picture_manager.CURRENT_PATH path = profile_picture_manager.CURRENT_PATH
if not os.path.exists(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"}) 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.""" """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(): 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: try:
from utils.profile_picture_manager import profile_picture_manager 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", {}) "metadata": result.get("metadata", {})
} }
else: else:
return { return JSONResponse(status_code=500, content={
"status": "error", "status": "error",
"message": result.get("error", "Unknown error"), "message": result.get("error", "Unknown error"),
"source": result.get("source") "source": result.get("source")
} })
except Exception as e: except Exception as e:
logger.error(f"Error in change-no-crop API: {e}") logger.error(f"Error in change-no-crop API: {e}")
import traceback import traceback
traceback.print_exc() 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") @router.post("/profile-picture/manual-crop")
async def apply_manual_crop(req: ManualCropRequest): async def apply_manual_crop(req: ManualCropRequest):
"""Apply a manual crop to the stored original image""" """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(): 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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
@@ -230,16 +230,16 @@ async def apply_manual_crop(req: ManualCropRequest):
"metadata": result.get("metadata", {}) "metadata": result.get("metadata", {})
} }
else: 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: 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") @router.post("/profile-picture/auto-crop")
async def apply_auto_crop(): async def apply_auto_crop():
"""Run intelligent auto-crop on the stored original image""" """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(): 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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
@@ -251,9 +251,9 @@ async def apply_auto_crop():
"metadata": result.get("metadata", {}) "metadata": result.get("metadata", {})
} }
else: 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: 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") @router.post("/profile-picture/description")
@@ -267,9 +267,9 @@ async def update_profile_picture_description(req: DescriptionUpdateRequest):
if result["success"]: if result["success"]:
return {"status": "ok", "message": "Description updated successfully"} return {"status": "ok", "message": "Description updated successfully"}
else: 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: 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") @router.post("/profile-picture/regenerate-description")
@@ -285,9 +285,9 @@ async def regenerate_profile_picture_description():
"description": result["description"] "description": result["description"]
} }
else: 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: 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") @router.get("/profile-picture/description")
@@ -298,7 +298,7 @@ async def get_profile_picture_description():
description = profile_picture_manager.get_current_description() description = profile_picture_manager.get_current_description()
return {"status": "ok", "description": description or ""} return {"status": "ok", "description": description or ""}
except Exception as e: 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 ========== # ========== Profile Picture — Album / Gallery ==========
@@ -311,7 +311,7 @@ async def list_album_entries():
entries = profile_picture_manager.get_album_entries() entries = profile_picture_manager.get_album_entries()
return {"status": "ok", "entries": entries, "count": len(entries)} return {"status": "ok", "entries": entries, "count": len(entries)}
except Exception as e: 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") @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() usage = profile_picture_manager.get_album_disk_usage()
return {"status": "ok", **usage} return {"status": "ok", **usage}
except Exception as e: 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}") @router.get("/profile-picture/album/{entry_id}")
@@ -334,25 +334,25 @@ async def get_album_entry(entry_id: str):
if meta: if meta:
return {"status": "ok", "entry": meta} return {"status": "ok", "entry": meta}
else: 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: 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}") @router.get("/profile-picture/album/{entry_id}/image/{image_type}")
async def serve_album_image(entry_id: str, image_type: str): async def serve_album_image(entry_id: str, image_type: str):
"""Serve an album entry's image (original or cropped)""" """Serve an album entry's image (original or cropped)"""
if image_type not in ("original", "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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
path = profile_picture_manager.get_album_image_path(entry_id, image_type) path = profile_picture_manager.get_album_image_path(entry_id, image_type)
if path: if path:
return FileResponse(path, media_type="image/png", headers={"Cache-Control": "no-cache"}) return FileResponse(path, media_type="image/png", headers={"Cache-Control": "no-cache"})
else: 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: 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") @router.post("/profile-picture/album/add")
@@ -373,10 +373,10 @@ async def add_to_album(file: UploadFile = File(...)):
"metadata": result.get("metadata", {}) "metadata": result.get("metadata", {})
} }
else: 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: except Exception as e:
logger.error(f"Error adding to album: {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") @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: except Exception as e:
logger.error(f"Error in batch album add: {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") @router.post("/profile-picture/album/{entry_id}/set-current")
async def set_album_entry_as_current(entry_id: str): async def set_album_entry_as_current(entry_id: str):
"""Set an album entry as the current Discord profile picture""" """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(): 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: try:
from utils.profile_picture_manager import profile_picture_manager from utils.profile_picture_manager import profile_picture_manager
result = await profile_picture_manager.set_album_entry_as_current( 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") "archived_entry_id": result.get("archived_entry_id")
} }
else: 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: 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") @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", {}) "metadata": result.get("metadata", {})
} }
else: 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: 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") @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", {}) "metadata": result.get("metadata", {})
} }
else: 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: 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") @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"]: if result["success"]:
return {"status": "ok", "message": "Description updated"} return {"status": "ok", "message": "Description updated"}
else: 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: 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}") @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): if profile_picture_manager.delete_album_entry(entry_id):
return {"status": "ok", "message": "Album entry deleted"} return {"status": "ok", "message": "Album entry deleted"}
else: 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: 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") @router.post("/profile-picture/album/delete-bulk")
@@ -510,7 +510,7 @@ async def bulk_delete_album_entries(req: BulkDeleteRequest):
**result **result
} }
except Exception as e: 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") @router.post("/profile-picture/album/add-current")
@@ -522,6 +522,6 @@ async def add_current_to_album():
if entry_id: if entry_id:
return {"status": "ok", "message": "Current PFP archived to album", "entry_id": entry_id} return {"status": "ok", "message": "Current PFP archived to album", "entry_id": entry_id}
else: 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: except Exception as e:
return {"status": "error", "message": str(e)} return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})

View File

@@ -3,6 +3,7 @@
import os import os
import json import json
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals import globals
from server_manager import server_manager from server_manager import server_manager
from routes.models import ServerConfigRequest from routes.models import ServerConfigRequest
@@ -63,7 +64,7 @@ def add_server(data: ServerConfigRequest):
server_manager.start_all_schedulers(globals.client) server_manager.start_all_schedulers(globals.client)
return {"status": "ok", "message": f"Server {data.guild_name} added successfully"} return {"status": "ok", "message": f"Server {data.guild_name} added successfully"}
else: 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}") @router.delete("/servers/{guild_id}")
@@ -73,7 +74,7 @@ def remove_server(guild_id: int):
if success: if success:
return {"status": "ok", "message": "Server removed successfully"} return {"status": "ok", "message": "Server removed successfully"}
else: 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}") @router.put("/servers/{guild_id}")
@@ -85,7 +86,7 @@ def update_server(guild_id: int, data: dict):
server_manager.start_all_schedulers(globals.client) server_manager.start_all_schedulers(globals.client)
return {"status": "ok", "message": "Server configuration updated"} return {"status": "ok", "message": "Server configuration updated"}
else: 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") @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'] required_fields = ['bedtime_hour', 'bedtime_minute', 'bedtime_hour_end', 'bedtime_minute_end']
for field in required_fields: for field in required_fields:
if field not in data: 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: try:
bedtime_hour = int(data['bedtime_hour']) 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']) bedtime_minute_end = int(data['bedtime_minute_end'])
if not (0 <= bedtime_hour <= 23) or not (0 <= bedtime_hour_end <= 23): 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): 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): 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) success = server_manager.update_server_config(guild_id, **data)
if success: 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}" "message": f"Bedtime range updated: {bedtime_hour:02d}:{bedtime_minute:02d} - {bedtime_hour_end:02d}:{bedtime_minute_end:02d}"
} }
else: 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: 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") @router.post("/servers/repair")
@@ -134,4 +135,4 @@ def repair_server_config():
server_manager.repair_config() server_manager.repair_config()
return {"status": "ok", "message": "Server configuration repaired and saved"} return {"status": "ok", "message": "Server configuration repaired and saved"}
except Exception as e: 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}"})

View File

@@ -2,6 +2,7 @@
import asyncio import asyncio
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
from fastapi.responses import JSONResponse
import discord import discord
import globals import globals
from utils.dm_logger import dm_logger 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 # Check if bot is running
if not globals.client or not globals.client.loop or not globals.client.loop.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 # Run the voice call setup in the bot's event loop
try: try:
@@ -41,7 +42,7 @@ async def initiate_voice_call(user_id: str = Form(...), voice_channel_id: str =
return result return result
except Exception as e: except Exception as e:
logger.error(f"Error initiating voice call: {e}", exc_info=True) 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): 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 # Get user and channel
user = await globals.client.fetch_user(user_id_int) user = await globals.client.fetch_user(user_id_int)
if not user: 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) channel = globals.client.get_channel(channel_id_int)
if not channel or not isinstance(channel, discord.VoiceChannel): 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) # Get a text channel for voice operations (use first text channel in guild)
text_channel = None text_channel = None
@@ -71,14 +72,14 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str):
break break
if not text_channel: 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 # Start containers
logger.info("Starting voice containers...") logger.info("Starting voice containers...")
containers_started = await ContainerManager.start_voice_containers() containers_started = await ContainerManager.start_voice_containers()
if not containers_started: 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 # Start voice session
logger.info(f"Starting voice session in {channel.name}") 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) await session_manager.start_session(channel.guild.id, channel, text_channel)
except Exception as e: except Exception as e:
await ContainerManager.stop_voice_containers() 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) # Set up voice call tracking (use integer ID)
session_manager.active_session.call_user_id = user_id_int 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: except Exception as e:
logger.error(f"Error in voice call implementation: {e}", exc_info=True) 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): 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 # Log to DM logger
dm_logger.log_user_message(user, sent_message, is_bot_message=True) dm_logger.log_user_message(user, sent_message, is_bot_message=True)
except: except Exception:
pass pass
except asyncio.CancelledError: except asyncio.CancelledError: