async def invite(self, ctx) -> None: """ Get a link to invite RPANBot to your guild. """ invite_link = "https://discord.com/oauth2/authorize?client_id={client_id}&scope=bot&permissions={invite_permissions}".format( client_id=self.bot.core.settings.discord.client_id, invite_permissions=self.bot.core.settings.discord.invite_permissions, ) await ctx.send( "", embed=RPANEmbed( title="Click here to invite the bot to your server.", description=f"You can also join the [bot support server!]({self.bot.core.settings.links.support_guild})", url=invite_link, user=ctx.author, bot=self.bot, message=ctx.message, ), )
async def howtobroadcast(self, ctx) -> None: """ Get information on how to broadcast to RPAN. """ await ctx.send( "", embed=RPANEmbed( title="Click here to view the section on the RPAN wiki.", url= "https://www.reddit.com/r/pan/wiki/index#wiki_how_do_i_broadcast_on_mobile.3F", description=dedent(""" There are two ways to broadcast to RPAN: from mobile and from desktop. You can easily broadcast from mobile using the [Reddit app.](https://www.reddit.com/r/pan/wiki/index#wiki_how_do_i_broadcast_on_mobile.3F) To broadcast from desktop, you can use [RPAN Studio.](https://www.reddit.com/r/RPANStudio/comments/hjimlq/you_want_a_desktop_streaming_solution_you_got_it/) """).strip(), user=ctx.author, bot=self.bot, message=ctx.message), )
async def topstreams(self, ctx, time_period: Optional[str] = None) -> None: """ View the top broadcasts on each RPAN subreddit. """ top_broadcasts, time = self.bot.core.strapi.get_top_broadcasts( time_period) fields = {} for subreddit, broadcast in top_broadcasts.items(): if broadcast is not None: fields[ f"r/{subreddit}"] = f"[{broadcast.title}]({broadcast.url})" await ctx.send( "", embed=RPANEmbed( title="Top Broadcasts", description= f"The top broadcast on each RPAN subreddit from within: {time}.", fields=fields, user=ctx.author, ))
async def developer_restart(self, ctx) -> None: """ DEVELOPER: Restart the bot. """ await ctx.send("", embed=RPANEmbed( title="Development - Restarting Bot", user=ctx.author, )) await self.bot.change_presence(status=Status.idle, activity=Activity( type=ActivityType.watching, name="a restart.", )) try: await self.bot.logout() except Exception: pass finally: print("DEVELOPER: Restarting bot.") execl(executable, executable, *argv)
async def before_invoke(self, ctx): """ Handles some events before continuing with invoking a command. """ # Handle the global commands cooldown. cooldown_bucket = self.spam_cooldown.get_bucket(ctx.message) cooldown_retry = cooldown_bucket.update_rate_limit( ctx.message.created_at.replace(tzinfo=timezone.utc).timestamp()) if cooldown_retry: # Increment the user's spam counter. if ctx.author.id in self.spam_counter: self.spam_counter[ctx.author.id] += 1 else: self.spam_counter[ctx.author.id] = 1 # Load the spam log channel. log_channel = await self.bot.find_channel( self.bot.core.settings.ids.exclusions_and_spam_channel) # Check if the user has been continually spamming. if self.spam_counter[ctx.author.id] >= 5: self.bot.db_session.add(ExcludedUser(user_id=ctx.author.id)) self.bot.db_session.commit() del self.bot.excluded_user_cache[ctx.author.id] del self.spam_counter[ctx.author.id] await log_channel.send( "", embed=RPANEmbed( title="Auto Ban Issued", description= "A user has been banned due to the amount of cooldowns they've received.", colour=0x800000, fields={ "User": f"{ctx.author} ({ctx.author.id})", "Guild": f"{ctx.guild.name} ({ctx.guild.id})", "Guild Owner": f"{ctx.guild.owner}\n({ctx.guild.owner_id})", }, ), ) else: await log_channel.send( "", embed=RPANEmbed( title="Spam Note", description="A user has been marked as spamming.", colour=0xFFFF00, fields={ "User": f"{ctx.author} ({ctx.author.id})", "Guild": f"{ctx.guild.name} ({ctx.guild.id})", "Guild Owner": f"{ctx.guild.owner}\n({ctx.guild.owner_id})", }, ), ) # Raise the exception that the user is on cooldown. raise GlobalCooldownFailure else: if ctx.author.id in self.spam_counter: self.spam_counter[ctx.author.id] # Start typing before executing the command. await ctx.trigger_typing()
async def on_command_error(self, ctx, error: Exception) -> None: # Ignore if the exception is command not found. if isinstance(error, CommandNotFound): return # Log the exception if it isn't in the exclusion list. print(error) if self.bot.core.sentry: exclusion_list = [ BadArgument, BotMissingPermissions, CheckFailure, ExcludedUserBlocked, MissingPermissions, MissingRequiredArgument, GlobalCooldownFailure ] if not any([ isinstance(error, excluded_error) for excluded_error in exclusion_list ]): self.bot.core.sentry.capture_exception(error) # Return if there is already an error handler for this command. cmd = ctx.command if hasattr(cmd, "on_error"): return # Don't send error messages for some exceptions. if isinstance(error, ExcludedUserBlocked) or isinstance( error, GlobalCooldownFailure): return # Send an error message to other exceptions. if isinstance(error, BotMissingPermissions): missing_perms = ", ".join(error.missing_perms) if "embed_links" not in missing_perms: await ctx.send( "", embed=RPANEmbed( title="Missing Permissions", description= f"The bot is missing the following permissions that are required for this command:\n{missing_perms}", colour=0x8B0000, ), ) else: await ctx.send( dedent(f""" **Missing Permissions** The bot is missing the following permissions that are required for this command (some required for core functionality): ``{missing_perms}`` """), ) elif isinstance(error, MissingPermissions): missing_perms = ", ".join(error.missing_perms) await ctx.send( "", embed=RPANEmbed( title="Insufficient Permissions", description= f"You require the following guild permission(s) to use this command: ``{missing_perms}``", colour=0x8B0000, ), ) elif isinstance(error, DeveloperCheckFailure): await ctx.send( "", embed=RPANEmbed( title="Insufficient Permissions", description= "This command can only be accessed by the bot's core developers.", colour=0x8B0000, ), ) elif isinstance(error, CheckFailure): await ctx.send( "", embed=RPANEmbed( title="Insufficient Permissions", description="This command cannot run here.", colour=0x8B0000, ), ) elif isinstance(error, BadArgument): await ctx.send( "", embed=RPANEmbed( title="You've input something wrong.", description= f"The following argument was input incorrectly: '{error.param}'", colour=0x8B0000, ), ) elif isinstance(error, MissingRequiredArgument): await ctx.send( "", embed=RPANEmbed( title="Missing Argument", description=dedent(f""" A required argument for this command is missing. **Usage:** ``{cmd.name} {cmd.signature}`` **Missing Argument:** {error.param.name} **Argument Key** ``[argument]`` optional argument ``<argument>`` required argument """.strip()), colour=0x8B0000, bot=self.bot, message=ctx.message, ), ) elif isinstance(error, CommandInvokeError): error_log_channel = await self.bot.fetch_channel( self.bot.core.settings.ids.error_channel) await error_log_channel.send( "", embed=RPANEmbed( title="Command Error Report", fields={ "Error": f"{error.original.__class__.__name__}: {error.original}", "Arguments": "\n".join(error.original.args), "Invoking User": f"{ctx.author} ({ctx.author.id})", "Invoking Message": ctx.message.content, "Guild": f"{ctx.guild.name} ({ctx.guild.id})", }, colour=0x8B0000, ), ) await ctx.send( "", embed=RPANEmbed( title="An error occurred while executing that command.", description= "But don't worry! A report has been sent to the bot's developers.", colour=0x8B0000, fields={ "Support Guild": f"[Click here to join the bot support guild.]({self.bot.core.settings.links.support_guild})", }, bot=self.bot, message=ctx.message, ), ) else: error_log_channel = await self.bot.fetch_channel( self.bot.core.settings.ids.error_channel) await error_log_channel.send( "", embed=RPANEmbed( title="Error Report", fields={ "Error": f"{error.__class__.__name__}: {error}", "Invoking User": f"{ctx.author} ({ctx.author.id})", "Invoking Message": ctx.message.content, "Guild": f"{ctx.guild.name} ({ctx.guild.id})", }, colour=0x8B0000, ), ) await ctx.send( "", embed=RPANEmbed( title="Something went wrong.", description= "But don't worry! A report has been sent to the bot's developers.", colour=0x8B0000, fields={ "Support Guild": f"[Click here to join the bot support guild.]({self.bot.core.settings.links.support_guild})", }, bot=self.bot, message=ctx.message, ), )
async def on_guild_join(self, guild) -> None: # Check that the guild isn't banned from the bot. is_banned = self.bot.db_session.query(ExcludedGuild).filter_by( guild_id=guild.id).first() if is_banned: self.exclusion_watch = guild.id log_channel = await self.bot.find_channel( self.bot.core.settings.ids.exclusions_and_spam_channel) await log_channel.send( "", embed=RPANEmbed( title="Excluded Guild Join Attempt", description="A banned guild attempted to add the bot.", fields={ "Guild Name": guild.name, "Guild ID": guild.id, "Owner": f"{guild.owner}\n({guild.owner_id})", }, thumbnail=guild.icon_url, ), ) await guild.leave() return # Check that the guild owner isn't banned from the bot. owner_is_banned = self.bot.db_session.query(ExcludedUser).filter_by( user_id=guild.owner_id).first() if owner_is_banned: self.exclusion_watch = guild.id log_channel = await self.bot.find_channel( self.bot.core.settings.ids.exclusions_and_spam_channel) await log_channel.send( "", embed=RPANEmbed( title="Excluded User Bot Invite", description= "A banned bot user attempted to add the bot to their guild.", fields={ "Guild Name": guild.name, "Guild ID": guild.id, "Owner": f"{guild.owner}\n({guild.owner_id})", }, thumbnail=guild.icon_url, ), ) await guild.leave() return # Log that the bot has joined a new guild. log_channel = await self.bot.find_channel( self.bot.core.settings.ids.join_leave_channel) await log_channel.send( "", embed=RPANEmbed( title="Guild Joined", description=f"Now in {len(self.bot.guilds)} guilds.", colour=0x32CD32, fields={ "Guild Name": guild.name, "Guild ID": guild.id, "Owner": f"{guild.owner}\n({guild.owner_id})", "Member Count": guild.member_count, }, thumbnail=guild.icon_url, ), )
async def send_bot_help(self, mapping) -> None: help_embed = RPANEmbed( title="RPANBot Command List", description=dedent(f""" **Info** Type ``{self.clean_prefix}help (command name)`` for more info on a command. [Click here to view a more in-depth description of the commands.]({Settings().links.site_base}/commands) **Argument Key** [argument] | optional argument <argument> | required argument """.strip()), url=Settings().links.site_base + "/commands", user=self.context.author, bot=self.context.bot, message=self.context.message, ) def get_category(command): cog = command.cog return cog.qualified_name if cog is not None else "None" filtered = await self.filter_commands(self.context.bot.commands, sort=True, key=get_category) category_fields = {} for category, commands in groupby(filtered, key=get_category): if category == "None": continue commands = sorted(commands, key=lambda cmd: len(cmd.name)) category_cmds = "" for cmd in commands: cmd_line = "\n\n" + cmd.name if cmd.signature: cmd_line += " " + cmd.signature if cmd.help: cmd_line += ":\n · " + cmd.help category_cmds += cmd_line category_fields[category] = self.wrap_listing(category_cmds) category_fields = sorted(category_fields.items(), key=lambda cat: len(cat[1]), reverse=True) for category_info in category_fields: help_embed.add_field( name=category_info[0], value=category_info[1], inline=False, ) await self.send_help_message( channel=self.get_destination(), text="", embed=help_embed, )
async def report(self, ctx, *, type: str = None) -> None: """ Get information on how to report policy breaking content. """ if type is not None: type = type.lower() report_tables = { "promoting hate based on identity or vulnerability": "https://www.reddit.com/report?reason=its-promoting-hate-based-on-identity-or-vulnerability", "spam": "https://www.reddit.com/report?reason=this-is-spam", "misinformation": "https://www.reddit.com/report?reason=this-is-misinformation", "targeted harassment": "https://www.reddit.com/report?reason=its-targeted-harassment", "violence or physical harm": "https://www.reddit.com/report?reason=it-threatens-violence-or-physical-harm", "rude, vulgar, or offensive": "https://www.reddit.com/report?reason=its-rude-vulgar-or-offensive", "abusing the report button": "https://www.reddit.com/report?reason=its-abusing-the-report-button", "copyright infringements": "https://www.reddit.com/report?reason=it-infringes-my-copyright", "trademark infringement": "https://www.reddit.com/report?reason=it-infringes-my-trademark-rights", "personal information": "https://www.reddit.com/report?reason=its-personal-and-confidential-information", "sexualizing minors": "https://www.reddit.com/report?reason=its-sexual-or-suggestive-content-involving-minors", "involuntary pornography": "https://www.reddit.com/report?reason=its-involuntary-pornography", "ban evasion": "https://www.reddit.com/report?reason=its-ban-evasion", "vote manipulation": "https://www.reddit.com/report?reason=its-vote-manipulation", "prohibited goods or services": "https://www.reddit.com/report?reason=its-a-transaction-for-prohibited-goods-or-services", "impersonation": "https://www.reddit.com/report?reason=it-impersonates-me", "netzdg report": "https://www.reddit.com/report?reason=report-this-content-under-netzdg", "self-harm or suicide": "https://www.reddit.com/report?reason=someone-is-considering-suicide-or-serious-self-harm", "appeal a suspension": "https://www.reddit.com/appeals", "appeal a subreddit ban": "https://www.reddit.com/message/compose?to=/r/reddit.com&subject=Subreddit+Ban+Appeal", "dmca": "https://www.redditinc.com/policies/user-agreement#text-content8", } if type is not None and type not in report_tables.keys(): aliases = { "rude": "rude, vulgar, or offensive", "vulgar": "rude, vulgar, or offensive", "offensive": "rude, vulgar, or offensive", "copyright infringement": "copyright infringements", "copyright": "copyright infringements", "trademark": "trademark infringement", "dox": "personal information", "harassment": "targeted harassment", "discrimination": "promoting hate based on identity or vulnerability", "racism": "promoting hate based on identity or vulnerability", "hate": "promoting hate based on identity or vulnerability", "self-harm": "self-harm or suicide", "netzdg": "netzdg report", "violence": "violence or physical harm", "harm": "violence or physical harm", } if type in aliases.keys(): type = aliases[type] embed = RPANEmbed( title="Click here to go to the report page.", description=dedent(""" If you need to report a user, comment, or post, please go to https://reddit.com/report (or click above) and report the content there. If you cannot find a category for the content you want to report, then [send a message to r/reddit.com](https://reddit.com/message/compose/?to=/r/reddit.com) with the content. """.strip()), url="https://reddit.com/report", user=ctx.author, ) if type in report_tables.keys(): embed.url = report_tables[type] embed.description = dedent(""" Visit the following link (or click above) to report for '{type}': {link} """.strip()).format(type=type.capitalize(), link=embed.url) await ctx.send("", embed=embed)
async def viewstream(self, ctx, username: str) -> None: """ Get the current or last stream of a specified user. """ broadcasts = self.bot.core.strapi.get_broadcasts() if broadcasts is None: await ctx.send( "", embed=RPANEmbed( title="View Stream", description= "There was a problem with fetching the broadcasts, please try again later.", colour=0xD2D219, user=ctx.author, bot=self.bot, message=ctx.message), ) return broadcast = broadcasts.has_streamer(name=username) if broadcast: await ctx.send( "", embed=RPANEmbed( title= f"u/{broadcast.author_name}'s Current Broadcast (Live)", fields={ "Title": broadcast.title, "Author": f"u/{broadcast.author_name}", "Subreddit": f"r/{broadcast.subreddit_name}", "RPAN Rank": f"{broadcast.global_rank}/{broadcast.total_streams}", "Current Viewers": broadcast.continuous_watchers, "Unique Viewers": broadcast.unique_watchers, }, url=broadcast.url, thumbnail=broadcast.thumbnail, user=ctx.author, bot=self.bot, message=ctx.message, ), ) else: broadcast = self.bot.core.strapi.get_last_broadcast(username) if broadcast: await ctx.send( "", embed=RPANEmbed( title=f"u/{broadcast.author_name}'s Last Broadcast", fields={ "Title": broadcast.title, "Author": f"u/{broadcast.author_name}", "Subreddit": f"r/{broadcast.subreddit_name}", "Status": "Off-Air", "Broadcasted": self.bot.core.strapi.format_broadcast_timestamp( broadcast.published_at).strftime( "%d/%m/%Y at %H:%M UTC"), "Unique Viewers": broadcast.unique_watchers, }, url=broadcast.url, thumbnail=broadcast.thumbnail, user=ctx.author, bot=self.bot, message=ctx.message, ), ) else: await ctx.send("No last broadcast was found for that user.")