discordbot/cogs/timer_cog.py
2025-06-05 21:31:06 -06:00

176 lines
6.5 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))