def _parse_timedelta(timedelta_string, repeating): """Parse a timedelta, taking into account if it is a repeating timedelta (day minimum) or not.""" result = None testing_text = "" for chunk in timedelta_string.split(): if chunk == "and": continue if chunk.isdigit(): testing_text += chunk continue testing_text += chunk.rstrip(",") if repeating: try: parsed = parse_timedelta( testing_text, minimum=timedelta(days=1), allowed_units=["weeks", "days"], ) except commands.BadArgument as ba: orig_message = str(ba)[0].lower() + str(ba)[1:] raise BadArgument( f"For the repeating portion of this reminder, {orig_message}. " "You must only use `days` or `weeks` when dealing with repeating reminders." ) else: parsed = parse_timedelta(testing_text, minimum=timedelta(minutes=1)) if parsed != result: result = parsed else: return None return result
async def removerole( self, ctx: commands.Context, role: discord.Role, time: str, *requiredroles: discord.Role ): """ Add a role to be removed after specified time on server Useful with an autorole cog """ guild = ctx.guild try: parsed_time = parse_timedelta(time, allowed_units=["weeks", "days", "hours"]) except commands.BadArgument: await ctx.maybe_send_embed("Error: Invalid time string.") return days = parsed_time.days hours = parsed_time.seconds // 60 // 60 to_set = {"days": days, "hours": hours, "remove": True} if requiredroles: to_set["required"] = [r.id for r in requiredroles] await self.config.guild(guild).roles.set_raw(role.id, value=to_set) await ctx.maybe_send_embed( f"Time Role for {role.name} set to {days} days and {hours} hours until removed" )
async def time(self, ctx: commands.Context, reminder_id: int, *, time: str): """Modify the time of an existing reminder.""" users_reminders = await self.get_user_reminders(ctx.message.author.id) old_reminder = self._get_reminder(users_reminders, reminder_id) if not old_reminder: await self._send_non_existant_msg(ctx, reminder_id) return try: time_delta = parse_timedelta(time, minimum=timedelta(minutes=1)) if not time_delta: await ctx.send_help() return except commands.BadArgument as ba: await self._send_message(ctx, str(ba)) return seconds = time_delta.total_seconds() future = int(current_time.time() + seconds) future_text = humanize_timedelta(timedelta=time_delta) new_reminder = old_reminder.copy() new_reminder.update(FUTURE=future, FUTURE_TEXT=future_text) async with self.config.reminders() as current_reminders: current_reminders.remove(old_reminder) current_reminders.append(new_reminder) await self._send_message( ctx, f"Reminder with ID# **{reminder_id}** has been edited successfully, " f"and will now remind you {future_text} from now.", )
async def time(self, ctx: commands.Context, reminder_id: int, *, time: str): """Modify the time of an existing reminder.""" users_reminders = await self.get_user_reminders(ctx.message.author.id) old_reminder = self._get_reminder(users_reminders, reminder_id) if not old_reminder: await self._send_non_existent_msg(ctx, reminder_id) return try: time_delta = parse_timedelta(time, minimum=timedelta(minutes=1)) if not time_delta: await ctx.send_help() return except commands.BadArgument as ba: await reply(ctx, str(ba)) return future = int(current_time.time() + time_delta.total_seconds()) future_text = humanize_timedelta(timedelta=time_delta) new_reminder = old_reminder.copy() new_reminder.update(FUTURE=future, FUTURE_TEXT=future_text) async with self.config.reminders() as current_reminders: current_reminders.remove(old_reminder) current_reminders.append(new_reminder) message = f"Reminder with ID# **{reminder_id}** will remind you in {future_text} from now (<t:{future}:f>)" if "REPEAT" in new_reminder and new_reminder["REPEAT"]: message += f", repeating every {humanize_timedelta(seconds=new_reminder['REPEAT'])} thereafter." else: message += "." await reply(ctx, message)
async def convert(self, ctx: Context, argument: str) -> datetime.timedelta: if argument.lower() == "all": return datetime.timedelta(days=9000) delta = parse_timedelta(argument, minimum=datetime.timedelta(hours=1)) if delta is None: raise BadArgument("That's not a valid time.") return delta
async def repeat(self, ctx: commands.Context, reminder_id: int, *, time: str): """Modify the repeating time of an existing reminder. Pass "0" to <time> in order to disable repeating.""" users_reminders = await self.get_user_reminders(ctx.message.author.id) old_reminder = self._get_reminder(users_reminders, reminder_id) if not old_reminder: await self._send_non_existent_msg(ctx, reminder_id) return if time.lower() in ["0", "stop", "none", "false", "no", "cancel", "n"]: new_reminder = old_reminder.copy() new_reminder.update(REPEAT=None) async with self.config.reminders() as current_reminders: current_reminders.remove(old_reminder) current_reminders.append(new_reminder) await reply( ctx, f"Reminder with ID# **{reminder_id}** will not repeat anymore. " f"The final reminder will be sent <t:{new_reminder['FUTURE']}:f>.", ) else: try: time_delta = parse_timedelta(time, minimum=timedelta(days=1), allowed_units=["weeks", "days"]) if not time_delta: await ctx.send_help() return except commands.BadArgument as ba: await reply(ctx, str(ba)) return new_reminder = old_reminder.copy() new_reminder.update(REPEAT=int(time_delta.total_seconds())) async with self.config.reminders() as current_reminders: current_reminders.remove(old_reminder) current_reminders.append(new_reminder) await reply( ctx, f"Reminder with ID# **{reminder_id}** will now remind you " f"every {humanize_timedelta(timedelta=time_delta)}, with the first reminder being sent " f"<t:{new_reminder['FUTURE']}:f>.", )
async def _create_reminder(self, ctx: commands.Context, time_and_optional_text: str): """Logic to create a reminder.""" author = ctx.message.author maximum = await self.config.max_user_reminders() users_reminders = await self.get_user_reminders(author.id) if len(users_reminders) > maximum - 1: plural = "reminder" if maximum == 1 else "reminders" await self._send_message( ctx, "You have too many reminders! " f"I can only keep track of {maximum} {plural} for you at a time.", ) return # Supported: # [p]remindme in {time} to {text} # [p]remindme in {time} {text} # [p]remindme in {time} # [p]remindme {time} to {text} # [p]remindme {time} {text} # [p]remindme {time} # [p]remindme to {text} in {time} # [p]remindme to {text} {time} # [p]remindme {text} in {time} # [p]remindme {text} {time} # find the time delta(s) in the text time = "" index = -1 time_index_start = -1 time_index_end = -1 prev_num = "" full_split = self.split_all.split(time_and_optional_text.strip()) for chunk in full_split: index += 1 if chunk.isspace(): continue chunk = self.alpha_num.sub("", chunk) if chunk == "and" and not prev_num: # "and" can appear between time deltas continue if chunk.isdigit(): prev_num = chunk if time_index_start == -1: time_index_start = index continue if chunk.isalpha() and prev_num: chunk = f"{prev_num} {chunk}" try: if parse_timedelta(chunk): time = f"{time} {chunk}" if time else chunk if time_index_start == -1: time_index_start = index time_index_end = index elif time: break else: time_index_start = -1 except commands.BadArgument: pass prev_num = "" if not time: await ctx.send_help() return # At this point we have a time string able to be parsed for a time delta, # as well as the starting and ending index of where it appeared in the text # detect preceding "in" so it can be removed from text as well if (time_index_start > 1 and self.alpha_num.sub( "", full_split[time_index_start - 2]) == "in"): time_index_start -= 2 # the time portion of the text must now either be at the beginning or the end if time_index_start != 0 and time_index_end != len(full_split) - 1: await ctx.send_help() return # delete time (and optional "in", and surrounding space) from the text del full_split[max(time_index_start - 1, 0):min(time_index_end + 2, len(full_split))] # if the text begins with an optional "to", delete that as well if len(full_split) > 1 and self.alpha_num.sub("", full_split[0]) == "to": del full_split[0:2] # parse resulting time delta try: time_delta = parse_timedelta(time, minimum=timedelta(minutes=1)) except commands.BadArgument as ba: await self._send_message(ctx, str(ba)) return # recreate cleaned up text text = "".join(full_split).strip() if len(text) > 1000: await self._send_message(ctx, "Your reminder text is too long.") return seconds = time_delta.total_seconds() future = int(current_time.time() + seconds) future_text = humanize_timedelta(timedelta=time_delta) next_reminder_id = self.get_next_user_reminder_id(users_reminders) reminder = { "USER_REMINDER_ID": next_reminder_id, "USER_ID": author.id, "REMINDER": text, "FUTURE": future, "FUTURE_TEXT": future_text, "JUMP_LINK": ctx.message.jump_url, } async with self.config.reminders() as current_reminders: current_reminders.append(reminder) await self._send_message( ctx, f"I will remind you of {'that' if text else 'this'} in {future_text}." ) if (ctx.guild and await self.config.guild(ctx.guild).me_too() and ctx.channel.permissions_for(ctx.me).add_reactions): query: discord.Message = await ctx.send( "If anyone else would like to be reminded as well, click the bell below!" ) self.me_too_reminders[query.id] = reminder await query.add_reaction(self.reminder_emoji) await asyncio.sleep(30) await delete(query) del self.me_too_reminders[query.id]
async def convert(cls, ctx: commands.Context, arg: str): maybe_td = commands.parse_timedelta(argument=arg) if not maybe_td: raise commands.BadArgument() return cls(maybe_td)