Initial commit: Miku Discord Bot
This commit is contained in:
207
.bot.bak.80825/api.py
Normal file
207
.bot.bak.80825/api.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# api.py
|
||||
|
||||
from fastapi import (
|
||||
FastAPI,
|
||||
Query,
|
||||
BackgroundTasks,
|
||||
Request, UploadFile,
|
||||
File,
|
||||
Form
|
||||
)
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
import globals
|
||||
from commands.actions import (
|
||||
force_sleep,
|
||||
wake_up,
|
||||
set_mood,
|
||||
reset_mood,
|
||||
check_mood,
|
||||
calm_miku,
|
||||
reset_conversation,
|
||||
send_bedtime_now
|
||||
)
|
||||
from utils.moods import nickname_mood_emoji
|
||||
from utils.autonomous import (
|
||||
miku_autonomous_tick,
|
||||
miku_say_something_general,
|
||||
miku_engage_random_user,
|
||||
share_miku_tweet,
|
||||
handle_custom_prompt
|
||||
)
|
||||
import asyncio
|
||||
import nest_asyncio
|
||||
import subprocess
|
||||
import io
|
||||
import discord
|
||||
import aiofiles
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse, PlainTextResponse
|
||||
nest_asyncio.apply()
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Serve static folder
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
# ========== Models ==========
|
||||
class MoodSetRequest(BaseModel):
|
||||
mood: str
|
||||
|
||||
class ConversationResetRequest(BaseModel):
|
||||
user_id: str
|
||||
|
||||
class CustomPromptRequest(BaseModel):
|
||||
prompt: str
|
||||
|
||||
# ========== Routes ==========
|
||||
@app.get("/")
|
||||
def read_index():
|
||||
return FileResponse("static/index.html")
|
||||
|
||||
@app.get("/logs")
|
||||
def get_logs():
|
||||
try:
|
||||
# Read last 100 lines of the log file
|
||||
with open("/app/bot.log", "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
last_100 = lines[-100:]
|
||||
return "".join(lines[-100] if len(lines) >= 100 else lines)
|
||||
except Exception as e:
|
||||
return f"Error reading log file: {e}"
|
||||
|
||||
@app.get("/prompt")
|
||||
def get_last_prompt():
|
||||
return {"prompt": globals.LAST_FULL_PROMPT or "No prompt has been issued yet."}
|
||||
|
||||
@app.get("/mood")
|
||||
def get_current_mood():
|
||||
return {"mood": check_mood()}
|
||||
|
||||
|
||||
@app.post("/mood")
|
||||
async def set_mood_endpoint(data: MoodSetRequest):
|
||||
success = set_mood(data.mood)
|
||||
if success:
|
||||
globals.client.loop.create_task(nickname_mood_emoji())
|
||||
return {"status": "ok", "new_mood": data.mood}
|
||||
return {"status": "error", "message": "Mood not recognized"}
|
||||
|
||||
|
||||
@app.post("/mood/reset")
|
||||
async def reset_mood_endpoint(background_tasks: BackgroundTasks):
|
||||
reset_mood()
|
||||
globals.client.loop.create_task(nickname_mood_emoji())
|
||||
return {"status": "ok", "new_mood": "neutral"}
|
||||
|
||||
|
||||
@app.post("/mood/calm")
|
||||
def calm_miku_endpoint():
|
||||
calm_miku()
|
||||
return {"status": "ok", "message": "Miku has calmed down."}
|
||||
|
||||
|
||||
@app.post("/conversation/reset")
|
||||
def reset_convo(data: ConversationResetRequest):
|
||||
reset_conversation(data.user_id)
|
||||
return {"status": "ok", "message": f"Memory reset for {data.user_id}"}
|
||||
|
||||
|
||||
@app.post("/sleep")
|
||||
async def force_sleep_endpoint():
|
||||
await force_sleep()
|
||||
globals.client.loop.create_task(nickname_mood_emoji())
|
||||
return {"status": "ok", "message": "Miku is now sleeping"}
|
||||
|
||||
|
||||
@app.post("/wake")
|
||||
async def wake_up_endpoint():
|
||||
await wake_up()
|
||||
globals.client.loop.create_task(nickname_mood_emoji())
|
||||
return {"status": "ok", "message": "Miku is now awake"}
|
||||
|
||||
|
||||
@app.post("/bedtime")
|
||||
async def bedtime_endpoint(background_tasks: BackgroundTasks):
|
||||
globals.client.loop.create_task(send_bedtime_now())
|
||||
return {"status": "ok", "message": "Bedtime message sent"}
|
||||
|
||||
@app.post("/autonomous/general")
|
||||
async def trigger_autonomous_general():
|
||||
globals.client.loop.create_task(miku_autonomous_tick(force=True, force_action="general"))
|
||||
return {"status": "ok", "message": "Miku say something general triggered manually"}
|
||||
|
||||
@app.post("/autonomous/engage")
|
||||
async def trigger_autonomous_engage_user():
|
||||
globals.client.loop.create_task(miku_autonomous_tick(force=True, force_action="engage_user"))
|
||||
return {"status": "ok", "message": "Miku engage random user triggered manually"}
|
||||
|
||||
@app.post("/autonomous/tweet")
|
||||
async def trigger_autonomous_tweet():
|
||||
globals.client.loop.create_task(miku_autonomous_tick(force=True, force_action="share_tweet"))
|
||||
return {"status": "ok", "message": "Miku share tweet triggered manually"}
|
||||
|
||||
@app.post("/autonomous/custom")
|
||||
async def custom_autonomous_message(req: CustomPromptRequest):
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
handle_custom_prompt(req.prompt), globals.client.loop
|
||||
)
|
||||
return {"success": True, "message": "Miku is working on it!"}
|
||||
except Exception as e:
|
||||
print(f"❌ Error running custom prompt in bot loop: {repr(e)}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@app.post("/manual/send")
|
||||
async def manual_send(
|
||||
message: str = Form(...),
|
||||
channel_id: str = Form(...),
|
||||
files: List[UploadFile] = File(default=[])
|
||||
):
|
||||
try:
|
||||
# Get the Discord channel Miku should post in
|
||||
channel = globals.client.get_channel(int(channel_id))
|
||||
if not channel:
|
||||
return {"success": False, "error": "Target channel not found"}
|
||||
|
||||
# Prepare file data (read in the async FastAPI thread)
|
||||
prepared_files = []
|
||||
for f in files:
|
||||
contents = await f.read()
|
||||
prepared_files.append((f.filename, contents))
|
||||
|
||||
# Define a coroutine that will run inside the bot loop
|
||||
async def send_message():
|
||||
channel = globals.client.get_channel(int(channel_id))
|
||||
if not channel:
|
||||
raise ValueError(f"Channel ID {channel_id} not found or bot cannot access it.")
|
||||
|
||||
discord_files = [
|
||||
discord.File(io.BytesIO(content), filename=filename)
|
||||
for filename, content in prepared_files
|
||||
]
|
||||
|
||||
await channel.send(content=message or None, files=discord_files or None)
|
||||
|
||||
# Schedule coroutine in bot's event loop
|
||||
future = asyncio.run_coroutine_threadsafe(send_message(), globals.client.loop)
|
||||
future.result(timeout=10) # Wait max 10 seconds for it to finish
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error in /manual/send: {repr(e)}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@app.get("/status")
|
||||
def status():
|
||||
return {
|
||||
"mood": globals.CURRENT_MOOD_NAME,
|
||||
"is_sleeping": globals.IS_SLEEPING,
|
||||
"previous_mood": globals.PREVIOUS_MOOD_NAME
|
||||
}
|
||||
|
||||
@app.get("/conversation/{user_id}")
|
||||
def get_conversation(user_id: str):
|
||||
return globals.conversation_history.get(user_id, [])
|
||||
Reference in New Issue
Block a user