Files
miku-discord/bot/utils/scheduled.py

196 lines
9.0 KiB
Python
Raw Normal View History

2025-12-07 17:15:09 +02:00
# utils/scheduled.py
import random
import json
import os
import time
import asyncio
from datetime import datetime, timedelta
from apscheduler.triggers.date import DateTrigger
from discord import Status, ActivityType
import globals
from server_manager import server_manager
from utils.llm import query_ollama
from utils.dm_interaction_analyzer import dm_analyzer
BEDTIME_TRACKING_FILE = "last_bedtime_targets.json"
async def send_monday_video_for_server(guild_id: int):
"""Send Monday video for a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
return
# No need to switch model - llama-swap handles this automatically
# Generate a motivational message
prompt = "It's Miku Monday! Give me an energetic and heartfelt Miku Monday morning message to inspire someone for the week ahead."
response = await query_ollama(prompt, user_id=f"weekly-motivation-{guild_id}", guild_id=guild_id)
video_url = "http://zip.koko210cloud.xyz/u/zEgU7Z.mp4"
# Use server-specific bedtime channels
target_channel_ids = server_config.bedtime_channel_ids
for channel_id in target_channel_ids:
channel = globals.client.get_channel(channel_id)
if channel is None:
print(f"❌ Could not find channel with ID {channel_id} in server {server_config.guild_name}")
continue
try:
await channel.send(content=response)
# Send video link
await channel.send(f"[Happy Miku Monday!]({video_url})")
print(f"✅ Sent Monday video to channel ID {channel_id} in server {server_config.guild_name}")
except Exception as e:
print(f"⚠️ Failed to send video to channel ID {channel_id} in server {server_config.guild_name}: {e}")
async def send_monday_video():
"""Legacy function - now sends to all servers"""
for guild_id in server_manager.servers:
await send_monday_video_for_server(guild_id)
def load_last_bedtime_targets():
if not os.path.exists(BEDTIME_TRACKING_FILE):
return {}
try:
with open(BEDTIME_TRACKING_FILE, "r") as f:
return json.load(f)
except Exception as e:
print(f"⚠️ Failed to load bedtime tracking file: {e}")
return {}
_last_bedtime_targets = load_last_bedtime_targets()
def save_last_bedtime_targets(data):
try:
with open(BEDTIME_TRACKING_FILE, "w") as f:
json.dump(data, f)
except Exception as e:
print(f"⚠️ Failed to save bedtime tracking file: {e}")
async def send_bedtime_reminder_for_server(guild_id: int, client=None):
"""Send bedtime reminder for a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
return
# Use provided client or fall back to globals.client
if client is None:
client = globals.client
if client is None:
print(f"⚠️ No Discord client available for bedtime reminder in server {guild_id}")
return
# No need to switch model - llama-swap handles this automatically
# Use server-specific bedtime channels
for channel_id in server_config.bedtime_channel_ids:
channel = client.get_channel(channel_id)
if not channel:
print(f"⚠️ Channel ID {channel_id} not found in server {server_config.guild_name}")
continue
guild = channel.guild
# Filter online members (excluding bots)
online_members = [
member for member in guild.members
if member.status in {Status.online, Status.idle, Status.dnd}
and not member.bot
]
specific_user_id = 214857593045254151 # target user ID
specific_user = guild.get_member(specific_user_id)
if specific_user and specific_user not in online_members:
online_members.append(specific_user)
if not online_members:
print(f"😴 No online members to ping in {guild.name}")
continue
# Avoid repeating the same person unless they're the only one
last_target_id = _last_bedtime_targets.get(str(guild.id))
eligible_members = [m for m in online_members if m.id != last_target_id]
if not eligible_members:
eligible_members = online_members # fallback if only one user
chosen_one = random.choice(eligible_members)
_last_bedtime_targets[str(guild.id)] = chosen_one.id
save_last_bedtime_targets(_last_bedtime_targets)
# 🎯 Status-aware phrasing
status_map = {
Status.online: "",
Status.idle: "Be sure to include the following information on their status too: Their profile status is currently idle. This implies they're not on their computer now, but are still awake.",
Status.dnd: "Be sure to include the following information on their status too: Their current profile status is 'Do Not Disturb.' This implies they are very absorbed in what they're doing. But it's still important for them to know when to stop for the day and get some sleep, right?",
Status.offline: "Be sure to include the following information on their status too: Their profile status is currently offline, but is it really? It's very likely they've just set it to invisible to avoid being seen that they're staying up so late!"
}
status_note = status_map.get(chosen_one.status, "")
# 🎮 Activity-aware phrasing
activity_note = ""
if chosen_one.activities:
for activity in chosen_one.activities:
if activity.type == ActivityType.playing:
activity_note = f"You should also include the following information on their current activity on their profile too: They are playing **{activity.name}** right now. It's getting late, though. Maybe it's time to pause, leave the rest of the game for tomorrow and rest..."
break
elif activity.type == ActivityType.streaming:
activity_note = f"You should also include the following information on their current activity on their profile too: They are streaming **{activity.name}** at this hour? They should know it's getting way too late for streams."
break
elif activity.type == ActivityType.watching:
activity_note = f"You should also include the following information on their current activity on their profile too: They are watching **{activity.name}** right now. That's cozy, but it's not good to binge so late."
break
elif activity.type == ActivityType.listening:
activity_note = f"You should also include the following information on their current activity on their profile too: They are listening to **{activity.name}** right now. Sounds like they're better off putting appropriate music to fall asleep to."
break
# Generate intelligent bedtime message
prompt = (
f"Write a sweet, funny, or encouraging bedtime message to remind someone it's getting late and they should sleep. "
f"Miku is currently feeling: {server_config.current_mood_description or 'neutral'}\nPlease word in a way that reflects this emotional tone."
)
bedtime_message = await query_ollama(prompt, user_id=f"bedtime-{guild_id}", guild_id=guild_id)
try:
await channel.send(f"{chosen_one.mention} {bedtime_message}")
print(f"🌙 Sent bedtime reminder to {chosen_one.display_name} in server {server_config.guild_name}")
except Exception as e:
print(f"⚠️ Failed to send bedtime reminder in server {server_config.guild_name}: {e}")
async def send_bedtime_reminder():
"""Legacy function - now sends to all servers"""
for guild_id in server_manager.servers:
await send_bedtime_reminder_for_server(guild_id, globals.client)
def schedule_random_bedtime():
"""Legacy function - now schedules for all servers"""
for guild_id in server_manager.servers:
# Schedule bedtime for each server using the async function
# This will be called from the server manager's event loop
print(f"⏰ Scheduling bedtime for server {guild_id}")
# Note: This function is now called from the server manager's context
# which properly handles the async operations
async def send_bedtime_now():
"""Send bedtime reminder immediately to all servers"""
for guild_id in server_manager.servers:
await send_bedtime_reminder_for_server(guild_id, globals.client)
async def run_daily_dm_analysis():
"""Run daily DM interaction analysis - reports one user per day"""
if dm_analyzer is None:
print("⚠️ DM Analyzer not initialized, skipping daily analysis")
return
print("📊 Running daily DM interaction analysis...")
await dm_analyzer.run_daily_analysis()