151 lines
6.1 KiB
Python
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):
|
|
await bot.add_cog(TimerCog(bot))
|