async def on_command_error(self, ctx, error): if isinstance(error, commands.CommandNotFound): pass elif isinstance(error, commands.MissingRequiredArgument): pass elif isinstance(error, commands.BadArgument): await ctx.send(error) elif isinstance(error, commands.NoPrivateMessage): await ctx.send(t("error.private_message")) elif isinstance(error, commands.MissingPermissions): await ctx.send(t("error.missing_permissions")) elif isinstance(error, commands.CheckFailure): pass elif isinstance(error, commands.CommandInvokeError): if isinstance(error.original, asyncio.TimeoutError): await ctx.channel.send(t("error.timeout")) elif isinstance(error.original, Forbidden): if error.original.text == "Cannot send messages to this user": await ctx.send(t("error.cannot_private_message")) else: raise error else: raise error else: raise error
async def channel(self, ctx): """Create a new event channel""" with self.bot.scoped_session() as session: guild = find_or_create_guild(session, ctx.guild.id) if not HavePermission(ctx.author, guild).channel(): return await ctx.send(t("error.missing_permissions")) with self.bot.scoped_session() as session: channel_count = event_channel_count_for_guild(session, ctx.guild.id) if channel_count >= self.MAX_CHANNELS: return await ctx.send(t("channel.channel_limit")) channel = await self.bot.create_discord_event_channel( ctx.guild, ctx.channel.category ) event_channel = EventChannel(id=channel.id, guild_id=ctx.guild.id) with self.bot.scoped_session() as session: session.add(event_channel) events = event_channel.events await self.list_events.call(events, channel) await ctx.send(t("channel.channel_created").format(channel.mention))
async def _get_title_from_user(self, ctx): """Retrieve the event title from the user""" await ctx.author.send(t("event.title_prompt")) while True: title = (await self.bot.get_next_pm(ctx.author)).content if len(title) <= self.MAX_TITLE_LENGTH: return title else: await ctx.author.send( t("event.invalid_title").format(self.MAX_TITLE_LENGTH))
def call(self, prefix, commands): embed = discord.Embed(title=t("help.available_commands")) embed.color = EMBED_COLOR embed.description = t("help.server_invite").format(self.INVITE) for command in commands: signature = prefix + command.name help_text = self._get_command_help_text(command) embed.add_field(name=signature, value=help_text, inline=False) return embed
async def timezone(self, ctx): """Set your local time zone""" await ctx.send(embed=self.time_zone_embed.call()) time_zone = await self.time_zone_input.call(ctx.author, ctx.channel) with self.bot.scoped_session() as session: user = find_or_create_user(session, ctx.author.id) user.time_zone = time_zone session.add(user) humanized_time_zone = t("time_zones.{}".format(time_zone.lower())) await ctx.send(t("time_zone.updated").format(humanized_time_zone))
async def _get_desc_from_user(self, ctx): """Retrieve the event description from the user""" await ctx.author.send(t("event.description_prompt")) while True: resp = (await self.bot.get_next_pm(ctx.author, timeout=240)).content if resp.upper() == "NONE": return None elif len(resp) <= self.MAX_DESC_LENGTH: return resp else: await ctx.author.send( t("event.invalid_description").format(self.MAX_DESC_LENGTH) )
def call(self): embed = discord.Embed() embed.color = EMBED_COLOR embed.title = t("time_zone.title") embed.description = "" for i, iso_time_zone in enumerate(ISO_TIME_ZONES, 1): time_zone_name = t("time_zones.{}".format(iso_time_zone.lower())) embed.description += "**{}** {}\n".format(i, time_zone_name) embed.description += "\n" embed.description += t("time_zone.footer").format(self.INVITE) return embed
async def _get_capacity_from_user(self, ctx): """Retrieve the event capacity from the user""" await ctx.author.send(t("event.capacity_prompt")) while True: resp = (await self.bot.get_next_pm(ctx.author)).content if resp.upper() == "NONE": return None elif resp.isdigit() and int(resp) in range(1, self.MAX_CAPACITY + 1): return int(resp) else: await ctx.author.send( t("event.invalid_capacity").format(self.MAX_CAPACITY) )
def _organizer_name(self, event, guild): """Retrieve the guild specific display name of the organizer""" organizer = guild.get_member(event.organizer_id) if organizer: return organizer.display_name else: return t("event.unknown_user")
async def on_guild_join(self, guild): with self.bot.scoped_session() as session: find_or_create_guild(session, guild.id) # Add entry for guild prefix in the cache self.bot.cache.update_prefix(guild.id, None) await guild.owner.send(t("welcome_message").format(guild.name))
async def delete(self, ctx, *, role: discord.Role): """Change or view the minimum role required to delete events""" with self.bot.scoped_session() as session: guild = find_or_create_guild(session, ctx.guild.id) guild.delete_role_id = role.id session.add(guild) await ctx.send(t("role.delete_role_changed").format(role))
async def prefix(self, ctx, new_prefix): """Update the server's command prefix""" with self.bot.scoped_session() as session: guild = find_or_create_guild(session, ctx.guild.id) guild.prefix = new_prefix session.add(guild) self.bot.cache.update_prefix(ctx.guild.id, new_prefix) await ctx.send(t("prefix.changed").format(new_prefix))
async def delete_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): with self.bot.scoped_session() as session: guild = find_or_create_guild(session, ctx.guild.id) role = discord.utils.get(ctx.guild.roles, id=guild.delete_role_id) await ctx.send( t("role.delete_role_current").format(role, ctx.prefix))
async def _get_start_time(self, ctx, iso_time_zone): """Retrieve a datetime UTC object from the user""" await ctx.author.send(t("event.start_time_prompt")) while True: start_time_str = (await self.bot.get_next_pm(ctx.author)).content try: utc_start_time = (arrow.get( start_time_str, ["YYYY-MM-DD h:mm A", "YYYY-MM-DD HH:mm"], tzinfo=iso_time_zone, ).to("utc").datetime) if utc_start_time < arrow.utcnow(): await ctx.author.send(t("event.start_time_in_the_past")) else: return utc_start_time except: await ctx.author.send(t("event.invalid_start_time"))
async def _get_event_from_user(self, user, events_dict): while True: resp = (await self.bot.get_next_pm(user, timeout=60)).content if resp == "cancel": return -1 if not resp.isdigit() or int(resp) not in list(events_dict.keys()): await user.send(t("event.invalid_selection_error")) else: event = events_dict[int(resp)] return event
async def _get_choice_from_user(self, user, channel, valid_range): while True: resp = (await self.bot.get_next_message(user, channel, timeout=60)).content if resp.lower() == "cancel": return 0 if not resp.isdigit() or not 0 < int(resp) <= valid_range: await user.send(t("event.invalid_selection_error")) else: return int(resp)
async def call(self, user, channel): while True: resp = (await self.bot.get_next_message(user, channel)).content if not self._valid_time_zone_input(resp): await channel.send(t("event.invalid_time_zone")) continue time_zone_index = int(resp) - 1 return ISO_TIME_ZONES[time_zone_index]
async def _get_time_zone(self, ctx): """Retrieve a valid time zone string from the user""" await ctx.author.send(embed=TimeZoneEmbed().call()) while True: resp = (await self.bot.get_next_pm(ctx.author)).content if self._valid_time_zone_input(resp): time_zone_index = int(resp) - 1 if time_zone_index in range(len(ISO_TIME_ZONES)): return ISO_TIME_ZONES[time_zone_index] else: await ctx.author.send(t("event.invalid_time_zone"))
def call(self): embed = discord.Embed() embed.color = EMBED_COLOR embed.title = t("time_zone.title") embed.description = t("time_zone.footer").format(self.INVITE) time_zone_index = 1 for region, time_zones in ISO_TIME_ZONES.items(): time_zone_list = "" for time_zone in time_zones: time_zone_name = t("time_zones.{}".format(time_zone.lower())) time_zone_list += "**{}** {}\n".format(time_zone_index, time_zone_name) time_zone_index += 1 embed.add_field(name=t(region), value=time_zone_list) return embed
def call(self, user_count, event_count, guild_count): embed = discord.Embed(title=t("apollo")) embed.color = EMBED_COLOR embed.description = self._description() embed.add_field(name=t("about.users"), value=user_count) embed.add_field(name=t("about.servers"), value=guild_count) embed.add_field(name=t("about.events"), value=event_count) embed.add_field(name=t("about.memory"), value=self._memory_usage()) embed.add_field(name=t("about.cpu"), value=self._cpu_usage()) embed.add_field(name=t("about.uptime"), value=self._uptime()) embed.set_footer(text=t("about.made_with"), icon_url="http://i.imgur.com/5BFecvA.png") return embed
async def event(self, ctx): """Create a new event""" # Clean up event channels that may have been deleted # while the bot was offline. self.sync_event_channels.call(ctx.guild.id) with self.bot.scoped_session() as session: guild = find_or_create_guild(session, ctx.guild.id) if not Can(ctx.author, guild).event(): return await ctx.send(t("error.missing_permissions")) with self.bot.scoped_session() as session: event_channels = ( session.query(EventChannel).filter_by(guild_id=ctx.guild.id).all() ) user = find_or_create_user(session, ctx.author.id) event = Event() event.title = await self._get_title_from_user(ctx) event.description = await self._get_desc_from_user(ctx) event.organizer = user event.capacity = await self._get_capacity_from_user(ctx) event.event_channel = await self._get_event_channel(ctx, event_channels) event.time_zone = await self._get_time_zone(ctx) event.start_time = await self._get_start_time(ctx, event.time_zone) channel = self.bot.get_channel(event.event_channel.id) await ctx.author.send(t("event.created").format(channel.mention)) with self.bot.scoped_session() as session: session.add(event) with self.bot.scoped_session() as session: events = ( session.query(Event) .filter_by(event_channel_id=event.event_channel_id) .all() ) await self.list_events.call(events, channel)
async def call(self, user, channel, selection, title=None, footer=None): """ Send a list of events to the user and ask them to pick one. Note only sends a list, and does not send the prompt before it. :param user: Member, e.g. context.author :param channel: Messageable :param selection: dict :param title: str or None :param footer: str or None :return: Event """ if title is None: title = t("selection.generic") if footer is None: footer = t("selection.generic_cancel") await channel.send( embed=self.selection_embed.call(selection, title, footer)) return await self._get_choice_from_user(user, channel, len(selection))
def call(self, event, responses, guild): """Create a Discord Embed to represent an event message""" embed = discord.Embed() embed.color = EMBED_COLOR embed.title = event.title if event.description: embed.description = event.description embed.set_footer(text=t("event.created_by").format( self._organizer_name(event, guild), emoji.SKULL)) # Start time field embed.add_field(name=t("event.time"), value=self._formatted_start_time(event), inline=False) accepted_members = self._accepted_members(responses, guild, event.capacity) embed.add_field( name=self._accepted_header(event.capacity, len(accepted_members)), value=self._format_members(accepted_members), ) declined_members = self._declined_members(responses, guild) embed.add_field(name=self.DECLINED_HEADER, value=self._format_members(declined_members)) tentative_members = self._tentative_members(responses, guild) embed.add_field(name=self.TENTATIVE_HEADER, value=self._format_members(tentative_members)) standby_members = self._standby_members(responses, guild, event.capacity) if len(standby_members) > 0: embed.add_field(name=self.STANDBY_HEADER, value=self._format_members(standby_members)) return embed
async def call(self, events, discord_channel): # Mark messages for deletion so that we can ignore them # in OnRawMessageDelete. for event in events: self.bot.cache.mark_message_for_deletion(event.message_id) await discord_channel.purge() if len(events) == 0: return await discord_channel.send(t("channel.no_events")) for event in self.sort_events_by_start_time(events): with self.bot.scoped_session() as session: responses = responses_for_event(session, event.id) await self.list_event.call(event, responses, discord_channel)
async def call(self, user, channel, iso_time_zone): """ Retrieve a datetime UTC object from the user :param user: Member, e.g. context.author :param channel: Messageable, e.g. context.author.dmchannel :param iso_time_zone: str, Option in Apollo.time_zones.ISO_TIME_ZONES :return: Arrow object """ while True: start_time_str = (await self.bot.get_next_message( user, channel)).content.upper() try: utc_start_time = (arrow.get( start_time_str, ["YYYY-MM-DD h:mm A", "YYYY-MM-DD HH:mm"], tzinfo=iso_time_zone, ).to("utc").datetime) if utc_start_time < arrow.utcnow(): await user.send(t("event.start_time_in_the_past")) else: return utc_start_time except: await user.send(t("event.invalid_start_time"))
async def call(self, user, channel): """ Get title from user :param user: Member, e.g. context.author :param channel: Messageable, e.g. context.author.dmchannel :return: str """ while True: title = (await self.bot.get_next_message(user, channel)).content if len(title) <= MAX_TITLE_LENGTH: return title else: await user.send( t("event.invalid_title").format(MAX_TITLE_LENGTH))
async def call(self, user, channel): """ Get description from user :param user: Member, e.g. context.author :param channel: Messageable, e.g. context.author.dmchannel :return: str """ while True: resp = (await self.bot.get_next_message(user, channel, timeout=240)).content if resp == "None": return None elif len(resp) <= MAX_DESC_LENGTH: return resp else: await user.send(t("event.invalid_description").format(MAX_DESC_LENGTH))
async def call(self, event, payload): channel = self.bot.get_channel(payload.channel_id) if payload.emoji.name == emoji.CLOCK: # We generally clear the reaction later on, but as we could we # waiting on the user to input a time zone, we don't want this # reaction to hang around for too long. await self.bot.remove_reaction(payload) discord_user = self.bot.get_user(payload.user_id) with self.bot.scoped_session() as session: apollo_user = find_or_create_user(session, payload.user_id) await self.request_local_start_time.call(apollo_user, discord_user, event) if payload.emoji.name == emoji.SKULL: member = self.bot.find_guild_member(payload.guild_id, payload.user_id) with self.bot.scoped_session() as session: guild = find_or_create_guild(session, payload.guild_id) if event.organizer_id != member.id and not HavePermission(member, guild).delete(): return await member.send("You don't have permission to do that.") with self.bot.scoped_session() as session: session.delete(event) await channel.delete_messages([discord.Object(id=event.message_id)]) with self.bot.scoped_session() as session: event_count = event_count_for_event_channel(session, channel.id) if event_count == 0: await channel.send(t("channel.no_events")) return rsvp_status = EMOJI_STATUSES.get(payload.emoji.name) if rsvp_status: await self.update_response.call(event.id, payload.user_id, rsvp_status) with self.bot.scoped_session() as session: responses = responses_for_event(session, event.id) await self.update_event.call(event, responses, channel)
async def call(self, user, channel): """ Retrieve the event capacity from the user :param user: Member, e.g. context.author :param channel: Messageable, e.g. context.author.dmchannel :return: int or None """ while True: resp = (await self.bot.get_next_message(user, channel)).content if resp.upper() == "NONE": return None elif resp.isdigit() and int(resp) in range(1, MAX_CAPACITY + 1): return int(resp) else: await channel.send( t("event.invalid_capacity").format(MAX_CAPACITY))
async def on_raw_message_delete(self, payload): # If the message is marked for deletion, it was deleted by the bot # as part of clearing the event channel. Unmark it, and return. if self.bot.cache.message_marked_for_deletion(payload.message_id): return self.bot.cache.unmark_message_for_deletion(payload.message_id) with self.bot.scoped_session() as session: event = find_event_from_message(session, payload.message_id) if not event: return session.delete(event) event_channel = event.event_channel if len(event_channel.events) == 0: discord_event_channel = self.bot.get_channel(event_channel.id) await discord_event_channel.send(t("channel.no_events"))