Files
miku-discord/backups/2025-12-07/old-bot-bak-80825/bot.py
koko210Serve 330cedd9d1 chore: organize backup files into dated directory structure
- Consolidated all .bak.* files from bot/ directory into backups/2025-12-07/
- Moved unused autonomous_wip.py to backups (verified not imported anywhere)
- Relocated old .bot.bak.80825/ backup directory into backups/2025-12-07/old-bot-bak-80825/
- Preserved autonomous_v1_legacy.py as it is still actively used by autonomous.py
- Created new backups/ directory with date-stamped subdirectory for better organization
2025-12-07 23:54:38 +02:00

258 lines
10 KiB
Python

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)