diff --git a/README.md b/README.md index 2e8c3e6..c9a2572 100755 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Main goal of this project is to get Robocop functionality done, secondary goal i - [ ] New feature: Highlights (problematic words automatically get posted to modmail channel, relies on modmail) - [ ] Feature creep: Shortlink completion (gl/ao/etc) - [ ] Feature creep: Pleroma embedding +- [x] Feature creep: Reminds - [x] A system for running jobs in background with an interval (will be called robocronp) - [x] Commands to list said jobs and remove them - [x] New moderation feature: timemute (mute with time, relies on robocronp) diff --git a/Robocop.py b/Robocop.py index 3bfff8e..b905090 100755 --- a/Robocop.py +++ b/Robocop.py @@ -56,6 +56,7 @@ initial_extensions = ['cogs.common', 'cogs.lockdown', 'cogs.legacy', 'cogs.links', + 'cogs.remind', 'cogs.robocronp', 'cogs.meme'] diff --git a/cogs/mod_timed.py b/cogs/mod_timed.py index 6da053c..968d35a 100644 --- a/cogs/mod_timed.py +++ b/cogs/mod_timed.py @@ -1,6 +1,5 @@ import discord import config -import time from datetime import datetime from discord.ext import commands from helpers.checks import check_if_staff diff --git a/cogs/remind.py b/cogs/remind.py new file mode 100644 index 0000000..4f59c02 --- /dev/null +++ b/cogs/remind.py @@ -0,0 +1,49 @@ +import discord +from datetime import datetime +from discord.ext import commands +from helpers.robocronp import add_job, get_crontab + + +class Remind: + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def remindlist(self, ctx): + """Lists your reminders.""" + ctab = get_crontab() + embed = discord.Embed(title=f"Active robocronp jobs") + for jobtimestamp in ctab["remind"]: + job_details = ctab["remind"][jobtimestamp][str(ctx.author.id)] + expiry_timestr = datetime.utcfromtimestamp(int(jobtimestamp))\ + .strftime('%Y-%m-%d %H:%M:%S (UTC)') + embed.add_field(name=f"Reminder for {expiry_timestr}", + value=f"Added on: {job_details['added']}, " + f"Text: {job_details['text']}", + inline=False) + await ctx.send(embed=embed) + + @commands.command() + async def remind(self, ctx, when: str, *, text: str = "something"): + """Reminds you about something.""" + + expiry_timestamp = self.bot.parse_time(when) + expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp) + duration_text = self.bot.get_relative_timestamp(time_to=expiry_datetime, + include_to=True, + humanized=True) + + safe_text = self.bot.escape_message(str(text)) + added_on = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S (UTC)") + + add_job("remind", + ctx.author.id, + {"text": safe_text, "added": added_on}, + expiry_timestamp) + + await ctx.send(f"{ctx.author.mention}: I'll remind you in DMs about" + f" {safe_text} in {duration_text}") + + +def setup(bot): + bot.add_cog(Remind(bot)) diff --git a/cogs/robocronp.py b/cogs/robocronp.py index 8feb4d2..97467b0 100644 --- a/cogs/robocronp.py +++ b/cogs/robocronp.py @@ -2,6 +2,7 @@ import asyncio import config import time import discord +import traceback from discord.ext import commands from helpers.robocronp import get_crontab, delete_job from helpers.restrictions import remove_restriction @@ -53,26 +54,42 @@ class Robocronp: await ctx.send(f"{ctx.author.mention}: Deleted!") async def do_jobs(self, ctab, jobtype, timestamp): + log_channel = self.bot.get_channel(config.log_channel) for job_name in ctab[jobtype][timestamp]: - job_details = ctab[jobtype][timestamp][job_name] - if jobtype == "unban": - target_user = await self.bot.get_user_info(job_name) - target_guild = self.bot.get_guild(job_details["guild"]) - await target_guild.unban(target_user, - reason="Robocronp: Timed ban expired.") - delete_job(timestamp, jobtype, job_name) - elif jobtype == "unmute": - remove_restriction(job_name, config.mute_role) - target_guild = self.bot.get_guild(job_details["guild"]) - target_member = target_guild.get_member(int(job_name)) - target_role = target_guild.get_role(config.mute_role) - await target_member.remove_roles(target_role, - reason="Robocronp: Timed " - "mute expired.") - delete_job(timestamp, jobtype, job_name) + try: + job_details = ctab[jobtype][timestamp][job_name] + if jobtype == "unban": + target_user = await self.bot.get_user_info(job_name) + target_guild = self.bot.get_guild(job_details["guild"]) + delete_job(timestamp, jobtype, job_name) + await target_guild.unban(target_user, + reason="Robocronp: Timed " + "ban expired.") + elif jobtype == "unmute": + remove_restriction(job_name, config.mute_role) + target_guild = self.bot.get_guild(job_details["guild"]) + target_member = target_guild.get_member(int(job_name)) + target_role = target_guild.get_role(config.mute_role) + await target_member.remove_roles(target_role, + reason="Robocronp: Timed " + "mute expired.") + delete_job(timestamp, jobtype, job_name) + elif jobtype == "remind": + text = job_details["text"] + added_on = job_details["added"] + target = await self.bot.get_user_info(int(job_name)) + if target: + await target.send("You asked to be reminded about" + f" `{text}` on {added_on}.") + delete_job(timestamp, jobtype, job_name) + except: + # Don't kill cronjobs if something goes wrong. + await log_channel.send("Crondo has errored: ```" + f"{traceback.format_exc()}```") async def minutely(self): await self.bot.wait_until_ready() + log_channel = self.bot.get_channel(config.log_channel) while not self.bot.is_closed(): try: ctab = get_crontab() @@ -83,11 +100,13 @@ class Robocronp: await self.do_jobs(ctab, jobtype, jobtimestamp) except: # Don't kill cronjobs if something goes wrong. - pass + await log_channel.send("Cron-minutely has errored: ```" + f"{traceback.format_exc()}```") await asyncio.sleep(60) async def hourly(self): await self.bot.wait_until_ready() + log_channel = self.bot.get_channel(config.log_channel) while not self.bot.is_closed(): # Your stuff that should run at boot # and after that every hour goes here @@ -96,7 +115,8 @@ class Robocronp: await self.send_data() except: # Don't kill cronjobs if something goes wrong. - pass + await log_channel.send("Cron-hourly has errored: ```" + f"{traceback.format_exc()}```") # Your stuff that should run an hour after boot # and after that every hour goes here