discordbot/cogs/timer_cog.py

151 lines
6.1 KiB
Python

import discord
from discord.ext import commands, tasks
from discord import app_commands
import json
import asyncio
from datetime import datetime, timedelta
import os
TIMER_FILE = 'data/timers.json'
class TimerCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.timers = []
self.load_timers()
self.timer_check_task.start()
def load_timers(self):
if not os.path.exists('data'):
os.makedirs('data')
if os.path.exists(TIMER_FILE):
with open(TIMER_FILE, 'r') as f:
try:
data = json.load(f)
# Convert string timestamps back to datetime objects
for timer_data in data:
timer_data['expires_at'] = datetime.fromisoformat(timer_data['expires_at'])
self.timers.append(timer_data)
except json.JSONDecodeError:
self.timers = []
else:
self.timers = []
print(f"Loaded {len(self.timers)} timers.")
def save_timers(self):
# Convert datetime objects to ISO format strings for JSON serialization
serializable_timers = []
for timer in self.timers:
timer_copy = timer.copy()
timer_copy['expires_at'] = timer_copy['expires_at'].isoformat()
serializable_timers.append(timer_copy)
with open(TIMER_FILE, 'w') as f:
json.dump(serializable_timers, f, indent=4)
print(f"Saved {len(self.timers)} timers.")
@tasks.loop(seconds=10) # Check every 10 seconds
async def timer_check_task(self):
now = datetime.now()
expired_timers = []
for timer in self.timers:
if timer['expires_at'] <= now:
expired_timers.append(timer)
for timer in expired_timers:
self.timers.remove(timer)
try:
channel = self.bot.get_channel(timer['channel_id'])
if channel:
user = self.bot.get_user(timer['user_id'])
if user:
message_content = f"{user.mention}, your timer for '{timer['message']}' has expired!"
if timer.get('ephemeral', True): # Default to True if not specified
# Ephemeral messages require interaction context, which we don't have here.
# For now, we'll send non-ephemeral if it was originally ephemeral.
# A better solution would be to store interaction context or use webhooks.
await channel.send(message_content)
else:
await channel.send(message_content)
else:
print(f"Could not find user {timer['user_id']} for timer.")
else:
print(f"Could not find channel {timer['channel_id']} for timer.")
except Exception as e:
print(f"Error sending timer message: {e}")
if expired_timers:
self.save_timers()
@timer_check_task.before_loop
async def before_timer_check_task(self):
await self.bot.wait_until_ready()
@app_commands.command(name="timer", description="Sets a timer, reminder, or alarm.")
@app_commands.describe(
time_str="Duration for the timer (e.g., 1h30m, 5m, 2d). Supports s, m, h, d.",
message="The message for your reminder.",
ephemeral="Whether the response should only be visible to you (defaults to True)."
)
async def timer_slash(self, interaction: discord.Interaction, time_str: str, message: str = "a reminder", ephemeral: bool = True):
"""
Sets a timer, reminder, or alarm as a slash command.
Usage: /timer time_str:1h30m message:Your reminder message ephemeral:False
Supports: s (seconds), m (minutes), h (hours), d (days)
"""
duration_seconds = 0
time_str = time_str.lower()
# Parse time string (e.g., 1h30m, 5m, 2d)
current_num = ""
for char in time_str:
if char.isdigit():
current_num += char
else:
if current_num:
num = int(current_num)
if char == 's':
duration_seconds += num
elif char == 'm':
duration_seconds += num * 60
elif char == 'h':
duration_seconds += num * 60 * 60
elif char == 'd':
duration_seconds += num * 60 * 60 * 24
else:
await interaction.response.send_message("Invalid time unit. Use s, m, h, or d.", ephemeral=ephemeral)
return
current_num = ""
else:
await interaction.response.send_message("Invalid time format. Example: `1h30m` or `5m`.", ephemeral=ephemeral)
return
if current_num: # Handle cases like "30s" without a unit at the end
await interaction.response.send_message("Invalid time format. Please specify a unit (s, m, h, d) for all numbers.", ephemeral=ephemeral)
return
if duration_seconds <= 0:
await interaction.response.send_message("Duration must be a positive value.", ephemeral=ephemeral)
return
expires_at = datetime.now() + timedelta(seconds=duration_seconds)
timer_data = {
'user_id': interaction.user.id,
'channel_id': interaction.channel_id,
'message': message,
'expires_at': expires_at,
'ephemeral': ephemeral
}
self.timers.append(timer_data)
self.save_timers()
await interaction.response.send_message(f"Timer set for {timedelta(seconds=duration_seconds)} from now for '{message}'.", ephemeral=ephemeral)
def cog_unload(self):
self.timer_check_task.cancel()
self.save_timers() # Ensure timers are saved on unload
async def setup(bot: commands.Bot):
await bot.add_cog(TimerCog(bot))