aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/cogs/reminders.py143
-rw-r--r--bot/utils/messages.py16
2 files changed, 82 insertions, 77 deletions
diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py
index 5f76164cd..b5998cc0e 100644
--- a/bot/cogs/reminders.py
+++ b/bot/cogs/reminders.py
@@ -9,14 +9,14 @@ from operator import itemgetter
import discord
from dateutil.parser import isoparse
from dateutil.relativedelta import relativedelta
-from discord import Member, Role
from discord.ext.commands import Cog, Context, Greedy, group
from bot.bot import Bot
-from bot.constants import Guild, Icons, MODERATION_ROLES, NEGATIVE_REPLIES, POSITIVE_REPLIES, STAFF_ROLES
+from bot.constants import Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, STAFF_ROLES
from bot.converters import Duration
from bot.pagination import LinePaginator
from bot.utils.checks import without_role_check
+from bot.utils.messages import send_denial
from bot.utils.scheduling import Scheduler
from bot.utils.time import humanize_delta
@@ -25,7 +25,7 @@ log = logging.getLogger(__name__)
WHITELISTED_CHANNELS = Guild.reminder_whitelist
MAXIMUM_REMINDERS = 5
-Mentionable = t.Union[Member, Role]
+Mentionable = t.Union[discord.Member, discord.Role]
class Reminders(Cog):
@@ -58,7 +58,6 @@ class Reminders(Cog):
if remind_at < now:
late = relativedelta(now, remind_at)
await self.send_reminder(reminder, late)
- await self._delete_reminder(reminder["id"])
else:
self.schedule_reminder(reminder)
@@ -104,17 +103,7 @@ class Reminders(Cog):
await ctx.send(embed=embed)
@staticmethod
- async def _send_denial(ctx: Context, reason: str) -> None:
- """Send an embed denying the user from creating a reminder."""
- embed = discord.Embed()
- embed.colour = discord.Colour.red()
- embed.title = random.choice(NEGATIVE_REPLIES)
- embed.description = reason
-
- await ctx.send(embed=embed)
-
- @staticmethod
- async def allow_mentions(ctx: Context, mentions: t.List[Mentionable]) -> t.Tuple[bool, str]:
+ async def _check_mentions(ctx: Context, mentions: t.Iterable[Mentionable]) -> t.Tuple[bool, str]:
"""
Returns whether or not the list of mentions is allowed.
@@ -127,11 +116,26 @@ class Reminders(Cog):
if without_role_check(ctx, *STAFF_ROLES):
return False, "members/roles"
elif without_role_check(ctx, *MODERATION_ROLES):
- return all(isinstance(mention, Member) for mention in mentions), "roles"
+ return all(isinstance(mention, discord.Member) for mention in mentions), "roles"
else:
return True, ""
- def get_mentionables_from_ids(self, mention_ids: t.List[str]) -> t.Iterator[Mentionable]:
+ @staticmethod
+ async def validate_mentions(ctx: Context, mentions: t.Iterable[Mentionable]) -> bool:
+ """
+ Filter mentions to see if the user can mention, and sends a denial if not allowed.
+
+ Returns whether or not the validation is successful.
+ """
+ mentions_allowed, disallowed_mentions = await Reminders._check_mentions(ctx, mentions)
+
+ if not mentions or mentions_allowed:
+ return True
+ else:
+ await send_denial(ctx, f"You can't mention other {disallowed_mentions} in your reminder!")
+ return False
+
+ def get_mentionables(self, mention_ids: t.List[int]) -> t.Iterator[Mentionable]:
"""Converts Role and Member ids to their corresponding objects if possible."""
guild = self.bot.get_guild(Guild.id)
for mention_id in mention_ids:
@@ -159,6 +163,19 @@ class Reminders(Cog):
# Now we can remove it from the schedule list
self.scheduler.cancel(reminder_id)
+ async def _edit_reminder(self, reminder_id: int, payload: dict) -> dict:
+ """
+ Edits a reminder in the database given the ID and payload.
+
+ Returns the edited reminder.
+ """
+ # Send the request to update the reminder in the database
+ reminder = await self.bot.api_client.patch(
+ 'bot/reminders/' + str(reminder_id),
+ json=payload
+ )
+ return reminder
+
async def _reschedule_reminder(self, reminder: dict) -> None:
"""Reschedule a reminder object."""
log.trace(f"Cancelling old task #{reminder['id']}")
@@ -193,13 +210,14 @@ class Reminders(Cog):
)
additional_mentions = ' '.join(
- mentionable.mention for mentionable in self.get_mentionables_from_ids(reminder["mentions"])
+ mentionable.mention for mentionable in self.get_mentionables(reminder["mentions"])
)
await channel.send(
content=f"{user.mention} {additional_mentions}",
embed=embed
)
+ await self._delete_reminder(reminder["id"])
@group(name="remind", aliases=("reminder", "reminders", "remindme"), invoke_without_command=True)
async def remind_group(
@@ -211,7 +229,7 @@ class Reminders(Cog):
@remind_group.command(name="new", aliases=("add", "create"))
async def new_reminder(
self, ctx: Context, mentions: Greedy[Mentionable], expiration: Duration, *, content: str
- ) -> t.Optional[discord.Message]:
+ ) -> None:
"""
Set yourself a simple reminder.
@@ -222,7 +240,8 @@ class Reminders(Cog):
# If they don't have permission to set a reminder in this channel
if ctx.channel.id not in WHITELISTED_CHANNELS:
- return await self._send_denial(ctx, "Sorry, you can't do that here!")
+ await send_denial(ctx, "Sorry, you can't do that here!")
+ return
# Get their current active reminders
active_reminders = await self.bot.api_client.get(
@@ -235,15 +254,16 @@ class Reminders(Cog):
# 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:
- return await self._send_denial(ctx, "You have too many active reminders!")
+ await send_denial(ctx, "You have too many active reminders!")
+ return
+
+ # Remove duplicate mentions
+ mentions = set(mentions)
+ mentions.discard(ctx.author)
# Filter mentions to see if the user can mention members/roles
- if mentions:
- mentions_allowed, disallowed_mentions = await self.allow_mentions(ctx, mentions)
- if not mentions_allowed:
- return await self._send_denial(
- ctx, f"You can't mention other {disallowed_mentions} in your reminder!"
- )
+ if not await self.validate_mentions(ctx, mentions):
+ return
mention_ids = [mention.id for mention in mentions]
@@ -278,7 +298,7 @@ class Reminders(Cog):
self.schedule_reminder(reminder)
@remind_group.command(name="list")
- async def list_reminders(self, ctx: Context) -> t.Optional[discord.Message]:
+ async def list_reminders(self, ctx: Context) -> None:
"""View a paginated embed of all reminders for your user."""
# Get all the user's reminders from the database.
data = await self.bot.api_client.get(
@@ -306,12 +326,12 @@ class Reminders(Cog):
mentions = ", ".join(
# Both Role and User objects have the `name` attribute
- mention.name for mention in self.get_mentionables_from_ids(mentions)
+ mention.name for mention in self.get_mentionables(mentions)
)
mention_string = f"\n**Mentions:** {mentions}" if mentions else ""
text = textwrap.dedent(f"""
- **Reminder #{id_}:** *expires in {time}* (ID: {id_}) {mention_string}
+ **Reminder #{id_}:** *expires in {time}* (ID: {id_}){mention_string}
{content}
""").strip()
@@ -324,7 +344,8 @@ class Reminders(Cog):
# Remind the user that they have no reminders :^)
if not lines:
embed.description = "No active reminders could be found."
- return await ctx.send(embed=embed)
+ await ctx.send(embed=embed)
+ return
# Construct the embed and paginate it.
embed.colour = discord.Colour.blurple()
@@ -344,65 +365,37 @@ class Reminders(Cog):
@edit_reminder_group.command(name="duration", aliases=("time",))
async def edit_reminder_duration(self, ctx: Context, id_: int, expiration: Duration) -> None:
"""
- Edit one of your reminder's expiration.
+ Edit one of your reminder's expiration.
Expiration is parsed per: http://strftime.org/
"""
- # Send the request to update the reminder in the database
- reminder = await self.bot.api_client.patch(
- 'bot/reminders/' + str(id_),
- json={'expiration': expiration.isoformat()}
- )
-
- # Send a confirmation message to the channel
- await self._send_confirmation(
- ctx,
- on_success="That reminder has been edited successfully!",
- reminder_id=id_,
- delivery_dt=expiration,
- )
-
- await self._reschedule_reminder(reminder)
+ await self.edit_reminder(ctx, id_, {'expiration': expiration.isoformat()})
@edit_reminder_group.command(name="content", aliases=("reason",))
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
- reminder = await self.bot.api_client.patch(
- 'bot/reminders/' + str(id_),
- json={'content': content}
- )
-
- # Parse the reminder expiration back into a datetime for the confirmation message
- expiration = isoparse(reminder['expiration']).replace(tzinfo=None)
-
- # Send a confirmation message to the channel
- await self._send_confirmation(
- ctx,
- on_success="That reminder has been edited successfully!",
- reminder_id=id_,
- delivery_dt=expiration,
- )
- await self._reschedule_reminder(reminder)
+ await self.edit_reminder(ctx, id_, {"content": content})
@edit_reminder_group.command(name="mentions", aliases=("pings",))
async def edit_reminder_mentions(self, ctx: Context, id_: int, mentions: Greedy[Mentionable]) -> None:
"""Edit one of your reminder's mentions."""
+ # Remove duplicate mentions
+ mentions = set(mentions)
+ mentions.discard(ctx.author)
+
# Filter mentions to see if the user can mention members/roles
- mentions_allowed, disallowed_mentions = await self.allow_mentions(ctx, mentions)
- if not mentions_allowed:
- return await self._send_denial(
- ctx, f"You can't mention other {disallowed_mentions} in your reminder!"
- )
+ if not await self.validate_mentions(ctx, mentions):
+ return
mention_ids = [mention.id for mention in mentions]
- reminder = await self.bot.api_client.patch(
- 'bot/reminders/' + str(id_),
- json={"mentions": mention_ids}
- )
+ await self.edit_reminder(ctx, id_, {"mentions": mention_ids})
+
+ async def edit_reminder(self, ctx: Context, id_: int, payload: dict) -> None:
+ """Edits a reminder with the given payload, then sends a confirmation message."""
+ reminder = await self._edit_reminder(id_, payload)
- # Parse the reminder expiration back into a datetime for the confirmation message
- expiration = isoparse(reminder['expiration']).replace(tzinfo=None)
+ # Parse the reminder expiration back into a datetime
+ expiration = isoparse(reminder["expiration"]).replace(tzinfo=None)
# Send a confirmation message to the channel
await self._send_confirmation(
diff --git a/bot/utils/messages.py b/bot/utils/messages.py
index a40a12e98..670289941 100644
--- a/bot/utils/messages.py
+++ b/bot/utils/messages.py
@@ -1,15 +1,17 @@
import asyncio
import contextlib
import logging
+import random
import re
from io import BytesIO
from typing import List, Optional, Sequence, Union
-from discord import Client, Embed, File, Member, Message, Reaction, TextChannel, Webhook
+from discord import Client, Colour, Embed, File, Member, Message, Reaction, TextChannel, Webhook
from discord.abc import Snowflake
from discord.errors import HTTPException
+from discord.ext.commands import Context
-from bot.constants import Emojis
+from bot.constants import Emojis, NEGATIVE_REPLIES
log = logging.getLogger(__name__)
@@ -132,3 +134,13 @@ def sub_clyde(username: Optional[str]) -> Optional[str]:
return re.sub(r"(clyd)(e)", replace_e, username, flags=re.I)
else:
return username # Empty string or None
+
+
+async def send_denial(ctx: Context, reason: str) -> None:
+ """Send an embed denying the user with the given reason."""
+ embed = Embed()
+ embed.colour = Colour.red()
+ embed.title = random.choice(NEGATIVE_REPLIES)
+ embed.description = reason
+
+ await ctx.send(embed=embed)