Initial commit: Miku Discord Bot
This commit is contained in:
257
.bot.bak.80825/bot.py
Normal file
257
.bot.bak.80825/bot.py
Normal file
@@ -0,0 +1,257 @@
|
||||
import discord
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import random
|
||||
import string
|
||||
import datetime
|
||||
import os
|
||||
import threading
|
||||
import uvicorn
|
||||
import logging
|
||||
import sys
|
||||
from api import app
|
||||
|
||||
from command_router import handle_command
|
||||
from utils.scheduled import (
|
||||
schedule_random_bedtime,
|
||||
send_bedtime_reminder,
|
||||
send_monday_video
|
||||
)
|
||||
from utils.image_handling import (
|
||||
download_and_encode_image,
|
||||
analyze_image_with_qwen,
|
||||
rephrase_as_miku
|
||||
)
|
||||
from utils.core import (
|
||||
is_miku_addressed,
|
||||
)
|
||||
from utils.moods import (
|
||||
detect_mood_shift,
|
||||
set_sleep_state,
|
||||
nickname_mood_emoji,
|
||||
rotate_mood,
|
||||
load_mood_description,
|
||||
clear_angry_mood_after_delay
|
||||
)
|
||||
from utils.media import overlay_username_with_ffmpeg
|
||||
from utils.kindness import detect_and_react_to_kindness
|
||||
from utils.llm import query_ollama
|
||||
from utils.autonomous import setup_autonomous_speaking, load_last_sent_tweets
|
||||
|
||||
import globals
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s: %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler("bot.log", mode='a', encoding='utf-8'),
|
||||
logging.StreamHandler(sys.stdout) # Optional: see logs in stdout too
|
||||
],
|
||||
force=True # Override previous configs
|
||||
)
|
||||
|
||||
@globals.client.event
|
||||
async def on_ready():
|
||||
print(f'🎤 MikuBot connected as {globals.client.user}')
|
||||
|
||||
globals.BOT_USER = globals.client.user
|
||||
|
||||
# Change mood every 1 hour
|
||||
rotate_mood.start()
|
||||
|
||||
# Schedule the weekly task (Monday 07:30)
|
||||
globals.scheduler.add_job(send_monday_video, 'cron', day_of_week='mon', hour=4, minute=30)
|
||||
|
||||
# Schedule first bedtime reminder
|
||||
schedule_random_bedtime()
|
||||
# Reschedule every midnight
|
||||
globals.scheduler.add_job(schedule_random_bedtime, 'cron', hour=21, minute=0)
|
||||
#scheduler.add_job(send_bedtime_reminder, 'cron', hour=12, minute=22)i
|
||||
|
||||
# Schedule autonomous speaking
|
||||
setup_autonomous_speaking()
|
||||
load_last_sent_tweets()
|
||||
|
||||
globals.scheduler.start()
|
||||
|
||||
@globals.client.event
|
||||
async def on_message(message):
|
||||
if message.author == globals.client.user:
|
||||
return
|
||||
|
||||
handled, globals.CURRENT_MOOD_NAME, globals.CURRENT_MOOD, globals.PREVIOUS_MOOD_NAME, globals.IS_SLEEPING = await handle_command(
|
||||
message,
|
||||
set_sleep_state
|
||||
)
|
||||
|
||||
if message.content.strip().lower() == "miku, rape this nigga balls" and message.reference:
|
||||
async with message.channel.typing():
|
||||
# Get replied-to user
|
||||
try:
|
||||
replied_msg = await message.channel.fetch_message(message.reference.message_id)
|
||||
target_username = replied_msg.author.display_name
|
||||
|
||||
# Prepare video
|
||||
base_video = "MikuMikuBeam.mp4"
|
||||
output_video = f"/tmp/video_{''.join(random.choices(string.ascii_letters, k=5))}.mp4"
|
||||
|
||||
await overlay_username_with_ffmpeg(base_video, output_video, target_username)
|
||||
|
||||
caption = f"Here you go, @{target_username}! 🌟"
|
||||
#await message.channel.send(content=caption, file=discord.File(output_video))
|
||||
await replied_msg.reply(file=discord.File(output_video))
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error processing video: {e}")
|
||||
await message.channel.send("Sorry, something went wrong while generating the video.")
|
||||
return
|
||||
|
||||
text = message.content.strip()
|
||||
|
||||
if await is_miku_addressed(message):
|
||||
|
||||
if globals.IS_SLEEPING:
|
||||
# Initialize sleepy response count if not set yet
|
||||
if globals.SLEEPY_RESPONSES_LEFT is None:
|
||||
globals.SLEEPY_RESPONSES_LEFT = random.randint(3, 5)
|
||||
print(f"🎲 Sleepy responses allowed: {globals.SLEEPY_RESPONSES_LEFT}")
|
||||
|
||||
if globals.SLEEPY_RESPONSES_LEFT > 0:
|
||||
if random.random() < 1/3: # ⅓ chance
|
||||
sleep_talk_lines = [
|
||||
"mnnn... five more minutes... zzz...",
|
||||
"nya... d-don't tickle me there... mm~",
|
||||
"zz... nyaa~ pancakes flying... eep...",
|
||||
"so warm... stay close... zzz...",
|
||||
"huh...? is it morning...? nooo... \*rolls over*",
|
||||
"\*mumbles* pink clouds... and pudding... heehee...",
|
||||
"\*softly snores* zzz... nyuu... mmh..."
|
||||
]
|
||||
response = random.choice(sleep_talk_lines)
|
||||
await message.channel.typing()
|
||||
await asyncio.sleep(random.uniform(1.5, 3.0)) # random delay before replying
|
||||
await message.channel.send(response)
|
||||
globals.SLEEPY_RESPONSES_LEFT -= 1
|
||||
print(f"💤 Sleepy responses left: {globals.SLEEPY_RESPONSES_LEFT}")
|
||||
else:
|
||||
# No response at all
|
||||
print("😴 Miku is asleep and didn't respond.")
|
||||
return # Skip any further message handling
|
||||
else:
|
||||
# Exceeded sleepy response count — wake up angry now!
|
||||
globals.IS_SLEEPING = False
|
||||
globals.CURRENT_MOOD_NAME = "angry"
|
||||
globals.CURRENT_MOOD = load_mood_description("angry")
|
||||
globals.SLEEPY_RESPONSES_LEFT = None
|
||||
|
||||
# Set angry period end time 40 minutes from now
|
||||
globals.FORCED_ANGRY_UNTIL = datetime.datetime.utcnow() + datetime.timedelta(minutes=40)
|
||||
|
||||
# Cancel any existing angry timer task first
|
||||
if globals.ANGRY_WAKEUP_TIMER and not globals.ANGRY_WAKEUP_TIMER.done():
|
||||
globals.ANGRY_WAKEUP_TIMER.cancel()
|
||||
|
||||
# Start cooldown task to clear angry mood after 40 mins
|
||||
globals.ANGRY_WAKEUP_TIMER = asyncio.create_task(clear_angry_mood_after_delay())
|
||||
|
||||
print("😡 Miku woke up angry and will stay angry for 40 minutes!")
|
||||
|
||||
globals.JUST_WOKEN_UP = True # Set flag for next response
|
||||
|
||||
await nickname_mood_emoji()
|
||||
await set_sleep_state(False)
|
||||
|
||||
# Immediately get an angry response to send back
|
||||
try:
|
||||
async with message.channel.typing():
|
||||
angry_response = await query_ollama("...", user_id=str(message.author.id))
|
||||
await message.channel.send(angry_response)
|
||||
finally:
|
||||
# Reset the flag after sending the angry response
|
||||
globals.JUST_WOKEN_UP = False
|
||||
|
||||
return
|
||||
|
||||
prompt = text # No cleanup — keep it raw
|
||||
user_id = str(message.author.id)
|
||||
|
||||
# 1st kindness check with just keywords
|
||||
if globals.CURRENT_MOOD not in ["angry", "irritated"]:
|
||||
await detect_and_react_to_kindness(message)
|
||||
|
||||
# Add replied Miku message to conversation history as context
|
||||
if message.reference:
|
||||
try:
|
||||
replied_msg = await message.channel.fetch_message(message.reference.message_id)
|
||||
if replied_msg.author == globals.client.user:
|
||||
history = globals.conversation_history.get(user_id, [])
|
||||
if not history or (history and history[-1][1] != replied_msg.content):
|
||||
globals.conversation_history.setdefault(user_id, []).append(("", replied_msg.content))
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to fetch replied message for context: {e}")
|
||||
|
||||
async with message.channel.typing():
|
||||
# If message has an image attachment
|
||||
if message.attachments:
|
||||
for attachment in message.attachments:
|
||||
if any(attachment.filename.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".webp"]):
|
||||
|
||||
base64_img = await download_and_encode_image(attachment.url)
|
||||
if not base64_img:
|
||||
await message.channel.send("I couldn't load the image, sorry!")
|
||||
return
|
||||
# Analyze image (objective description)
|
||||
qwen_description = await analyze_image_with_qwen(base64_img)
|
||||
miku_reply = await rephrase_as_miku(qwen_description, prompt)
|
||||
|
||||
await message.channel.send(miku_reply)
|
||||
return
|
||||
|
||||
# If message is just a prompt, no image
|
||||
response = await query_ollama(prompt, user_id=str(message.author.id))
|
||||
|
||||
await message.channel.send(response)
|
||||
|
||||
# 2nd kindness check (only if no keywords detected)
|
||||
if globals.CURRENT_MOOD not in ["angry", "irritated"]:
|
||||
await detect_and_react_to_kindness(message, after_reply=True)
|
||||
|
||||
# Manual Monday test command
|
||||
if message.content.lower().strip() == "!monday":
|
||||
await send_monday_video()
|
||||
#await message.channel.send("✅ Monday message sent (or attempted). Check logs.")
|
||||
return
|
||||
|
||||
if globals.AUTO_MOOD and 'response' in locals():
|
||||
# Block auto mood updates if forced angry period is active
|
||||
now = datetime.datetime.utcnow()
|
||||
if globals.FORCED_ANGRY_UNTIL and now < globals.FORCED_ANGRY_UNTIL:
|
||||
print("🚫 Skipping auto mood detection — forced angry period active.")
|
||||
else:
|
||||
detected = detect_mood_shift(response)
|
||||
if detected and detected != globals.CURRENT_MOOD_NAME:
|
||||
|
||||
# Block direct transitions to asleep unless from sleepy
|
||||
if detected == "asleep" and globals.CURRENT_MOOD_NAME != "sleepy":
|
||||
print("❌ Ignoring asleep mood; Miku wasn't sleepy before.")
|
||||
else:
|
||||
globals.PREVIOUS_MOOD_NAME = globals.CURRENT_MOOD_NAME
|
||||
globals.CURRENT_MOOD_NAME = detected
|
||||
globals.CURRENT_MOOD = load_mood_description(detected)
|
||||
await nickname_mood_emoji()
|
||||
print(f"🔄 Auto-updated mood to: {detected}")
|
||||
|
||||
if detected == "asleep":
|
||||
globals.IS_SLEEPING = True
|
||||
await set_sleep_state(True)
|
||||
await asyncio.sleep(3600) # 1 hour
|
||||
globals.IS_SLEEPING = False
|
||||
await set_sleep_state(False)
|
||||
globals.CURRENT_MOOD_NAME = "neutral"
|
||||
globals.CURRENT_MOOD = load_mood_description("neutral")
|
||||
|
||||
def start_api():
|
||||
uvicorn.run(app, host="0.0.0.0", port=3939, log_level="info")
|
||||
|
||||
threading.Thread(target=start_api, daemon=True).start()
|
||||
globals.client.run(globals.DISCORD_BOT_TOKEN)
|
||||
Reference in New Issue
Block a user