(Reminders): Created reminders module and system.
This commit is contained in:
253
bot/modules/reminders/commands.py
Normal file
253
bot/modules/reminders/commands.py
Normal file
@@ -0,0 +1,253 @@
|
||||
import re
|
||||
import asyncio
|
||||
import datetime
|
||||
import discord
|
||||
|
||||
from utils.lib import parse_dur, parse_ranges, multiselect_regex
|
||||
|
||||
from .module import module
|
||||
from .data import reminders
|
||||
from .reminder import Reminder
|
||||
|
||||
|
||||
reminder_regex = re.compile(
|
||||
r"""
|
||||
(^)?(?P<type> (?: \b in) | (?: every))
|
||||
\s*(?P<duration> (?: day| hour| (?:\d+\s*(?:(?:d|h|m|s)[a-zA-Z]*)?(?:\s|and)*)+))
|
||||
(?:(?(1) (?:, | ; | : | \. | to)? | $))
|
||||
""",
|
||||
re.IGNORECASE | re.VERBOSE | re.DOTALL
|
||||
)
|
||||
|
||||
reminder_limit = 20
|
||||
|
||||
|
||||
@module.cmd(
|
||||
name="remindme",
|
||||
desc="Ask me to remind you about important tasks.",
|
||||
group="Productivity",
|
||||
aliases=('reminders', 'reminder'),
|
||||
flags=('remove', 'clear')
|
||||
)
|
||||
async def cmd_remindme(ctx, flags):
|
||||
"""
|
||||
Usage``:
|
||||
{prefix}remindme in <duration> to <task>
|
||||
{prefix}remindme every <duration> to <task>
|
||||
|
||||
{prefix}reminders
|
||||
{prefix}reminders --clear
|
||||
{prefix}reminders --remove <ids>
|
||||
Description:
|
||||
Ask LionBot to remind you about important tasks.
|
||||
Examples``:
|
||||
{prefix}remindme in 2h 20m, Revise chapter 1
|
||||
{prefix}remindme every hour, Drink water!
|
||||
{prefix}remindme Anatomy class in 8h 20m
|
||||
"""
|
||||
# TODO: (FUTURE) every day at 9:00
|
||||
|
||||
if flags['remove']:
|
||||
# Do removal stuff
|
||||
rows = reminders.fetch_rows_where(
|
||||
userid=ctx.author.id,
|
||||
_extra="ORDER BY remind_at ASC"
|
||||
)
|
||||
if not rows:
|
||||
return await ctx.reply("You have no reminders to remove!")
|
||||
|
||||
live = Reminder.fetch(*(row.reminderid for row in rows))
|
||||
|
||||
lines = []
|
||||
num_field = len(str(len(live) - 1))
|
||||
for i, reminder in enumerate(live):
|
||||
lines.append(
|
||||
"`[{:{}}]` | {}".format(
|
||||
i,
|
||||
num_field,
|
||||
reminder.formatted
|
||||
)
|
||||
)
|
||||
|
||||
description = '\n'.join(lines)
|
||||
description += (
|
||||
"\n\nPlease select the reminders to remove, or type `c` to cancel.\n"
|
||||
"(For example, respond with `1, 2, 3` or `1-3`.)"
|
||||
)
|
||||
embed = discord.Embed(
|
||||
description=description,
|
||||
colour=discord.Colour.orange(),
|
||||
timestamp=datetime.datetime.utcnow()
|
||||
).set_author(
|
||||
name="Reminders for {}".format(ctx.author.display_name),
|
||||
icon_url=ctx.author.avatar_url
|
||||
)
|
||||
|
||||
out_msg = await ctx.reply(embed=embed)
|
||||
|
||||
def check(msg):
|
||||
valid = msg.channel == ctx.ch and msg.author == ctx.author
|
||||
valid = valid and (re.search(multiselect_regex, msg.content) or msg.content.lower() == 'c')
|
||||
return valid
|
||||
|
||||
try:
|
||||
message = await ctx.client.wait_for('message', check=check, timeout=60)
|
||||
except asyncio.TimeoutError:
|
||||
await out_msg.delete()
|
||||
await ctx.error_reply("Session timed out. No reminders were deleted.")
|
||||
return
|
||||
|
||||
try:
|
||||
await out_msg.delete()
|
||||
await message.delete()
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
if message.content.lower() == 'c':
|
||||
return
|
||||
|
||||
to_delete = [
|
||||
live[index].reminderid
|
||||
for index in parse_ranges(message.content) if index < len(live)
|
||||
]
|
||||
if not to_delete:
|
||||
return await ctx.error_reply("Nothing to delete!")
|
||||
|
||||
# Delete the selected reminders
|
||||
Reminder.delete(*to_delete)
|
||||
|
||||
# Ack
|
||||
await ctx.embed_reply(
|
||||
"{tick} Reminder{plural} deleted.".format(
|
||||
tick='✅',
|
||||
plural='s' if len(to_delete) > 1 else ''
|
||||
)
|
||||
)
|
||||
elif flags['clear']:
|
||||
# Do clear stuff
|
||||
rows = reminders.fetch_rows_where(
|
||||
userid=ctx.author.id,
|
||||
)
|
||||
if not rows:
|
||||
return await ctx.reply("You have no reminders to remove!")
|
||||
|
||||
Reminder.delete(*(row.reminderid for row in rows))
|
||||
await ctx.embed_reply(
|
||||
"{tick} Reminders cleared.".format(
|
||||
tick='✅',
|
||||
)
|
||||
)
|
||||
elif ctx.args:
|
||||
# Add a new reminder
|
||||
|
||||
content = None
|
||||
duration = None
|
||||
repeating = None
|
||||
|
||||
# First parse it
|
||||
match = re.search(reminder_regex, ctx.args)
|
||||
if match:
|
||||
repeating = match.group('type').lower() == 'every'
|
||||
|
||||
duration_str = match.group('duration').lower()
|
||||
if duration_str.isdigit():
|
||||
duration = int(duration_str)
|
||||
elif duration_str == 'day':
|
||||
duration = 24 * 60 * 60
|
||||
elif duration_str == 'hour':
|
||||
duration = 60 * 60
|
||||
else:
|
||||
duration = parse_dur(duration_str)
|
||||
|
||||
content = (ctx.args[:match.start()] + ctx.args[match.end():]).strip()
|
||||
if content.startswith('to '):
|
||||
content = content[3:].strip()
|
||||
else:
|
||||
# Legacy parsing, without requiring "in" at the front
|
||||
splits = ctx.args.split(maxsplit=1)
|
||||
if len(splits) == 2 and splits[0].isdigit():
|
||||
repeating = False
|
||||
duration = int(splits[0]) * 60
|
||||
content = splits[1].strip()
|
||||
|
||||
# Sanity checking
|
||||
if not duration or not content:
|
||||
return await ctx.error_reply(
|
||||
"Sorry, I didn't understand your reminder!\n"
|
||||
"See `{prefix}help remindme` for usage and examples.".format(prefix=ctx.best_prefix)
|
||||
)
|
||||
|
||||
# Don't allow rapid repeating reminders
|
||||
if repeating and duration < 10 * 60:
|
||||
return await ctx.error_reply(
|
||||
"You can't have a repeating reminder shorter than `10` minutes!"
|
||||
)
|
||||
|
||||
# Check the user doesn't have too many reminders already
|
||||
count = reminders.select_one_where(
|
||||
userid=ctx.author.id,
|
||||
select_columns=("COUNT(*)",)
|
||||
)[0]
|
||||
if count > reminder_limit:
|
||||
return await ctx.error_reply(
|
||||
"Sorry, you have reached your maximum of `{}` reminders!".format(reminder_limit)
|
||||
)
|
||||
|
||||
# Create reminder
|
||||
reminder = Reminder.create(
|
||||
userid=ctx.author.id,
|
||||
content=content,
|
||||
message_link=ctx.msg.jump_url,
|
||||
interval=duration if repeating else None,
|
||||
remind_at=datetime.datetime.utcnow() + datetime.timedelta(seconds=duration)
|
||||
)
|
||||
|
||||
# Schedule reminder
|
||||
reminder.schedule()
|
||||
|
||||
# Ack
|
||||
embed = discord.Embed(
|
||||
title="Reminder Created!",
|
||||
colour=discord.Colour.orange(),
|
||||
description="Got it! I will remind you <t:{}:R>.".format(reminder.timestamp),
|
||||
timestamp=datetime.datetime.utcnow()
|
||||
)
|
||||
await ctx.reply(embed=embed)
|
||||
elif ctx.alias.lower() == 'remindme':
|
||||
# Show hints about adding reminders
|
||||
...
|
||||
else:
|
||||
# Show formatted list of reminders
|
||||
rows = reminders.fetch_rows_where(
|
||||
userid=ctx.author.id,
|
||||
_extra="ORDER BY remind_at ASC"
|
||||
)
|
||||
if not rows:
|
||||
return await ctx.reply("You have no reminders!")
|
||||
|
||||
live = Reminder.fetch(*(row.reminderid for row in rows))
|
||||
|
||||
lines = []
|
||||
num_field = len(str(len(live) - 1))
|
||||
for i, reminder in enumerate(live):
|
||||
lines.append(
|
||||
"`[{:{}}]` | {}".format(
|
||||
i,
|
||||
num_field,
|
||||
reminder.formatted
|
||||
)
|
||||
)
|
||||
|
||||
description = '\n'.join(lines)
|
||||
embed = discord.Embed(
|
||||
description=description,
|
||||
colour=discord.Colour.orange(),
|
||||
timestamp=datetime.datetime.utcnow()
|
||||
).set_author(
|
||||
name="{}'s reminders".format(ctx.author.display_name),
|
||||
icon_url=ctx.author.avatar_url
|
||||
).set_footer(
|
||||
text="For examples and usage see {}help reminders".format(ctx.best_prefix)
|
||||
)
|
||||
|
||||
await ctx.reply(embed=embed)
|
||||
Reference in New Issue
Block a user