yor-discord-bot / cogs / reminder.py
reminder.py
Raw
"""This file contains the Reminder Discord Cog extension.
"""
from re import A
import discord
from discord.ext import commands
from asyncio import sleep
import time

from py_files.src.cog_utils.reminder_heap import ReminderHeap


class Reminder(commands.Cog):
    """Reminder provides functions related to reminding users.
    """
    def __init__(self, bot) -> None:
        """Constructs a Reminder object
        """
        self.bot = bot
        self.reminders = ReminderHeap()
        
    @commands.Cog.listener()
    async def on_ready(self):
        await self.update_reminder()
            
    async def update_reminder(self):
        while True:
            await sleep(5)
            while self.reminders.peek_reminder() is not None and\
                time.time() >= self.reminders.peek_reminder():
                ctx = self.reminders.pop_reminder()
                # remindme command
                if ctx.remind_self:
                    reply = f"Hey {ctx.author.mention}, don't forget"\
                            f'about this:\n```{ctx.reminder}```'
                    await ctx.reply(reply)
                # remind command
                else:
                    reply = f'Hey {ctx.member.mention}, {ctx.author.mention} '\
                            f'wanted you to remember about this:\n'\
                            f'```{ctx.reminder}```'
                    await ctx.reply(reply)
    
    @commands.command(name='remindme')
    async def remind_me(self, ctx: commands.Context) -> None:
        """Adds a reminder for the user.
        
        Args:
            ctx: A discord command Context.
            
        Returns:
            None.
        
        Example Usage:
        !remindme 1y2mo5d30min remind me to do something
        """
        err_msg = 'Please enter your reminder in the following format:\n'\
                  f'```{self.bot.command_prefix}remindme 1y2mo5d30min remind'\
                  'me to do something.```'\
                  'Where:\n```y: year, mo: month, d: days, min: minutes```'
        msg = ctx.message.content.split()
        if len(msg) <= 2:
            await ctx.send(err_msg)
            return
        ctx.remind_self = True
        epoch_time = await self._parse_str_time(ctx, msg[1], err_msg)
        # return if invalid str_time formatting
        if not epoch_time:
            return
        # join reminder request
        reminder = ' '.join(msg[2:])
        self.reminders.add_reminder(ctx, reminder, epoch_time)
        await ctx.message.add_reaction(self.bot.reactions['checkmark'])

    @commands.command(name='remind')
    async def remind(self, ctx: commands.Context) -> None:
        err_msg = 'Please enter your reminder in the following format:\n'\
                  f'```{self.bot.command_prefix}remind @user 1y2mo5d30min remind'\
                  'me to do something.```'\
                  'Where:\n```y: year, mo: month, d: days, min: minutes```'
        msg = ctx.message.content.split()
        if len(msg) <= 3:
            await ctx.send(err_msg)
            return
        try:
            member = await ctx.guild.fetch_member(msg[1][2:-1])
        except:
            await ctx.send('Cannot find user.')
            return
        ctx.remind_self = False
        ctx.member = member # TODO: not getting stored
        epoch_time = await self._parse_str_time(ctx, msg[2], err_msg)
        # return if invalid str_time formatting
        if not epoch_time:
            return
        # join reminder request
        reminder = ' '.join(msg[3:])
        self.reminders.add_reminder(ctx, reminder, epoch_time)
        await ctx.message.add_reaction(self.bot.reactions['checkmark'])

    @commands.command(name='myreminders')
    async def check_reminders(self, ctx: commands.Context) -> None:
        author_id = ctx.author.id
        reply = []
        reminders = self.reminders.check_reminder(author_id)
        if not reminders:
            await ctx.reply(f'You do not have any reminders, use the'
                      f'`{self.bot.command_prefix}remindme` command '
                      'to set reminders!')
            return
        for cnt, rem in enumerate(reminders):
            temp = [f'{cnt+1}.)']
            content = rem.reminder
            if len(content) > 37:
                content = f'{content[:38]}...'
            temp.append('{:<40}'.format(content))
            temp.append('in')
            temp.append(f'`{self._remaining_time(rem.epoch_time)}`')
            reply.append(' '.join(temp))
        # embed format
        title = f"{ctx.author}'s Reminders"
        desc = '\n'.join(reply)
        embed = discord.Embed(
            title=title,
            description=desc
            )
        embed.color = discord.Color.from_rgb(255, 161, 122)
        await ctx.send(embed=embed)
            
    def _remaining_time(self, epoch_time: float) -> str:
        """
        """
        total_min = (epoch_time - time.time()) / 60
        result = []
        # year
        if total_min >= 525960:
            yr = total_min // 525960
            total_min = total_min % 525960
            result.append(f'{int(yr)}yr')
        # month
        if total_min >= 43800:
            mo = total_min // 43800
            total_min = total_min % 43800
            result.append(f'{int(mo)}mo')
        # day
        if total_min >= 1440:
            day = total_min // 1440
            total_min = total_min % 1440
            result.append(f'{int(day)}day')
        # hour
        if total_min >= 60:
            hr = total_min // 60
            total_min = total_min % 60
            result.append(f'{int(hr)}hr')
        # minute
        result.append(f'{int(total_min)}min')
        return ' '.join(result)
        
    async def _parse_str_time(self, 
                          ctx: commands.Context, 
                          str_time: str, 
                          err_msg: str) -> float:
        """Parses a str str_time into an int.
        
        Args:
            ctx: A discord command Context.
            str_time: A str time.
            err_msg: An error message to send when format error occurs.
            
        Returns:
            A float time in epoch.
        """
        total_mins = 0
        str_time = str_time.lower()
        # year
        if 'yr' in str_time:
            try:
                ind = str_time.find('yr')
                yr = int(str_time[:ind])
                total_mins += 525960 * yr
                str_time = str_time[ind+2:]
            except:
                await ctx.send(err_msg)
                return
        # month
        if 'mo' in str_time:
            try:
                ind = str_time.find('mo')
                month = int(str_time[:ind])
                total_mins += 43800 * month
                str_time = str_time[ind+2:]
            except:
                await ctx.send(err_msg)
                return
        # day
        if 'd' in str_time:
            try:
                ind = str_time.find('d')
                day = int(str_time[:ind])
                total_mins += 1440 * day
                str_time = str_time[ind+1:]
            except:
                await ctx.send(err_msg)
                return
        # hour
        if 'hr' in str_time:
            try: 
                ind = str_time.find('hr')
                hour = int(str_time[:ind])
                total_mins += 60 * hour
                str_time = str_time[ind+2:]
            except:
                await ctx.send(err_msg)
                return
        # minute
        if 'min' in str_time:
            try:
                ind = str_time.find('min')
                minute = int(str_time[:ind])
                total_mins += minute
            except:
                await ctx.send(err_msg)
                return
        # non-valid str
        if total_mins == 0:
            await ctx.send(err_msg)
            return
        return total_mins * 60 + time.time()    
        
async def setup(bot):
    await bot.add_cog(Reminder(bot))