def str_dict(self) -> Dict[str, str]: return { 'user': user_mention(self.user_id) if self.user_id else '', 'channel': channel_mention(self.channel_id) if self.channel_id else 'PM', 'timestamp': format_datetime(self.timestamp), 'remind_time': format_datetime(self.remind_time), 'pin': 'pinned' if self.pin else '', 'message': self.message }
def format_quote(self, quote: Quote, show_saved=True): s_fmt = "[{0}] <#{1}> <{2}> {3}" if self.cog_config.show_channel else "[{0}] <{2}> {3}" if self.cog_config.datetime_format == 'seconds': timestamp_str = format_datetime(quote.timestamp, seconds=True) elif self.cog_config.datetime_format == 'datetime': timestamp_str = format_datetime(quote.timestamp, seconds=False) elif self.cog_config.datetime_format == 'date': timestamp_str = format_date(quote.timestamp) else: raise RuntimeError("Invalid date_format??") s = s_fmt.format(timestamp_str, quote.channel_id, quote.author.mention, quote.message) if show_saved: s += "\n*(saved by {})*".format(quote.saved_by.name) return s
async def task_reminder_expired(self, reminder: ReminderData): logger.info("Reminder has expired: {!r}".format(reminder)) # determine the destination (user or channel) if reminder.channel_id: dest = discord.Object(id=reminder.channel_id) message = f"{reminder.message}\n\n*({user_mention(reminder.user_id)})*" else: # user reminder # can't use discord.Object - send_message would interpret it as a channel, not a user dest = discord.utils.get(self.bot.get_all_members(), id=reminder.user_id) message = "**Reminder** At {} UTC, you asked me to send you a reminder: {}".format( format_datetime(reminder.timestamp), reminder.message) posted_messages = await self.send_message(dest, message) if reminder.pin: try: await self.bot.pin_message(posted_messages[0]) except discord.HTTPException as e: logger.exception( "Error pinning message; skipping: {!r}".format(reminder)) await self.send_output( "Error trying to pin reminder/saylater message: {!r}.". format(reminder)) # stop scheduled retries and remove the reminder try: for instance in self.scheduler.get_instances( self.task_reminder_expired): if instance.args[0] is reminder: instance.cancel() break except asyncio.InvalidStateError: pass try: self.reminders.remove(reminder) except ValueError: logger.warning( "task_reminder_expired: Reminder not in list of reminders - " "already removed? {!r}".format(reminder)) # set up recurring reminder if reminder.renew_data and reminder.renew_data.interval: reminder.remind_time += reminder.renew_data.interval reminder.renew_data.limit -= 1 if reminder.renew_data.limit <= 0: logger.debug("Recurring reminder has reached recurrence limit") elif reminder.renew_data.limit_time and \ reminder.remind_time > reminder.renew_data.limit_time: logger.debug("Recurring reminder has reached time limit") else: logger.debug("Setting up recurrence") self.add_reminder(reminder) self._save_reminders()
def str_dict(self) -> Dict[str, str]: return { 'interval': format_timedelta(self.interval) if self.interval else '', 'limit': '{:d}'.format(self.limit) if self.limit else '', 'limit_time': format_datetime(self.limit_time) if self.limit_time else '' }
async def kaztime(self, ctx: commands.Context): """!kazhelp description: "Gets the current KazTron Time (UTC, GMT). Can be used to help convert community programme schedules to your timezone." parameters: [] examples: - command: .kaztime description: Shows the current time. """ await self.send_message(ctx.message.channel, ctx.message.author.mention + "Current KazTime: {} UTC".format(format_datetime(datetime.utcnow(), seconds=True)))
async def send_check_in_list(self, dest: discord.Channel, check_ins: Pagination, member: discord.Member): es = EmbedSplitter(auto_truncate=True, title="Check-in list", description=member.mention, colour=self.EMBED_COLOR) es.set_footer(text="Page {:d}/{:d}".format(check_ins.page + 1, check_ins.total_pages)) for check_in in check_ins: # type: model.CheckIn f_name = format_datetime(check_in.timestamp) f_message = '{}\n*{:d} {}* – {}\n\\_\\_\\_'.format( member.mention, check_in.word_count, self.PROJECT_UNIT_MAP[check_in.project_type], check_in.message) es.add_field(name=f_name, value=f_message, inline=False) for em in es.finalize(): await self.bot.send_message(dest, embed=em)
async def show_badges(self, dest: discord.Channel, badges: Pagination, member: discord.Member): es = EmbedSplitter(auto_truncate=True, title="Badges", description="{} - {:d} badges".format( member.mention, len(badges)), colour=self.EMBED_COLOUR) es.set_footer(text="Page {:d}/{:d} (total {:d} badges)".format( badges.page + 1, badges.total_pages, len(badges))) for b in badges: # type: model.Badge es.add_field_no_break(name=format_datetime(b.timestamp), value=self._get_badge(b.badge)) es.add_field_no_break(name="From", value=user_mention(b.from_user.discord_id)) es.add_field(name="For", value=b.reason + '\n' + r'\_' * 16, inline=False) for em in es.finalize(): await self.bot.send_message(dest, embed=em)
async def list(self, ctx: commands.Context): """ Lists all future reminders you've requested. """ logger.info("reminder list: {}".format(message_log_str(ctx.message))) items = [] filtered = filter(lambda r: r.user_id == ctx.message.author.id, self.reminders) sorted_reminders = sorted(filtered, key=lambda r: r.remind_time) for reminder in sorted_reminders: items.append("At {} UTC (in {}): {}".format( format_datetime(reminder.remind_time), format_timedelta(reminder.remind_time - datetime.utcnow()), reminder.message )) if items: reminder_list = format_list(items) else: reminder_list = 'None' await self.bot.send_message(ctx.message.author, "**Your reminders**\n" + reminder_list) try: await self.bot.delete_message(ctx.message) except discord.Forbidden: pass # no permission or in a PM; oh well, this is not critical
async def on_reminder_expired(self, reminder: ReminderData): logger.info("Reminder has expired: {!r}".format(reminder)) user = discord.utils.get(self.bot.get_all_members(), id=reminder.user_id) try: await self.bot.send_message( user, "**Reminder** At {} UTC, you asked me to send you a reminder: {}".format( format_datetime(reminder.timestamp), reminder.message ) ) except discord.errors.DiscordException as e: logger.error("Error sending reminder: {}".format(exc_log_str(e))) reminder.remind_time += 30 # try again a little later reminder.start_timer(self.bot.loop, self.on_reminder_expired) else: try: self.reminders.remove(reminder) except ValueError: logger.warning("on_reminder_expired: Reminder not in list of reminders - " "already removed? {!r}".format(reminder)) self._save_reminders()
async def check_in_delta(self, ctx: commands.Context, *, datespec: NaturalDateConverter = None): """!kazhelp description: "Get a report of wordcount changes in a given check-in week, compared to previous check-in." parameters: - name: datespec type: datespec optional: true default: 'last week ("7 days ago")' description: A date in any unambiguous format (2018-03-14, March 14 2018, 14 March 2018, today, 1 month ago, etc.). The report will be for the check-in week that includes this date. examples: - command: .checkin delta description: Get a report for last week. - command: .checkin delta 2018-04-18 description: Get a report for the week that includes 18 April 2018. """ if not datespec: datespec = datetime.utcnow() - timedelta(days=7) start, end = self.c.get_check_in_week(datespec) week_str = "the week from {} to {}".format(format_datetime(start), format_datetime(end)) try: report, ci_map = self.c.generate_check_in_deltas(datespec) except orm.exc.NoResultFound: await self.bot.say("No check-ins for {}.".format(week_str)) return # sort descending by diff value s_users = list(report.keys()) s_users.sort(key=lambda u: report[u] if report.get(u, None) else float('-inf'), reverse=True) # Prepare display delta_strings = [] for u in s_users: if report[u] is not None: unit = self.PROJECT_UNIT_MAP[ci_map[u].project_type] if ci_map.get(u, None) \ else 'words' delta_strings.append( "{0} ({1:+d} {3} - *total {2:d} {3}*)".format( u.mention, report[u], ci_map[u].word_count if ci_map.get(u, None) else 0, unit)) else: delta_strings.append("{0} (no check-in)".format(u.mention)) delta_list_str = '\n'.join(delta_strings) # Prepare the overall embed es = EmbedSplitter(title="User Progress Report", colour=solarized.cyan, description="Report for " + week_str + '\n\n', timestamp=datetime.utcnow(), repeat_header=True, auto_truncate=True) es.set_footer(text="Generated: ") es.add_field(name="_", value=delta_list_str, inline=False) await self.send_message(ctx.message.channel, embed=es)
async def check_in_report(self, ctx: commands.Context, *, datespec: NaturalDateConverter = None): """!kazhelp description: "Get a report of who has or has not checked in in a given week." parameters: - name: datespec type: datespec optional: true default: 'last week ("7 days ago")' description: A date in any unambiguous format (2018-03-14, March 14 2018, 14 March 2018, today, 1 month ago, etc.). The report will be for the check-in week that includes this date. examples: - command: .checkin report description: Get a report for last week. - command: .checkin report 2018-04-18 description: Get a report for the week that includes 18 April 2018. """ if not datespec: datespec = datetime.utcnow() - timedelta(days=7) start, end = self.c.get_check_in_week(datespec) week_str = "the week from {} to {}".format(format_datetime(start), format_datetime(end)) try: ci, nci = self.c.generate_check_in_report( datespec) # checked in, not checked in except orm.exc.NoResultFound: await self.bot.say("No check-ins for {}.".format(week_str)) return # # determine sorting order of each list # # checked in: by name ci_users = list(ci.keys()) ci_users.sort( key=lambda u: u.nick.lower() if u.nick else u.name.lower()) # not checked in: by last checkin date pre-reporting week nci_users = list(nci.keys()) epoch = datetime(1970, 1, 1) nci_users.sort(key=lambda u: nci[u].timestamp if nci.get(u, None) else epoch, reverse=True) # # Prepare display # # format strings for display ci_list_str = '\n'.join("{0} ({1} - *{2:d} {3}*)".format( u.mention, format_datetime(ci[u].timestamp), ci[u].word_count, self.PROJECT_UNIT_MAP[ci[u].project_type]) for u in ci_users) nci_list_str = '\n'.join("{0} (last: {1})".format( u.mention, format_date(nci[u].timestamp) if nci.get(u, None) else 'Never') for u in nci_users) # Prepare the overall embed es = EmbedSplitter(title="Check-In Report", colour=solarized.green, description="Report for " + week_str, timestamp=datetime.utcnow(), repeat_header=True, auto_truncate=True) es.set_footer(text="Generated: ") if len(ci_list_str) < Limits.EMBED_FIELD_VALUE: es.add_field(name="Checked in", value=ci_list_str or 'Nobody', inline=False) else: es.add_field(name="Checked in", value="{:d} users (list too long)".format( len(ci_users)), inline=False) es.add_field(name="Did NOT check in", value=nci_list_str, inline=False) await self.send_message(ctx.message.channel, embed=es)
async def reminder(self, ctx: commands.Context, *, args: str): """ Sends you a personal reminder by PM at some point in the future. TIP: Make sure you've enabled "Allow direct messages from server members" for the server the bot is on. TIP: You should double-check the reminder time in the confirmation PM, to make sure your timespec was interpreted correctly. **Usage:** `.remind <timespec>: <message>` **Arguments:** * `<timespec>: `: A time in the future to send you a reminder, followed by a colon and a space. This can be an absolute date and time `2018-03-07 12:00:00`, a relative time `in 2h 30m` (the space between hours and minutes, or other different units, is important), or combinations of the two (`tomorrow at 1pm`). Times are in UTC+0000, unless you specify your time zone (e.g. `12:00:00 UTC-5`). * `<message>`: The message to include with the reminder. **Examples:** .remind in 2 hours: Feed the dog .remind on 24 december at 4:50pm: Grandma's christmas call .remind tomorrow at 8am: Start Spotlight """ logger.info("reminder: {}".format(message_log_str(ctx.message))) # check existing count n = reduce(lambda c, r: c+1 if r.user_id == ctx.message.author.id else c, self.reminders, 0) if n >= self.MAX_PER_USER: logger.warning("Cannot add reminder: user {} at limit".format(ctx.message.author)) await self.bot.say(("Oops! You already have too many future reminders! " "The limit is {:d} per person.").format(self.MAX_PER_USER)) return try: timespec_s, msg = re.split(r':\s+|,', args, maxsplit=1) except ValueError: raise commands.BadArgument("message") timestamp = datetime.utcnow() # first one allows "10 minutes" as a future input, second is a fallback timespec = dt_parse('in ' + timespec_s, future=True) or dt_parse(timespec_s, future=True) if timespec is None: raise commands.BadArgument("timespec", timespec_s[:64]) elif timespec <= timestamp: raise commands.BadArgument("past") reminder = ReminderData( user_id=ctx.message.author.id, timestamp=timestamp, remind_time=timespec, msg=msg ) reminder.start_timer(self.bot.loop, self.on_reminder_expired) self.reminders.append(reminder) self._save_reminders() logger.info("Set reminder: {!r}".format(reminder)) await self.bot.say("Got it! I'll remind you by PM at {} UTC (in {!s}).".format( format_datetime(reminder.remind_time), format_timedelta(reminder.remind_time - datetime.utcnow()) ))