Skip to content

Commit

Permalink
Update reminder to be an app command + support french prompt + option…
Browse files Browse the repository at this point in the history
…ally give date & time
  • Loading branch information
SonOfLope committed Feb 4, 2025
1 parent b4d7385 commit 7157367
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 77 deletions.
121 changes: 70 additions & 51 deletions cogs/admin/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
from cogs.admin import is_authorized
import asyncio
import random
import re
import datetime
from datetime import timezone, timedelta
import pytz

eastern = pytz.timezone('America/Toronto')

def _create_server_management_cmds(trema_bot, trema_db):

Expand Down Expand Up @@ -227,74 +233,87 @@ async def pick_winner():
asyncio.create_task(pick_winner())
await ctx.author.send('Tirage au sort organisé avec succès.')

@trema_bot.command(name="remindme", description="Set a reminder for a message")
@trema_bot.message_command(name="Set Reminder")
@is_authorized(trema_db)
async def remindme(ctx, message_link: Option(str, "Lien du message à rappeler"), delay: Option(str, "Délai avant le rappel. Ex: '1 week', '3 hours', '4 days'")):
import re
from datetime import datetime, timezone, timedelta
async def remindme(ctx, message: discord.Message):
"""
Context menu command to set a reminder for the selected message.
"""
await ctx.respond("Veuillez vérifier vos messages privés pour des instructions supplémentaires.", ephemeral=True)

def parse_delay(input_str):
try:
parsed_date = datetime.datetime.strptime(input_str, "%Y-%m-%d %H:%M:%S")
local_dt = eastern.localize(parsed_date)
return local_dt.astimezone(timezone.utc)
except ValueError:
pass

def parse_delay(delay_str):
units = {
'second': 1, 'seconds': 1,
'minute': 60, 'minutes': 60,
'hour': 3600, 'hours': 3600,
# EN
'second': 1, 'seconds': 1, 'sec': 1, 'secs': 1,
'minute': 60, 'minutes': 60, 'min': 60, 'mins': 60,
'hour': 3600, 'hours': 3600, 'hr': 3600, 'hrs': 3600,
'day': 86400, 'days': 86400,
'week': 604800, 'weeks': 604800
'week': 604800, 'weeks': 604800,
# FR
'seconde': 1, 'secondes': 1, 'sec': 1, 'secs': 1,
'heure': 3600, 'heures': 3600, 'h': 3600,
'jour': 86400, 'jours': 86400,
'semaine': 604800, 'semaines': 604800, 'sem': 604800
}
pattern = r'(\d+)\s*(second|seconds|minute|minutes|hour|hours|day|days|week|weeks)'
match = re.match(pattern, delay_str.lower())
if not match:
return None
amount, unit = match.groups()
return int(amount) * units[unit]

message_link_regex = r"https?://discord(?:app)?\.com/channels/(\d+)/(\d+)/(\d+)"
match = re.match(message_link_regex, message_link)
if not match:
await ctx.respond("Lien du message invalide.", ephemeral=True)
return

guild_id, channel_id, message_id = match.groups()
pattern = r'(\d+)\s*(second|seconds|sec|secs|minute|minutes|min|mins|hour|hours|hr|hrs|day|days|week|weeks|seconde|secondes|heure|heures|h|jour|jours|semaine|semaines|sem)'

guild = ctx.guild
if str(guild.id) != guild_id:
await ctx.respond("Le lien du message doit être dans ce serveur.", ephemeral=True)
return
match = re.match(pattern, input_str.lower())
if match:
amount, unit = match.groups()
delay_seconds = int(amount) * units[unit]
return datetime.datetime.now(timezone.utc)+ timedelta(seconds=delay_seconds)

channel = guild.get_channel(int(channel_id))
if not channel:
await ctx.respond("Le canal du message n'a pas été trouvé.", ephemeral=True)
return
return None

try:
message = await channel.fetch_message(int(message_id))
except discord.NotFound:
await ctx.respond("Message introuvable.", ephemeral=True)
reminder_input = await prompt_user(ctx, "Entrez le délai avant le rappel. Ex: '30 minutes', '1 week/semaine', '3 hours/heures'. Vous pouvez aussi entrer une date et heure précises (AAAA-MM-JJ HH:MM:SS).", 'Délai')
if not reminder_input:
await ctx.respond("Commande annulée : Aucun délai fourni.", ephemeral=True)
return

delay_seconds = parse_delay(delay)
if delay_seconds is None:
await ctx.respond("Format de délai invalide. Utilisez par exemple '1 week', '3 hours', '4 days'.", ephemeral=True)
scheduled_time = parse_delay(reminder_input)
if scheduled_time is None:
await ctx.respond("Format de délai invalide. Utilisez par exemple '30 minutes', '1 week', '3 hours' ou une date et heure précises (AAAA-MM-JJ HH:MM:SS).", ephemeral=True)
return

scheduled_time = datetime.now(timezone.utc) + timedelta(seconds=delay_seconds)

confirmation_message = await ctx.channel.send(
f"Rappel programmé pour [ce message]({message_link}) dans {delay}. Réagissez avec ✅ pour vous abonner."
)

await confirmation_message.add_reaction('✅')
public = await prompt_user_with_confirmation(ctx, "Le rappel sera-t-il public ? (True/False)")
if public is None:
await ctx.respond("Commande annulée : Choix public/privé non fourni.", ephemeral=True)
return

await ctx.respond(f"Rappel programmé pour le message: {message.jump_url} dans {delay}.", ephemeral=True)

reminder_data = {
'guild_id': guild.id,
'confirmation_channel_id': ctx.channel.id,
'confirmation_message_id': confirmation_message.id,
'guild_id': ctx.guild.id,
'confirmation_channel_id': ctx.channel.id if public else None,
'confirmation_message_id': None,
'scheduled_time': scheduled_time.timestamp(),
'message_link': message_link,
'message_link': message.jump_url,
'creator_id': ctx.author.id,
'status': 'pending',
'public': public
}
trema_db.add_reminder(reminder_data)

asyncio.create_task(send_reminder(reminder_data, trema_bot, trema_db))
reminder_id = trema_db.add_reminder(reminder_data)

formatted_time = scheduled_time.astimezone(eastern).strftime('%Y-%m-%d %H:%M:%S %Z')
if public:
confirmation_message = await ctx.channel.send(
f"Rappel public programmé pour [ce message]({message.jump_url}) {formatted_time}. Réagissez avec ✅ pour vous abonner."
)

await confirmation_message.add_reaction('✅')
trema_db.update_reminder_confirmation_message(reminder_id, confirmation_message.id)

else:
await ctx.author.send(
f"Rappel privé programmé pour [ce message]({message.jump_url}) {formatted_time}."
)

asyncio.create_task(send_reminder(reminder_id, trema_bot, trema_db))
2 changes: 1 addition & 1 deletion cogs/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,5 @@ async def on_ready():
reminders = trema_db.get_pending_reminders()

for reminder in reminders:
asyncio.create_task(send_reminder(reminder, trema_bot, trema_db))
asyncio.create_task(send_reminder(reminder['_id'], trema_bot, trema_db))

81 changes: 60 additions & 21 deletions cogs/utils/discord.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import asyncio
import discord
import os
from datetime import datetime, timezone, timedelta


_ROLE_EVERYONE = "@everyone"

Expand Down Expand Up @@ -106,38 +108,75 @@ async def send_delayed_dm(user, message, delay, condition=None, message_type='te
else:
await user.send(message)

async def send_reminder(reminder_data, trema_bot, trema_db):
async def send_reminder(reminder_id, trema_bot, trema_db):
try:
from datetime import datetime
scheduled_time = datetime.fromtimestamp(reminder_data['scheduled_time'])
now = datetime.now()
reminder_data = trema_db.get_reminder(reminder_id)
if not reminder_data:
print(f"Erreur: Rappel {reminder_id} introuvable.")
return

scheduled_time = datetime.fromtimestamp(reminder_data['scheduled_time'], timezone.utc)
now = datetime.now(timezone.utc)
delay_seconds = (scheduled_time - now).total_seconds()
if delay_seconds > 0:
await asyncio.sleep(delay_seconds)
while delay_seconds > 3600:
sleep_time = delay_seconds * 2 / 3
await asyncio.sleep(sleep_time)

now_utc = datetime.now(timezone.utc)
delay_seconds = (scheduled_time - now_utc).total_seconds()

if delay_seconds > 0:
await asyncio.sleep(delay_seconds)

current_status = trema_db.get_reminder_status(reminder_id)
if current_status != 'pending':
return
trema_db.update_reminder_status(reminder_data['_id'], 'sent')

guild = trema_bot.get_guild(reminder_data['guild_id'])
if guild is None:
return
confirmation_channel = guild.get_channel(reminder_data['confirmation_channel_id'])
if confirmation_channel is None:
return
confirmation_message = await confirmation_channel.fetch_message(reminder_data['confirmation_message_id'])
if confirmation_message is None:
return

reaction = discord.utils.get(confirmation_message.reactions, emoji='✅')
if reaction:
users = await reaction.users().flatten()
users = [user for user in users if user.id != trema_bot.user.id]
for user in users:
creator = await trema_bot.fetch_user(reminder_data['creator_id'])
public = reminder_data.get('public', True)

if public:
confirmation_channel = guild.get_channel(reminder_data['confirmation_channel_id'])
if confirmation_channel is None:
try:
await user.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
await creator.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
except discord.Forbidden:
pass
return

# send to creator of reminder
creator = await trema_bot.fetch_user(reminder_data['creator_id'])
await creator.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
trema_db.update_reminder_status(reminder_data, 'sent')
confirmation_message = await confirmation_channel.fetch_message(reminder_data['confirmation_message_id'])
if confirmation_message is None:
try:
await creator.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
except discord.Forbidden:
pass
return

reaction = discord.utils.get(confirmation_message.reactions, emoji='✅')
if reaction:
users = await reaction.users().flatten()
users = [user for user in users if user.id != trema_bot.user.id]

for user in users:
try:
await user.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
except discord.Forbidden:
pass
try:
await creator.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
except discord.Forbidden:
pass
else:
try:
await creator.send(f"Voici votre rappel pour le message: {reminder_data['message_link']}")
except discord.Forbidden:
pass
except Exception as e:
print(f"Erreur lors de l'envoi du rappel: {e}")

Expand Down
18 changes: 18 additions & 0 deletions db/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,13 +468,31 @@ def add_reminder(self, reminder):
reminder["_id"] = self.generate_rand_id("reminders")
reminder["status"] = "pending"
self.add_document("reminders", reminder)
return reminder["_id"]

def update_reminder_status(self, reminder_id, status):
reminders_collection = self._get_collection("reminders")
query = {"_id": reminder_id}
update = {"$set": {"status": status}}
reminders_collection.update_one(query, update)

def get_reminder_status(self, reminder_id):
reminders_collection = self._get_collection("reminders")
reminder = reminders_collection.find_one({"_id": reminder_id})
if reminder is None:
return None
return reminder.get("status")

def update_reminder_confirmation_message(self, reminder_id, message_id):
reminders_collection = self._get_collection("reminders")
query = {"_id": reminder_id}
update = {"$set": {"confirmation_message_id": message_id}}
reminders_collection.update_one(query, update)

def get_reminder(self, reminder_id):
reminders_collection = self._get_collection("reminders")
return reminders_collection.find_one({"_id": reminder_id})

mongo_user = os.getenv('MONGO_USER')
mongo_password = os.getenv('MONGO_PASSWORD')
mongo_host = os.getenv('MONGO_HOST')
Expand Down
9 changes: 5 additions & 4 deletions db/schemas/reminders.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
"guild_id": {"type": "number"},
"scheduled_time": {"type": "number"},
"status": {"type": "string", "enum": ["pending", "sent", "cancelled"]},
"confirmation_message_id": {"type": "number"},
"confirmation_channel_id": {"type": "number"},
"confirmation_message_id": {"type": ["number", "null"]},
"confirmation_channel_id": {"type": ["number", "null"]},
"message_link": {"type": "string"},
"creator_id": {"type": "number"}
"creator_id": {"type": "number"},
"public": {"type": "boolean"}
},
"required": ["_id", "guild_id", "scheduled_time", "status", "confirmation_message_id", "confirmation_channel_id", "message_link"]
"required": ["_id", "guild_id", "scheduled_time", "status", "confirmation_channel_id", "message_link"]
}
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ services:
networks:
- app_network

mongoClient:
image: mongoclient/mongoclient:latest
container_name: mongoClient
networks:
- app_network
ports:
- 3300:3000
volumes:
- ./mongoClient:/home/mongoClient/config
depends_on:
- mongodb

networks:
app_network:
driver: bridge
Expand Down

0 comments on commit 7157367

Please sign in to comment.