aboutsummaryrefslogtreecommitdiffstats
path: root/bot/cogs/reminders.py
diff options
context:
space:
mode:
authorGravatar Akarys42 <[email protected]>2019-09-23 16:16:10 +0200
committerGravatar Akarys42 <[email protected]>2019-09-23 16:16:10 +0200
commitf4f3b2a15ecdce5291ef2d2d98b0af6d77fbc228 (patch)
tree250036c9d5162c6ee62d1a7bd6c999a03a2caad5 /bot/cogs/reminders.py
parentChange log.error to log.exception (diff)
parentMerge 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.py328
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")