diff options
| author | 2019-09-23 16:16:10 +0200 | |
|---|---|---|
| committer | 2019-09-23 16:16:10 +0200 | |
| commit | f4f3b2a15ecdce5291ef2d2d98b0af6d77fbc228 (patch) | |
| tree | 250036c9d5162c6ee62d1a7bd6c999a03a2caad5 /bot/cogs/reminders.py | |
| parent | Change log.error to log.exception (diff) | |
| parent | Merge branch 'master' of https://github.com/python-discord/bot into python-di... (diff) | |
Merge branch 'python-discord-master'
Diffstat (limited to '')
| -rw-r--r-- | bot/cogs/reminders.py | 328 |
1 files changed, 104 insertions, 224 deletions
diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index e8177107b..8460de91f 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -1,22 +1,21 @@ import asyncio -import datetime import logging import random import textwrap +from datetime import datetime +from operator import itemgetter +from typing import Optional -from aiohttp import ClientResponseError from dateutil.relativedelta import relativedelta -from discord import Colour, Embed -from discord.ext.commands import Bot, Context, group +from discord import Colour, Embed, Message +from discord.ext.commands import Bot, Cog, Context, group -from bot.constants import ( - Channels, Icons, Keys, NEGATIVE_REPLIES, - POSITIVE_REPLIES, STAFF_ROLES, URLs -) +from bot.constants import Channels, Icons, NEGATIVE_REPLIES, POSITIVE_REPLIES, STAFF_ROLES +from bot.converters import ExpirationDate from bot.pagination import LinePaginator from bot.utils.checks import without_role_check from bot.utils.scheduling import Scheduler -from bot.utils.time import humanize_delta, parse_rfc1123, wait_until +from bot.utils.time import humanize_delta, wait_until log = logging.getLogger(__name__) @@ -24,28 +23,26 @@ WHITELISTED_CHANNELS = (Channels.bot,) MAXIMUM_REMINDERS = 5 -class Reminders(Scheduler): +class Reminders(Scheduler, Cog): + """Provide in-channel reminder functionality.""" def __init__(self, bot: Bot): self.bot = bot - self.headers = {"X-API-Key": Keys.site_api} super().__init__() - async def on_ready(self): - # Get all the current reminders for re-scheduling - response = await self.bot.http_session.get( - url=URLs.site_reminders_api, - headers=self.headers + @Cog.listener() + async def on_ready(self) -> None: + """Get all current reminders from the API and reschedule them.""" + response = await self.bot.api_client.get( + 'bot/reminders', + params={'active': 'true'} ) - response_data = await response.json() - - # Find the current time, timezone-aware. - now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) + now = datetime.utcnow() loop = asyncio.get_event_loop() - for reminder in response_data["reminders"]: - remind_at = parse_rfc1123(reminder["remind_at"]) + for reminder in response: + remind_at = datetime.fromisoformat(reminder['expiration'][:-1]) # If the reminder is already overdue ... if remind_at < now: @@ -56,43 +53,18 @@ class Reminders(Scheduler): self.schedule_task(loop, reminder["id"], reminder) @staticmethod - async def _send_confirmation(ctx: Context, response: dict, on_success: str): - """ - Send an embed confirming whether or not a change was made successfully. - - :return: A Boolean value indicating whether it failed (True) or passed (False) - """ - + async def _send_confirmation(ctx: Context, on_success: str) -> None: + """Send an embed confirming the reminder change was made successfully.""" embed = Embed() - - if not response.get("success"): - embed.colour = Colour.red() - embed.title = random.choice(NEGATIVE_REPLIES) - embed.description = response.get("error_message", "An unexpected error occurred.") - - log.warn(f"Unable to create/edit/delete a reminder. Response: {response}") - failed = True - - else: - embed.colour = Colour.green() - embed.title = random.choice(POSITIVE_REPLIES) - embed.description = on_success - - failed = False - + embed.colour = Colour.green() + embed.title = random.choice(POSITIVE_REPLIES) + embed.description = on_success await ctx.send(embed=embed) - return failed - - async def _scheduled_task(self, reminder: dict): - """ - A coroutine which sends the reminder once the time is reached. - - :param reminder: the data of the reminder. - :return: - """ + async def _scheduled_task(self, reminder: dict) -> None: + """A coroutine which sends the reminder once the time is reached, and cancels the running task.""" reminder_id = reminder["id"] - reminder_datetime = parse_rfc1123(reminder["remind_at"]) + reminder_datetime = datetime.fromisoformat(reminder['expiration'][:-1]) # Send the reminder message once the desired duration has passed await wait_until(reminder_datetime) @@ -104,51 +76,24 @@ class Reminders(Scheduler): # Now we can begone with it from our schedule list. self.cancel_task(reminder_id) - async def _delete_reminder(self, reminder_id: str): - """ - Delete a reminder from the database, given its ID. - - :param reminder_id: The ID of the reminder. - """ - - # The API requires a list, so let's give it one :) - json_data = { - "reminders": [ - reminder_id - ] - } - - await self.bot.http_session.delete( - url=URLs.site_reminders_api, - headers=self.headers, - json=json_data - ) + async def _delete_reminder(self, reminder_id: str) -> None: + """Delete a reminder from the database, given its ID, and cancel the running task.""" + await self.bot.api_client.delete('bot/reminders/' + str(reminder_id)) # Now we can remove it from the schedule list self.cancel_task(reminder_id) - async def _reschedule_reminder(self, reminder): - """ - Reschedule a reminder object. - - :param reminder: The reminder to be rescheduled. - """ - + async def _reschedule_reminder(self, reminder: dict) -> None: + """Reschedule a reminder object.""" loop = asyncio.get_event_loop() self.cancel_task(reminder["id"]) self.schedule_task(loop, reminder["id"], reminder) - async def send_reminder(self, reminder, late: relativedelta = None): - """ - Send the reminder. - - :param reminder: The data about the reminder. - :param late: How late the reminder is (if at all) - """ - - channel = self.bot.get_channel(int(reminder["channel_id"])) - user = self.bot.get_user(int(reminder["user_id"])) + async def send_reminder(self, reminder: dict, late: relativedelta = None) -> None: + """Send the reminder.""" + channel = self.bot.get_channel(reminder["channel_id"]) + user = self.bot.get_user(reminder["author"]) embed = Embed() embed.colour = Colour.blurple() @@ -173,19 +118,17 @@ class Reminders(Scheduler): await self._delete_reminder(reminder["id"]) @group(name="remind", aliases=("reminder", "reminders"), invoke_without_command=True) - async def remind_group(self, ctx: Context, duration: str, *, content: str): - """ - Commands for managing your reminders. - """ - - await ctx.invoke(self.new_reminder, duration=duration, content=content) + async def remind_group(self, ctx: Context, expiration: ExpirationDate, *, content: str) -> None: + """Commands for managing your reminders.""" + await ctx.invoke(self.new_reminder, expiration=expiration, content=content) @remind_group.command(name="new", aliases=("add", "create")) - async def new_reminder(self, ctx: Context, duration: str, *, content: str): + async def new_reminder(self, ctx: Context, expiration: ExpirationDate, *, content: str) -> Optional[Message]: """ Set yourself a simple reminder. - """ + Expiration is parsed per: http://strftime.org/ + """ embed = Embed() # If the user is not staff, we need to verify whether or not to make a reminder at all. @@ -200,13 +143,13 @@ class Reminders(Scheduler): return await ctx.send(embed=embed) # Get their current active reminders - response = await self.bot.http_session.get( - url=URLs.site_reminders_user_api.format(user_id=ctx.author.id), - headers=self.headers + active_reminders = await self.bot.api_client.get( + 'bot/reminders', + params={ + 'user__id': str(ctx.author.id) + } ) - active_reminders = await response.json() - # Let's limit this, so we don't get 10 000 # reminders from kip or something like that :P if len(active_reminders) > MAXIMUM_REMINDERS: @@ -217,78 +160,53 @@ class Reminders(Scheduler): return await ctx.send(embed=embed) # Now we can attempt to actually set the reminder. - try: - response = await self.bot.http_session.post( - url=URLs.site_reminders_api, - headers=self.headers, - json={ - "user_id": str(ctx.author.id), - "duration": duration, - "content": content, - "channel_id": str(ctx.channel.id) - } - ) - - response_data = await response.json() - - # AFAIK only happens if the user enters, like, a quintillion weeks - except ClientResponseError: - embed.colour = Colour.red() - embed.title = random.choice(NEGATIVE_REPLIES) - embed.description = ( - "An error occurred while adding your reminder to the database. " - "Did you enter a reasonable duration?" - ) - - log.warn(f"User {ctx.author} attempted to create a reminder for {duration}, but failed.") - - return await ctx.send(embed=embed) - - # Confirm to the user whether or not it worked. - failed = await self._send_confirmation( - ctx, response_data, - on_success="Your reminder has been created successfully!" + reminder = await self.bot.api_client.post( + 'bot/reminders', + json={ + 'author': ctx.author.id, + 'channel_id': ctx.message.channel.id, + 'content': content, + 'expiration': expiration.isoformat() + } ) - # If it worked, schedule the reminder. - if not failed: - loop = asyncio.get_event_loop() - reminder = response_data["reminder"] + # Confirm to the user that it worked. + await self._send_confirmation( + ctx, on_success="Your reminder has been created successfully!" + ) - self.schedule_task(loop, reminder["id"], reminder) + loop = asyncio.get_event_loop() + self.schedule_task(loop, reminder["id"], reminder) @remind_group.command(name="list") - async def list_reminders(self, ctx: Context): - """ - View a paginated embed of all reminders for your user. - """ - + async def list_reminders(self, ctx: Context) -> Optional[Message]: + """View a paginated embed of all reminders for your user.""" # Get all the user's reminders from the database. - response = await self.bot.http_session.get( - url=URLs.site_reminders_user_api, - params={"user_id": str(ctx.author.id)}, - headers=self.headers + data = await self.bot.api_client.get( + 'bot/reminders', + params={'user__id': str(ctx.author.id)} ) - data = await response.json() - now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) + now = datetime.utcnow() # Make a list of tuples so it can be sorted by time. - reminders = [ - (rem["content"], rem["remind_at"], rem["friendly_id"]) for rem in data["reminders"] - ] - - reminders.sort(key=lambda rem: rem[1]) + reminders = sorted( + ( + (rem['content'], rem['expiration'], rem['id']) + for rem in data + ), + key=itemgetter(1) + ) lines = [] - for index, (content, remind_at, friendly_id) in enumerate(reminders): + for content, remind_at, id_ in reminders: # Parse and humanize the time, make it pretty :D - remind_datetime = parse_rfc1123(remind_at) + remind_datetime = datetime.fromisoformat(remind_at[:-1]) time = humanize_delta(relativedelta(remind_datetime, now)) text = textwrap.dedent(f""" - **Reminder #{index}:** *expires in {time}* (ID: {friendly_id}) + **Reminder #{id_}:** *expires in {time}* (ID: {id_}) {content} """).strip() @@ -314,93 +232,55 @@ class Reminders(Scheduler): ) @remind_group.group(name="edit", aliases=("change", "modify"), invoke_without_command=True) - async def edit_reminder_group(self, ctx: Context): - """ - Commands for modifying your current reminders. - """ - + async def edit_reminder_group(self, ctx: Context) -> None: + """Commands for modifying your current reminders.""" await ctx.invoke(self.bot.get_command("help"), "reminders", "edit") @edit_reminder_group.command(name="duration", aliases=("time",)) - async def edit_reminder_duration(self, ctx: Context, friendly_id: str, duration: str): - """ - Edit one of your reminders' duration. + async def edit_reminder_duration(self, ctx: Context, id_: int, expiration: ExpirationDate) -> None: """ + Edit one of your reminder's expiration. + Expiration is parsed per: http://strftime.org/ + """ # Send the request to update the reminder in the database - response = await self.bot.http_session.patch( - url=URLs.site_reminders_user_api, - headers=self.headers, - json={ - "user_id": str(ctx.author.id), - "friendly_id": friendly_id, - "duration": duration - } + reminder = await self.bot.api_client.patch( + 'bot/reminders/' + str(id_), + json={'expiration': expiration.isoformat()} ) # Send a confirmation message to the channel - response_data = await response.json() - failed = await self._send_confirmation( - ctx, response_data, - on_success="That reminder has been edited successfully!" + await self._send_confirmation( + ctx, on_success="That reminder has been edited successfully!" ) - if not failed: - await self._reschedule_reminder(response_data["reminder"]) + await self._reschedule_reminder(reminder) @edit_reminder_group.command(name="content", aliases=("reason",)) - async def edit_reminder_content(self, ctx: Context, friendly_id: str, *, content: str): - """ - Edit one of your reminders' content. - """ - + async def edit_reminder_content(self, ctx: Context, id_: int, *, content: str) -> None: + """Edit one of your reminder's content.""" # Send the request to update the reminder in the database - response = await self.bot.http_session.patch( - url=URLs.site_reminders_user_api, - headers=self.headers, - json={ - "user_id": str(ctx.author.id), - "friendly_id": friendly_id, - "content": content - } + reminder = await self.bot.api_client.patch( + 'bot/reminders/' + str(id_), + json={'content': content} ) # Send a confirmation message to the channel - response_data = await response.json() - failed = await self._send_confirmation( - ctx, response_data, - on_success="That reminder has been edited successfully!" + await self._send_confirmation( + ctx, on_success="That reminder has been edited successfully!" ) - - if not failed: - await self._reschedule_reminder(response_data["reminder"]) + await self._reschedule_reminder(reminder) @remind_group.command("delete", aliases=("remove",)) - async def delete_reminder(self, ctx: Context, friendly_id: str): - """ - Delete one of your active reminders. - """ - - # Send the request to delete the reminder from the database - response = await self.bot.http_session.delete( - url=URLs.site_reminders_user_api, - headers=self.headers, - json={ - "user_id": str(ctx.author.id), - "friendly_id": friendly_id - } - ) - - response_data = await response.json() - failed = await self._send_confirmation( - ctx, response_data, - on_success="That reminder has been deleted successfully!" + async def delete_reminder(self, ctx: Context, id_: int) -> None: + """Delete one of your active reminders.""" + await self._delete_reminder(id_) + await self._send_confirmation( + ctx, on_success="That reminder has been deleted successfully!" ) - if not failed: - await self._delete_reminder(response_data["reminder_id"]) - -def setup(bot: Bot): +def setup(bot: Bot) -> None: + """Reminders cog load.""" bot.add_cog(Reminders(bot)) log.info("Cog loaded: Reminders") |