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
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from utils.logger import get_logger
from utils.log_config import load_config as load_log_config
@@ -23,7 +24,7 @@ app = FastAPI()
async def global_exception_handler(request: Request, exc: Exception):
"""Catch all unhandled exceptions and log them properly."""
logger.error(f"Unhandled exception on {request.method} {request.url.path}: {exc}", exc_info=True)
return {"success": False, "error": "Internal server error"}
return JSONResponse(status_code=500, content={"success": False, "error": "Internal server error"})
# ========== Logging Middleware ==========
@app.middleware("http")

View File

@@ -1,6 +1,7 @@
"""Autonomous action routes: V1, V2, per-server autonomous."""
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals
from server_manager import server_manager
from routes.models import CustomPromptRequest
@@ -25,7 +26,7 @@ async def trigger_autonomous_general(guild_id: int = None):
globals.client.loop.create_task(miku_say_something_general())
return {"status": "ok", "message": "Autonomous general message queued for all servers"}
else:
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/engage")
@@ -65,7 +66,7 @@ async def trigger_autonomous_engage_user(
return {"status": "ok", "message": " ".join(msg_parts)}
else:
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/tweet")
@@ -86,7 +87,7 @@ async def trigger_autonomous_tweet(guild_id: int = None, tweet_url: str = None):
msg += f" with URL {tweet_url}"
return {"status": "ok", "message": msg}
else:
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/custom")
@@ -101,7 +102,7 @@ async def custom_autonomous_message(req: CustomPromptRequest, guild_id: int = No
globals.client.loop.create_task(handle_custom_prompt(req.prompt))
return {"status": "ok", "message": "Custom autonomous message queued for all servers"}
else:
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/reaction")
@@ -116,7 +117,7 @@ async def trigger_autonomous_reaction(guild_id: int = None):
globals.client.loop.create_task(miku_autonomous_reaction(force=True))
return {"status": "ok", "message": "Autonomous reaction queued for all servers"}
else:
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/autonomous/join-conversation")
@@ -135,7 +136,7 @@ async def trigger_detect_and_join_conversation(guild_id: int = None):
return {"status": "ok", "message": "Detect and join conversation queued for all servers"}
else:
logger.error(f"Bot not ready: client={globals.client}, loop={globals.client.loop if globals.client else None}")
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
# ========== Per-Server Autonomous ==========
@@ -148,7 +149,7 @@ async def trigger_autonomous_general_for_server(guild_id: int):
await miku_say_something_general_for_server(guild_id)
return {"status": "ok", "message": f"Autonomous general message triggered for server {guild_id}"}
except Exception as e:
return {"status": "error", "message": f"Failed to trigger autonomous message: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to trigger autonomous message: {e}"})
@router.post("/servers/{guild_id}/autonomous/engage")
@@ -175,7 +176,7 @@ async def trigger_autonomous_engage_for_server(
return {"status": "ok", "message": " ".join(msg_parts)}
except Exception as e:
return {"status": "error", "message": f"Failed to trigger user engagement: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to trigger user engagement: {e}"})
@router.post("/servers/{guild_id}/autonomous/custom")
@@ -187,9 +188,9 @@ async def custom_autonomous_message_for_server(guild_id: int, req: CustomPromptR
if success:
return {"status": "ok", "message": f"Custom autonomous message sent to server {guild_id}"}
else:
return {"status": "error", "message": f"Failed to send custom message to server {guild_id}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to send custom message to server {guild_id}"})
except Exception as e:
return {"status": "error", "message": f"Error: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
@router.post("/servers/{guild_id}/autonomous/tweet")
@@ -200,7 +201,7 @@ async def trigger_autonomous_tweet_for_server(guild_id: int):
await share_miku_tweet_for_server(guild_id)
return {"status": "ok", "message": f"Autonomous tweet sharing triggered for server {guild_id}"}
except Exception as e:
return {"status": "error", "message": f"Failed to trigger tweet sharing: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to trigger tweet sharing: {e}"})
# ========== Autonomous V2 ==========
@@ -213,7 +214,7 @@ async def get_v2_stats(guild_id: int):
stats = get_v2_stats_for_server(guild_id)
return {"status": "ok", "guild_id": guild_id, "stats": stats}
except Exception as e:
return {"status": "error", "message": str(e)}
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
@router.get("/autonomous/v2/check/{guild_id}")
@@ -223,16 +224,16 @@ async def manual_v2_check(guild_id: int):
from utils.autonomous_v2_integration import manual_trigger_v2_check
if not globals.client:
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
result = await manual_trigger_v2_check(guild_id, globals.client)
if isinstance(result, str):
return {"status": "error", "message": result}
return JSONResponse(status_code=500, content={"status": "error", "message": result})
return {"status": "ok", "guild_id": guild_id, "analysis": result}
except Exception as e:
return {"status": "error", "message": str(e)}
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
@router.get("/autonomous/v2/status")
@@ -258,4 +259,4 @@ async def get_v2_status():
return {"status": "ok", "servers": status}
except Exception as e:
return {"status": "error", "message": str(e)}
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})

View File

@@ -2,6 +2,7 @@
import asyncio
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals
from routes.models import BipolarTriggerRequest
from utils.logger import get_logger
@@ -106,23 +107,23 @@ def trigger_argument(data: BipolarTriggerRequest):
try:
channel_id = int(data.channel_id)
except ValueError:
return {"status": "error", "message": "Invalid channel ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid channel ID format"})
message_id = None
if data.message_id:
try:
message_id = int(data.message_id)
except ValueError:
return {"status": "error", "message": "Invalid message ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid message ID format"})
if not is_bipolar_mode():
return {"status": "error", "message": "Bipolar mode is not enabled"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Bipolar mode is not enabled"})
if is_argument_in_progress(channel_id):
return {"status": "error", "message": "An argument is already in progress in this channel"}
return JSONResponse(status_code=409, content={"status": "error", "message": "An argument is already in progress in this channel"})
if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
return {"status": "error", "message": "Discord client not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"})
# If message_id is provided, use the message-based trigger
if message_id:
@@ -145,7 +146,7 @@ def trigger_argument(data: BipolarTriggerRequest):
# Otherwise, find the channel and trigger normally
channel = globals.client.get_channel(channel_id)
if not channel:
return {"status": "error", "message": f"Channel {channel_id} not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"Channel {channel_id} not found"})
# Trigger the argument
globals.client.loop.create_task(force_trigger_argument(channel, globals.client, data.context))
@@ -168,19 +169,19 @@ def trigger_dialogue(data: dict):
message_id_str = data.get("message_id")
if not message_id_str:
return {"status": "error", "message": "Message ID is required"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Message ID is required"})
# Parse message ID
try:
message_id = int(message_id_str)
except ValueError:
return {"status": "error", "message": "Invalid message ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid message ID format"})
if not is_bipolar_mode():
return {"status": "error", "message": "Bipolar mode is not enabled"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Bipolar mode is not enabled"})
if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
return {"status": "error", "message": "Discord client not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"})
async def trigger_dialogue_task():
try:
@@ -191,7 +192,7 @@ def trigger_dialogue(data: dict):
try:
message = await channel.fetch_message(message_id)
break
except:
except Exception:
continue
if not message:
@@ -272,7 +273,7 @@ def cleanup_bipolar_webhooks():
from utils.bipolar_mode import cleanup_webhooks
if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
return {"status": "error", "message": "Discord client not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"})
globals.client.loop.create_task(cleanup_webhooks(globals.client))
return {"status": "ok", "message": "Webhook cleanup started"}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
"""Core routes: index, logs, prompts, status, conversation."""
from fastapi import APIRouter
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, JSONResponse
import globals
from server_manager import server_manager
from utils.conversation_history import conversation_history
@@ -26,7 +26,7 @@ def get_logs():
last_100 = lines[-100:] if len(lines) >= 100 else lines
return "".join(last_100)
except Exception as e:
return f"Error reading log file: {e}"
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error reading log file: {e}"})
@router.get("/prompt")

View File

@@ -5,6 +5,7 @@ import os
import json
from typing import List
from fastapi import APIRouter, UploadFile, File, Form
from fastapi.responses import JSONResponse
import discord
import globals
from routes.models import CustomPromptRequest
@@ -25,7 +26,7 @@ async def send_custom_prompt_dm(user_id: str, req: CustomPromptRequest):
user_id_int = int(user_id)
user = globals.client.get_user(user_id_int)
if not user:
return {"status": "error", "message": f"User {user_id} not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"User {user_id} not found"})
# Use the LLM query function for DM context
from utils.llm import query_llama
@@ -47,9 +48,9 @@ async def send_custom_prompt_dm(user_id: str, req: CustomPromptRequest):
return {"status": "ok", "message": f"Custom DM prompt queued for user {user_id}"}
except ValueError:
return {"status": "error", "message": "Invalid user ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID format"})
except Exception as e:
return {"status": "error", "message": f"Error: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
@router.post("/dm/{user_id}/manual")
@@ -65,7 +66,7 @@ async def send_manual_message_dm(
user_id_int = int(user_id)
user = globals.client.get_user(user_id_int)
if not user:
return {"status": "error", "message": f"User {user_id} not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"User {user_id} not found"})
# Read file content immediately before the request closes
file_data = []
@@ -78,7 +79,7 @@ async def send_manual_message_dm(
})
except Exception as e:
logger.error(f"Failed to read file {file.filename}: {e}")
return {"status": "error", "message": f"Failed to read file {file.filename}: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to read file {file.filename}: {e}"})
async def send_dm_message_and_files():
try:
@@ -120,9 +121,9 @@ async def send_manual_message_dm(
return {"status": "ok", "message": f"Manual DM message queued for user {user_id}"}
except ValueError:
return {"status": "error", "message": "Invalid user ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID format"})
except Exception as e:
return {"status": "error", "message": f"Error: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
# ========== DM Logging Endpoints ==========
@@ -134,7 +135,7 @@ def get_dm_users():
users = dm_logger.get_all_dm_users()
return {"status": "ok", "users": users}
except Exception as e:
return {"status": "error", "message": f"Failed to get DM users: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get DM users: {e}"})
@router.get("/dms/users/{user_id}")
@@ -146,9 +147,9 @@ def get_dm_user_conversation(user_id: str):
summary = dm_logger.get_user_conversation_summary(user_id_int)
return {"status": "ok", "summary": summary}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to get user conversation: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get user conversation: {e}"})
@router.get("/dms/users/{user_id}/conversations")
@@ -180,10 +181,10 @@ def get_dm_conversations(user_id: str, limit: int = 50):
return {"status": "ok", "conversations": conversations}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to get conversations for user {user_id}: {e}")
return {"status": "error", "message": f"Failed to get conversations: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get conversations: {e}"})
@router.get("/dms/users/{user_id}/search")
@@ -195,9 +196,9 @@ def search_dm_conversations(user_id: str, query: str, limit: int = 10):
results = dm_logger.search_user_conversations(user_id_int, query, limit)
return {"status": "ok", "results": results}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to search conversations: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to search conversations: {e}"})
@router.get("/dms/users/{user_id}/export")
@@ -209,9 +210,9 @@ def export_dm_conversation(user_id: str, format: str = "json"):
export_path = dm_logger.export_user_conversation(user_id_int, format)
return {"status": "ok", "export_path": export_path, "format": format}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to export conversation: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to export conversation: {e}"})
@router.delete("/dms/users/{user_id}")
@@ -225,11 +226,11 @@ def delete_dm_user_logs(user_id: str):
os.remove(log_file)
return {"status": "ok", "message": f"Deleted DM logs for user {user_id}"}
else:
return {"status": "error", "message": f"No DM logs found for user {user_id}"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"No DM logs found for user {user_id}"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to delete DM logs: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete DM logs: {e}"})
# ========== User Blocking & DM Management ==========
@@ -242,7 +243,7 @@ def get_blocked_users():
return {"status": "ok", "blocked_users": blocked_users}
except Exception as e:
logger.error(f"Failed to get blocked users: {e}")
return {"status": "error", "message": f"Failed to get blocked users: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get blocked users: {e}"})
@router.post("/dms/users/{user_id}/block")
@@ -261,13 +262,13 @@ def block_user(user_id: str):
logger.info(f"User {user_id} ({username}) blocked")
return {"status": "ok", "message": f"User {username} has been blocked"}
else:
return {"status": "error", "message": f"User {username} is already blocked"}
return JSONResponse(status_code=409, content={"status": "error", "message": f"User {username} is already blocked"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to block user {user_id}: {e}")
return {"status": "error", "message": f"Failed to block user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to block user: {e}"})
@router.post("/dms/users/{user_id}/unblock")
@@ -281,13 +282,13 @@ def unblock_user(user_id: str):
logger.info(f"User {user_id} unblocked")
return {"status": "ok", "message": f"User has been unblocked"}
else:
return {"status": "error", "message": f"User is not blocked"}
return JSONResponse(status_code=409, content={"status": "error", "message": f"User is not blocked"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to unblock user {user_id}: {e}")
return {"status": "error", "message": f"Failed to unblock user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to unblock user: {e}"})
@router.post("/dms/users/{user_id}/conversations/{conversation_id}/delete")
@@ -308,10 +309,10 @@ def delete_conversation(user_id: str, conversation_id: str):
return {"status": "ok", "message": "Message deletion queued (will delete from both Discord and logs)"}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to queue conversation deletion {conversation_id}: {e}")
return {"status": "error", "message": f"Failed to delete conversation: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete conversation: {e}"})
@router.post("/dms/users/{user_id}/conversations/delete-all")
@@ -331,10 +332,10 @@ def delete_all_conversations(user_id: str):
return {"status": "ok", "message": "Bulk deletion queued (will delete all Miku messages from Discord and clear logs)"}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to queue bulk conversation deletion for user {user_id}: {e}")
return {"status": "error", "message": f"Failed to delete conversations: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete conversations: {e}"})
@router.post("/dms/users/{user_id}/delete-completely")
@@ -348,13 +349,13 @@ def delete_user_completely(user_id: str):
logger.info(f"Completely deleted user {user_id}")
return {"status": "ok", "message": "User data deleted completely"}
else:
return {"status": "error", "message": "No user data found"}
return JSONResponse(status_code=404, content={"status": "error", "message": "No user data found"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to completely delete user {user_id}: {e}")
return {"status": "error", "message": f"Failed to delete user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete user: {e}"})
# ========== DM Interaction Analysis Endpoints ==========
@@ -366,7 +367,7 @@ def run_dm_analysis():
from utils.dm_interaction_analyzer import dm_analyzer
if dm_analyzer is None:
return {"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."}
return JSONResponse(status_code=503, content={"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."})
# Schedule analysis in Discord's event loop
async def run_analysis():
@@ -377,7 +378,7 @@ def run_dm_analysis():
return {"status": "ok", "message": "DM analysis started"}
except Exception as e:
logger.error(f"Failed to run DM analysis: {e}")
return {"status": "error", "message": f"Failed to run DM analysis: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to run DM analysis: {e}"})
@router.post("/dms/users/{user_id}/analyze")
@@ -387,7 +388,7 @@ def analyze_user_interaction(user_id: str):
from utils.dm_interaction_analyzer import dm_analyzer
if dm_analyzer is None:
return {"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."}
return JSONResponse(status_code=503, content={"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."})
user_id_int = int(user_id)
@@ -401,10 +402,10 @@ def analyze_user_interaction(user_id: str):
return {"status": "ok", "message": f"Analysis started for user {user_id}", "reported": True}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to analyze user {user_id}: {e}")
return {"status": "error", "message": f"Failed to analyze user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to analyze user: {e}"})
@router.get("/dms/analysis/reports")
@@ -432,7 +433,7 @@ def get_analysis_reports(limit: int = 20):
return {"status": "ok", "reports": reports}
except Exception as e:
logger.error(f"Failed to get reports: {e}")
return {"status": "error", "message": f"Failed to get reports: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get reports: {e}"})
@router.get("/dms/analysis/reports/{user_id}")
@@ -461,7 +462,7 @@ def get_user_reports(user_id: str, limit: int = 10):
return {"status": "ok", "reports": reports}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to get user reports: {e}")
return {"status": "error", "message": f"Failed to get user reports: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get user reports: {e}"})

View File

@@ -1,6 +1,7 @@
"""Evil mode routes."""
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals
from routes.models import EvilMoodSetRequest
from utils.logger import get_logger
@@ -43,7 +44,7 @@ def enable_evil_mode():
globals.client.loop.create_task(apply_evil_mode_changes(globals.client))
return {"status": "ok", "message": "Evil mode enabled", "evil_mode": True}
else:
return {"status": "error", "message": "Discord client not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"})
@router.post("/evil-mode/disable")
@@ -58,7 +59,7 @@ def disable_evil_mode():
globals.client.loop.create_task(revert_evil_mode_changes(globals.client))
return {"status": "ok", "message": "Evil mode disabled", "evil_mode": False}
else:
return {"status": "error", "message": "Discord client not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"})
@router.post("/evil-mode/toggle")
@@ -67,7 +68,7 @@ def toggle_evil_mode():
from utils.evil_mode import apply_evil_mode_changes, revert_evil_mode_changes
if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
return {"status": "error", "message": "Discord client not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Discord client not ready"})
if globals.EVIL_MODE:
globals.client.loop.create_task(revert_evil_mode_changes(globals.client))
@@ -95,10 +96,10 @@ def set_evil_mood_endpoint(data: EvilMoodSetRequest):
from utils.evil_mode import set_evil_mood, is_valid_evil_mood, update_all_evil_nicknames
if not is_valid_evil_mood(data.mood):
return {
return JSONResponse(status_code=400, content={
"status": "error",
"message": f"Mood '{data.mood}' not recognized. Available evil moods: {', '.join(globals.EVIL_AVAILABLE_MOODS)}"
}
})
success = set_evil_mood(data.mood)
if success:
@@ -107,4 +108,4 @@ def set_evil_mood_endpoint(data: EvilMoodSetRequest):
globals.client.loop.create_task(update_all_evil_nicknames(globals.client))
return {"status": "ok", "new_mood": data.mood}
return {"status": "error", "message": "Failed to set evil mood"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to set evil mood"})

View File

@@ -1,6 +1,7 @@
"""Figurine subscriber and send routes."""
from fastapi import APIRouter, Form
from fastapi.responses import JSONResponse
import globals
from utils.figurine_notifier import (
load_subscribers as figurine_load_subscribers,
@@ -29,7 +30,7 @@ async def add_figurine_subscriber(user_id: str = Form(...)):
ok = figurine_add_subscriber(uid)
return {"status": "ok", "added": ok}
except Exception as e:
return {"status": "error", "message": str(e)}
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
@router.delete("/figurines/subscribers/{user_id}")
@@ -39,7 +40,7 @@ async def delete_figurine_subscriber(user_id: str):
ok = figurine_remove_subscriber(uid)
return {"status": "ok", "removed": ok}
except Exception as e:
return {"status": "error", "message": str(e)}
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
@router.post("/figurines/send_now")
@@ -49,7 +50,7 @@ async def figurines_send_now(tweet_url: str = Form(None)):
logger.info(f"Sending figurine DMs to all subscribers, tweet_url: {tweet_url}")
globals.client.loop.create_task(send_figurine_dm_to_all_subscribers(globals.client, tweet_url=tweet_url))
return {"status": "ok", "message": "Figurine DMs queued"}
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
@router.post("/figurines/send_to_user")
@@ -59,14 +60,14 @@ async def figurines_send_to_user(user_id: str = Form(...), tweet_url: str = Form
if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
logger.error("Bot not ready")
return {"status": "error", "message": "Bot not ready"}
return JSONResponse(status_code=503, content={"status": "error", "message": "Bot not ready"})
try:
user_id_int = int(user_id)
logger.debug(f"Parsed user_id as {user_id_int}")
except ValueError:
logger.error(f"Invalid user ID: '{user_id}'")
return {"status": "error", "message": "Invalid user ID"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID"})
# Clean up tweet URL if it's empty string
if tweet_url == "":

View File

@@ -1,6 +1,7 @@
"""GPU selection routes."""
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from utils.logger import get_logger
logger = get_logger('api')
@@ -22,7 +23,7 @@ async def select_gpu(request: Request):
gpu = data.get("gpu", "nvidia").lower()
if gpu not in ["nvidia", "amd"]:
return {"status": "error", "message": "Invalid GPU selection. Must be 'nvidia' or 'amd'"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid GPU selection. Must be 'nvidia' or 'amd'"})
try:
from config_manager import config_manager
@@ -32,7 +33,7 @@ async def select_gpu(request: Request):
logger.info(f"GPU Selection: Switched to {gpu.upper()} GPU")
return {"status": "ok", "message": f"Switched to {gpu.upper()} GPU", "gpu": gpu}
else:
return {"status": "error", "message": "Failed to save GPU state"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to save GPU state"})
except Exception as e:
logger.error(f"GPU Selection Error: {e}")
return {"status": "error", "message": str(e)}
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})

View File

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

View File

@@ -1,6 +1,7 @@
"""Language mode routes."""
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals
from utils.logger import get_logger
@@ -53,7 +54,7 @@ def toggle_language_mode():
def set_language_mode(language: str = "english"):
"""Set language mode to either 'english' or 'japanese'"""
if language.lower() not in ["english", "japanese"]:
return {"error": f"Invalid language mode '{language}'. Use 'english' or 'japanese'."}, 400
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid language mode '{language}'. Use 'english' or 'japanese'."})
globals.LANGUAGE_MODE = language.lower()
model_used = globals.JAPANESE_TEXT_MODEL if language.lower() == "japanese" else globals.TEXT_MODEL

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
"""Mood management routes: DM mood, per-server mood, available moods, test mood."""
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals
from server_manager import server_manager
from routes.models import MoodSetRequest
@@ -23,7 +24,7 @@ async def set_mood_endpoint(data: MoodSetRequest):
# This endpoint now operates on DM_MOOD
from utils.moods import MOOD_EMOJIS
if data.mood not in MOOD_EMOJIS:
return {"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"})
# Update DM mood (DMs don't have nicknames, so no nickname update needed)
globals.DM_MOOD = data.mood
@@ -94,13 +95,13 @@ async def set_server_mood_endpoint(guild_id: int, data: MoodSetRequest):
# Check if server exists
if guild_id not in server_manager.servers:
logger.warning(f"Server {guild_id} not found in server_manager.servers")
return {"status": "error", "message": "Server not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": "Server not found"})
# Check if mood is valid
from utils.moods import MOOD_EMOJIS
if data.mood not in MOOD_EMOJIS:
logger.warning(f"Mood '{data.mood}' not found in MOOD_EMOJIS. Available moods: {list(MOOD_EMOJIS.keys())}")
return {"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Mood '{data.mood}' not recognized. Available moods: {', '.join(MOOD_EMOJIS.keys())}"})
success = server_manager.set_server_mood(guild_id, data.mood)
logger.debug(f"Server mood set result: {success}")
@@ -113,7 +114,7 @@ async def set_server_mood_endpoint(guild_id: int, data: MoodSetRequest):
return {"status": "ok", "new_mood": data.mood, "guild_id": guild_id}
logger.warning(f"set_server_mood returned False for unknown reason")
return {"status": "error", "message": "Failed to set server mood"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to set server mood"})
@router.post("/servers/{guild_id}/mood/reset")
@@ -124,7 +125,7 @@ async def reset_server_mood_endpoint(guild_id: int):
# Check if server exists
if guild_id not in server_manager.servers:
logger.warning(f"Server {guild_id} not found in server_manager.servers")
return {"status": "error", "message": "Server not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": "Server not found"})
logger.debug(f"Server validation passed, calling set_server_mood")
success = server_manager.set_server_mood(guild_id, "neutral")
@@ -138,7 +139,7 @@ async def reset_server_mood_endpoint(guild_id: int):
return {"status": "ok", "new_mood": "neutral", "guild_id": guild_id}
logger.warning(f"set_server_mood returned False for unknown reason")
return {"status": "error", "message": "Failed to reset server mood"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to reset server mood"})
@router.get("/servers/{guild_id}/mood/state")
@@ -147,7 +148,7 @@ def get_server_mood_state(guild_id: int):
mood_state = server_manager.get_server_mood_state(guild_id)
if mood_state:
return {"status": "ok", "guild_id": guild_id, "mood_state": mood_state}
return {"status": "error", "message": "Server not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": "Server not found"})
# ========== Misc Mood ==========
@@ -166,7 +167,7 @@ async def test_mood_change(guild_id: int, data: MoodSetRequest):
# Check if server exists
if guild_id not in server_manager.servers:
return {"status": "error", "message": f"Server {guild_id} not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"Server {guild_id} not found"})
server_config = server_manager.get_server_config(guild_id)
logger.debug(f"TEST: Server config found: {server_config.guild_name if server_config else 'None'}")
@@ -189,4 +190,4 @@ async def test_mood_change(guild_id: int, data: MoodSetRequest):
return {"status": "ok", "message": f"Test mood change completed", "success": success}
return {"status": "error", "message": "Mood change failed"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Mood change failed"})

View File

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

View File

@@ -3,6 +3,7 @@
import os
import json
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import globals
from server_manager import server_manager
from routes.models import ServerConfigRequest
@@ -63,7 +64,7 @@ def add_server(data: ServerConfigRequest):
server_manager.start_all_schedulers(globals.client)
return {"status": "ok", "message": f"Server {data.guild_name} added successfully"}
else:
return {"status": "error", "message": "Failed to add server"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to add server"})
@router.delete("/servers/{guild_id}")
@@ -73,7 +74,7 @@ def remove_server(guild_id: int):
if success:
return {"status": "ok", "message": "Server removed successfully"}
else:
return {"status": "error", "message": "Failed to remove server"}
return JSONResponse(status_code=404, content={"status": "error", "message": "Failed to remove server"})
@router.put("/servers/{guild_id}")
@@ -85,7 +86,7 @@ def update_server(guild_id: int, data: dict):
server_manager.start_all_schedulers(globals.client)
return {"status": "ok", "message": "Server configuration updated"}
else:
return {"status": "error", "message": "Failed to update server configuration"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to update server configuration"})
@router.post("/servers/{guild_id}/bedtime-range")
@@ -96,7 +97,7 @@ def update_server_bedtime_range(guild_id: int, data: dict):
required_fields = ['bedtime_hour', 'bedtime_minute', 'bedtime_hour_end', 'bedtime_minute_end']
for field in required_fields:
if field not in data:
return {"status": "error", "message": f"Missing required field: {field}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Missing required field: {field}"})
try:
bedtime_hour = int(data['bedtime_hour'])
@@ -105,12 +106,12 @@ def update_server_bedtime_range(guild_id: int, data: dict):
bedtime_minute_end = int(data['bedtime_minute_end'])
if not (0 <= bedtime_hour <= 23) or not (0 <= bedtime_hour_end <= 23):
return {"status": "error", "message": "Hours must be between 0 and 23"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Hours must be between 0 and 23"})
if not (0 <= bedtime_minute <= 59) or not (0 <= bedtime_minute_end <= 59):
return {"status": "error", "message": "Minutes must be between 0 and 59"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Minutes must be between 0 and 59"})
except (ValueError, TypeError):
return {"status": "error", "message": "Invalid time values provided"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid time values provided"})
success = server_manager.update_server_config(guild_id, **data)
if success:
@@ -122,9 +123,9 @@ def update_server_bedtime_range(guild_id: int, data: dict):
"message": f"Bedtime range updated: {bedtime_hour:02d}:{bedtime_minute:02d} - {bedtime_hour_end:02d}:{bedtime_minute_end:02d}"
}
else:
return {"status": "error", "message": "Updated config but failed to update scheduler"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Updated config but failed to update scheduler"})
else:
return {"status": "error", "message": "Failed to update bedtime range"}
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to update bedtime range"})
@router.post("/servers/repair")
@@ -134,4 +135,4 @@ def repair_server_config():
server_manager.repair_config()
return {"status": "ok", "message": "Server configuration repaired and saved"}
except Exception as e:
return {"status": "error", "message": f"Failed to repair configuration: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to repair configuration: {e}"})

View File

@@ -2,6 +2,7 @@
import asyncio
from fastapi import APIRouter, Form
from fastapi.responses import JSONResponse
import discord
import globals
from utils.dm_logger import dm_logger
@@ -29,7 +30,7 @@ async def initiate_voice_call(user_id: str = Form(...), voice_channel_id: str =
# Check if bot is running
if not globals.client or not globals.client.loop or not globals.client.loop.is_running():
return {"success": False, "error": "Bot is not running"}
return JSONResponse(status_code=503, content={"success": False, "error": "Bot is not running"})
# Run the voice call setup in the bot's event loop
try:
@@ -41,7 +42,7 @@ async def initiate_voice_call(user_id: str = Form(...), voice_channel_id: str =
return result
except Exception as e:
logger.error(f"Error initiating voice call: {e}", exc_info=True)
return {"success": False, "error": str(e)}
return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str):
@@ -57,11 +58,11 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str):
# Get user and channel
user = await globals.client.fetch_user(user_id_int)
if not user:
return {"success": False, "error": "User not found"}
return JSONResponse(status_code=404, content={"success": False, "error": "User not found"})
channel = globals.client.get_channel(channel_id_int)
if not channel or not isinstance(channel, discord.VoiceChannel):
return {"success": False, "error": "Voice channel not found"}
return JSONResponse(status_code=404, content={"success": False, "error": "Voice channel not found"})
# Get a text channel for voice operations (use first text channel in guild)
text_channel = None
@@ -71,14 +72,14 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str):
break
if not text_channel:
return {"success": False, "error": "No accessible text channel found"}
return JSONResponse(status_code=404, content={"success": False, "error": "No accessible text channel found"})
# Start containers
logger.info("Starting voice containers...")
containers_started = await ContainerManager.start_voice_containers()
if not containers_started:
return {"success": False, "error": "Failed to start voice containers"}
return JSONResponse(status_code=500, content={"success": False, "error": "Failed to start voice containers"})
# Start voice session
logger.info(f"Starting voice session in {channel.name}")
@@ -88,7 +89,7 @@ async def _initiate_voice_call_impl(user_id: str, voice_channel_id: str):
await session_manager.start_session(channel.guild.id, channel, text_channel)
except Exception as e:
await ContainerManager.stop_voice_containers()
return {"success": False, "error": f"Failed to start voice session: {str(e)}"}
return JSONResponse(status_code=500, content={"success": False, "error": f"Failed to start voice session: {str(e)}"})
# Set up voice call tracking (use integer ID)
session_manager.active_session.call_user_id = user_id_int
@@ -143,7 +144,7 @@ Keep it brief (1-2 sentences). Make it feel personal and enthusiastic!"""
except Exception as e:
logger.error(f"Error in voice call implementation: {e}", exc_info=True)
return {"success": False, "error": str(e)}
return JSONResponse(status_code=500, content={"success": False, "error": str(e)})
async def _voice_call_timeout_handler(voice_session, user: discord.User, channel: discord.VoiceChannel):
@@ -171,7 +172,7 @@ async def _voice_call_timeout_handler(voice_session, user: discord.User, channel
# Log to DM logger
dm_logger.log_user_message(user, sent_message, is_bot_message=True)
except:
except Exception:
pass
except asyncio.CancelledError: