Files
miku-discord/bot/routes/manual_send.py

198 lines
8.2 KiB
Python
Raw Permalink Normal View History

"""Manual message sending routes + message reactions."""
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
logger = get_logger('api')
router = APIRouter()
@router.post("/manual/send")
async def manual_send(
message: str = Form(...),
channel_id: str = Form(...),
files: List[UploadFile] = File(default=[]),
reply_to_message_id: str = Form(None),
mention_author: bool = Form(True)
):
try:
channel = globals.client.get_channel(int(channel_id))
if not channel:
return JSONResponse(status_code=404, content={"status": "error", "message": "Channel not found"})
# Read file content immediately before the request closes
file_data = []
for file in files:
try:
file_content = await file.read()
file_data.append({
'filename': file.filename,
'content': file_content
})
except Exception as e:
logger.error(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:
reference_message = None
if reply_to_message_id:
try:
reference_message = await channel.fetch_message(int(reply_to_message_id))
except Exception as e:
logger.error(f"Could not fetch message {reply_to_message_id} for reply: {e}")
return
if message.strip():
if reference_message:
await channel.send(message, reference=reference_message, mention_author=mention_author)
logger.info(f"Manual message sent as reply to #{channel.name}")
else:
await channel.send(message)
logger.info(f"Manual message sent to #{channel.name}")
for file_info in file_data:
try:
await channel.send(file=discord.File(io.BytesIO(file_info['content']), filename=file_info['filename']))
logger.info(f"File {file_info['filename']} sent to #{channel.name}")
except Exception as e:
logger.error(f"Failed to send file {file_info['filename']}: {e}")
except Exception as e:
logger.error(f"Failed to send message: {e}")
globals.client.loop.create_task(send_message_and_files())
return {"status": "ok", "message": "Message and files queued for sending"}
except Exception as e:
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
@router.post("/manual/send-webhook")
async def manual_send_webhook(
message: str = Form(...),
channel_id: str = Form(...),
persona: str = Form("miku"),
files: List[UploadFile] = File(default=[]),
reply_to_message_id: str = Form(None),
mention_author: bool = Form(True)
):
"""Send a manual message via webhook as either Hatsune Miku or Evil Miku"""
try:
from utils.bipolar_mode import get_or_create_webhooks_for_channel, get_miku_display_name, get_evil_miku_display_name
channel = globals.client.get_channel(int(channel_id))
if not channel:
return JSONResponse(status_code=404, content={"status": "error", "message": "Channel not found"})
if persona not in ["miku", "evil"]:
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid persona. Must be 'miku' or 'evil'"})
file_data = []
for file in files:
try:
file_content = await file.read()
file_data.append({
'filename': file.filename,
'content': file_content
})
except Exception as e:
logger.error(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:
webhooks = await get_or_create_webhooks_for_channel(channel)
if not webhooks:
logger.error(f"Failed to create webhooks for channel #{channel.name}")
return
webhook = webhooks["evil_miku"] if persona == "evil" else webhooks["miku"]
display_name = get_evil_miku_display_name() if persona == "evil" else get_miku_display_name()
discord_files = []
for file_info in file_data:
discord_files.append(discord.File(io.BytesIO(file_info['content']), filename=file_info['filename']))
from utils.bipolar_mode import get_persona_avatar_urls
avatar_urls = get_persona_avatar_urls()
avatar_url = avatar_urls.get("evil_miku") if persona == "evil" else avatar_urls.get("miku")
if discord_files:
await webhook.send(
content=message, username=display_name,
avatar_url=avatar_url, files=discord_files, wait=True
)
else:
await webhook.send(
content=message, username=display_name,
avatar_url=avatar_url, wait=True
)
persona_name = "Evil Miku" if persona == "evil" else "Hatsune Miku"
logger.info(f"Manual webhook message sent as {persona_name} to #{channel.name}")
except Exception as e:
logger.error(f"Failed to send webhook message: {e}")
import traceback
traceback.print_exc()
globals.client.loop.create_task(send_webhook_message())
return {"status": "ok", "message": f"Webhook message queued for sending as {persona}"}
except Exception as e:
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
@router.post("/messages/react")
async def add_reaction_to_message(
message_id: str = Form(...),
channel_id: str = Form(...),
emoji: str = Form(...)
):
"""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 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 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 JSONResponse(status_code=404, content={"status": "error", "message": f"Channel {channel_id} not found"})
async def add_reaction_task():
try:
message = await channel.fetch_message(msg_id)
await message.add_reaction(emoji)
logger.info(f"Added reaction {emoji} to message {msg_id} in channel #{channel.name}")
except discord.NotFound:
logger.error(f"Message {msg_id} not found in channel #{channel.name}")
except discord.Forbidden:
logger.error(f"Bot doesn't have permission to add reactions in channel #{channel.name}")
except discord.HTTPException as e:
logger.error(f"Failed to add reaction: {e}")
except Exception as e:
logger.error(f"Unexpected error adding reaction: {e}")
globals.client.loop.create_task(add_reaction_task())
return {
"status": "ok",
"message": f"Reaction {emoji} queued for message {message_id}"
}
except Exception as e:
logger.error(f"Failed to add reaction: {e}")
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to add reaction: {e}"})