async def logset_channel(self, ctx: commands.Context, module: str, channel: discord.TextChannel = None): """Set the log channel for a module Passing no log channel effectively acts as disabling the module """ module = await retrieve_module(ctx, module) if channel and not channel.permissions_for(ctx.guild.me).send_messages: await ctx.send( warning(i18n("I'm not able to send messages in that channel"))) return await module.module_config.set_raw("_log_channel", value=getattr(channel, "id", None)) if channel: await ctx.send( tick( i18n("Module **{module}** will now log to {channel}"). format(module=module.friendly_name, channel=channel.mention))) else: await ctx.send( tick( i18n( "The log channel for module **{module}** has been cleared" ).format(module=module.friendly_name)))
async def requirerole(self, ctx: commands.Context, *roles: RoleTuple): """Require one of any specific roles to use the bot in the current guild To require a member to __not__ have one or more roles, you can use `~` before the role name to treat it as a blacklisted role. If a role name has `~` at the start of it's name, you can escape it with a backslash (`\`) character. Blacklisted roles override any possible whitelisted roles a member may have. Role names are case sensitive. If a role has spaces in it's name, wrap it in quotes. Passing no roles removes any currently set role requirements. The guild owner and members with the Administrator permission always bypass these requirements, regardless of roles.""" seen = SeenSet() roles = tuple( (k, v) for k, v in roles if seen.mark_seen(k)) # type: Tuple[Tuple[discord.Role, bool]] whitelist = tuple(r for r, v in roles if v) # type: Tuple[discord.Role] blacklist = tuple(r for r, v in roles if not v) # type: Tuple[discord.Role] if ctx.guild.default_role in roles: await ctx.send( warning( _("I can't set a role requirement with the guild's default role - " "if you'd like to clear your current role requirements, " "you can execute this command with no arguments to do so." ))) return await self.config.guild(ctx.guild).roles.set({ "whitelist": [x.id for x in whitelist], "blacklist": [x.id for x in blacklist] }) if not roles: await ctx.send(tick(_("Cleared currently set role requirements."))) return whitelist = ", ".join( escape(str(x), mass_mentions=True, formatting=True) for x in whitelist) blacklist = ", ".join( escape(str(x), mass_mentions=True, formatting=True) for x in blacklist) msg = _( "A member will now need to pass the following checks to use my commands:\n\n" ) if whitelist: msg += _("**Any of the following roles:**\n{roles}").format( roles=whitelist) if whitelist and blacklist: msg += "\n\n" if blacklist: msg += _("**None of the following roles:**\n{roles}").format( roles=blacklist) await ctx.send(tick(msg))
async def _add_remove(self, ctx: commands.Context, role: discord.Role, *, rm: bool = False): if role.is_default(): raise commands.BadArgument( "cannot make a server's default role mentionable") async with self.config.guild(ctx.guild).roles() as roles: if rm is False: if role.id in roles: await ctx.send( warning(_("That role is already mentionable"))) return roles.append(role.id) else: if role.id not in roles: await ctx.send( warning(_("That role is not currently mentionable"))) return roles.remove(role.id) await ctx.send( escape( tick( _("`{}` is now allowed to be mentioned").format(role. name)), mass_mentions=True, ) if rm is False else escape( tick( _("`{}` is no longer allowed to be mentioned"). format(role.name)), mass_mentions=True, ))
async def cogwhitelist_remove(self, ctx: commands.Context, cog: str, guild_id: int = None): """Removes a cog or guild from the list of whitelisted cogs/guilds If a guild ID is specified, it's removed from the specified cogs' list of allowed guilds """ cog = cog.lower() proper_name = cog_name(self.bot, cog) or cog async with self.config.cogs() as cogs: if cog not in cogs: return await fmt( ctx, warning(_("**{cog}** doesn't currently require a whitelist to use")), cog=proper_name, ) if guild_id: if guild_id not in cogs[cog]: return await fmt( ctx, warning(_("That guild isn't allowed to use **{cog}**")), cog=proper_name, ) cogs[cog].remove(guild_id) await fmt( ctx, tick(_("That guild is no longer allowed to use **{cog}**.")), cog=proper_name, ) else: cogs.pop(cog) await fmt( ctx, tick(_("**{cog}** no longer requires a whitelist to use")), cog=proper_name )
async def starboard_channel(self, ctx: Context, channel: discord.TextChannel = None): """Set or clear the server's starboard channel""" if channel and channel.guild.id != ctx.guild.id: await ctx.send(error(i18n("That channel isn't in this server"))) return await ctx.starboard.channel.set(getattr(channel, "id", None)) if channel is None: await ctx.send(tick(i18n("Cleared the current starboard channel"))) else: await ctx.send( tick( i18n("Set the starboard channel to {}").format( channel.mention)))
async def change_creator(self): attribute_to = await self._prompt_user_input( i18n("Which user would you like to make the new quote creator?"), converter=commands.MemberConverter, ) self.quote.message_author = attribute_to await self.send(tick(i18n("Attributed quote to **{}**.").format(str(attribute_to))))
async def rndactivity_delay( self, ctx: commands.Context, *, duration: FutureTime.converter(min_duration=_min_duration, strict=True, max_duration=None) ): """Set the amount of time required to pass to change the bot's playing status Duration can be formatted in any of the following ways: • `5m` • `1h3.5m` • `5 minutes` • `1 hour 3.5 minutes` Minimum duration between changes is 5 minutes. Default delay is every 10 minutes. """ await self.config.delay.set(duration.total_seconds()) await fmt( ctx, tick( _( "Set time between status changes to {duration}.\nThis change will take effect " "after the next status change." ) ), duration=duration.format(), )
async def stars_unignore(self, ctx: Context, name: str, *, reason: str = None): """Remove a channel or member from the server's ignore list `reason` is only used if unignoring a member """ item = await resolve_any(ctx, name, commands.TextChannelConverter, commands.MemberConverter) if isinstance(item, discord.Member) and not await hierarchy_allows( self.bot, ctx.author, item): await ctx.send( error( i18n( "You aren't allowed to remove that member from the ignore list" ))) return elif (isinstance(item, discord.TextChannel) and item == await ctx.starboard.resolve_starboard()): await ctx.send( warning( i18n( "The starboard channel is always ignored and cannot be manually " "ignored nor unignored."))) return if not await ctx.starboard.is_ignored(item): await ctx.send( warning( i18n( "That user is not already ignored from using this server's starboard" ) if isinstance(item, discord.Member) else i18n( "That channel is not already being ignored"))) return await ctx.starboard.unignore(item) await ctx.send( tick( i18n("**{}** is no longer ignored from this server's starboard" )).format(item)) if isinstance(item, discord.Member): try: await modlog.create_case( bot=self.bot, guild=ctx.guild, created_at=ctx.message.created_at, action_type="starboardunblock", user=item, moderator=ctx.author, reason=reason, until=None, channel=None, ) except RuntimeError: pass
async def stars_unhide(self, ctx: Context, message: StarboardMessage): """Unhide a previously hidden message""" if message.hidden is False: return await ctx.send( error(i18n("That message hasn't been hidden"))) message.hidden = False await ctx.send( tick( i18n("The message sent by **{}** is no longer hidden.").format( message.author)))
async def stars_hide(self, ctx: Context, message: StarboardMessage): """Hide a message from the starboard""" if message.hidden: return await ctx.send(error(i18n("That message is already hidden")) ) message.hidden = True await ctx.send( tick( i18n("The message sent by **{}** is now hidden.").format( message.author)))
async def starboard_selfstar(self, ctx: Context, toggle: bool = None): """Toggles if members can star their own messages Member statistics do not respect this setting, and always ignore self-stars. """ toggle = ( not await ctx.starboard.selfstar()) if toggle is None else toggle await ctx.starboard.selfstar.set(toggle) await ctx.send( tick( i18n("Members can now star their own messages") if toggle else i18n("Members can no longer star their own messages")))
async def logset_reset(self, ctx: commands.Context): """Reset the server's log settings""" if await confirm( ctx, content=warning( i18n( "Are you sure you want to reset this server's log settings?" ))): await config.guild(ctx.guild).clear() await ctx.send(tick(i18n("Server log settings have been reset."))) else: await ctx.send(i18n("Okay then."))
async def delete_quote(self): if await confirm( self.ctx, content=warning( i18n( "Are you sure you want to delete this quote?\n\n" "Unless you have a time machine, **this action is irreversible!**" ) ), ): await self.quote.delete() await self.send(tick(i18n("Quote deleted."))) raise StopLoop
async def starboardset_v2_import(self, ctx: Context, mongo_uri: str): """Import Red v2 instance data Please note that this is not officially supported, and this import tool is provided as-is. Only messages are imported currently; server settings are not imported, and must be setup again. In most cases, `mongodb://localhost:27017` will work just fine if you're importing a local v2 instance. """ if not await confirm( ctx, timeout=90.0, content=i18n( "**PLEASE READ THIS! UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T!**" "\n" "Importing from v2 instances is not officially supported, due to the vast" " differences in backend data storage schemas. This command is provided as-is," " with no guarantee of maintenance nor stability." "\n\n" "Server settings will not be imported and must be setup again." "\n" "Starred messages data will be imported, but if a message is present in" " my current data set, **it will be overwritten** with the imported data." "\n\n\n" "Please react with \N{WHITE HEAVY CHECK MARK} to confirm that you wish to continue." ), ): await ctx.send(i18n("Import cancelled."), delete_after=30) return tmp = await ctx.send( i18n("Importing data... (this could take a while)")) try: async with ctx.typing(): await v2_migration.import_data(self.bot, mongo_uri) except v2_migration.NoMotorError: await fmt( ctx, error( i18n( "Motor is not installed; cannot import v2 data.\n\n" "Please do `{prefix}pipinstall motor` and re-attempt the import." )), ) else: await ctx.send(tick(i18n("Imported successfully."))) finally: await tmp.delete()
async def _command(self, ctx: commands.Context, toggle: bool = None): # noqa if toggle is None: # noinspection PyTypeChecker toggle = not await DummyModule().config.guild(ctx.guild ).ignore.guild() # noinspection PyTypeChecker await DummyModule().config.guild(ctx.guild ).ignore.guild.set(toggle) await ctx.send( tick( i18n("Now ignoring the current server") if toggle else i18n("No longer ignoring the current server")))
async def logset_module(self, ctx: commands.Context, module: str, *settings: str): """Get or set a module's settings""" module = await retrieve_module(ctx, module) if not settings: await ctx.send(embed=await module.config_embed()) else: await module.toggle_options(*settings) await ctx.send( content=tick( i18n("Updated settings for module **{}**").format( module.friendly_name)), embed=await module.config_embed(), )
async def timedrole_add( self, ctx: commands.Context, member: discord.Member, duration: FutureTime.converter(strict=True, min_duration=2 * 60), *roles: discord.Role): """Add one or more roles to a user for a set amount of time. You can give a user up to 10 roles at once - this doesn't limit the total amount of timed roles a member can have, however. Examples for duration: `5d`, `1mo`, `1y2mo3w4d5m6s` Abbreviations: `s` for seconds, `m` for minutes, `h` for hours, `d` for days, `w` for weeks, `mo` for months, `y` for years. Any longer abbreviation is accepted. `m` assumes minutes instead of months. One month is counted as 30 days, and one year is counted as 365 days. All invalid abbreviations are ignored. Minimum duration for a timed role is two minutes. Any roles that are above the top role of either the command issuer or the bot are silently filtered out. """ roles = [ x for x in roles if not any([ x in member.roles, x >= ctx.author.top_role, x >= ctx.me.top_role ]) ] if not roles or len(roles) > 10: await ctx.send_help() return for role in roles: role = await TempRole.create(member, role=role, duration=duration, added_by=ctx.author) await role.apply_role() await fmt( ctx, tick( _("Added role(s) {roles} to member **{member}** for **{duration}** successfully." )), roles=", ".join([bold(x.name) for x in roles]), member=member, duration=duration.format(), )
async def rndactivity_clear(self, ctx: commands.Context): """Clears all set statuses""" amount = len(await self.config.statuses()) if await confirm( ctx, content=_( "Are you sure you want to clear {amount} statuses?\n\n" "**This action is irreversible!**" ).format(amount=amount), ): await self.config.statuses.set([]) await self.bot.change_presence(activity=None, status=self.bot.guilds[0].me.status) await fmt(ctx, tick(_("Successfully removed {amount} status strings.")), amount=amount) else: await fmt(ctx, _("Okay then."))
async def _add_status(self, ctx: commands.Context, game: str, *, game_type: int = 0): try: self.format_status({"type": game_type, "game": game}) except KeyError as e: await fmt( ctx, warning( _( "Parsing that status failed \N{EM DASH} {placeholder} is not a valid " "placeholder" ) ), placeholder=str(e), ) else: async with self.config.statuses() as statuses: statuses.append({"type": game_type, "game": game}) await fmt(ctx, tick(_("Added status **#{id}** successfully.")), id=len(statuses))
async def rndactivity_remove(self, ctx: commands.Context, status: int): """Remove one or more statuses by their IDs You can retrieve the ID for a status with [p]rndactivity list """ async with self.config.statuses() as statuses: if len(statuses) < status: return await fmt(ctx, warning(_("No status with the ID `{id}` exists")), id=status) removed = statuses.pop(status - 1) if not statuses: await self.bot.change_presence( activity=None, status=getattr(ctx.me, "status", None) ) removed = escape(self.format_status(removed, return_formatted=False)[0], mass_mentions=True) await fmt( ctx, tick(_("Removed status **#{id}** (`{status}`) successfully.")), id=status, status=removed, )
async def cogwhitelist_add(self, ctx: commands.Context, cog: str, server_id: int = None): """Add a cog and/or guild to the list of whitelisted cogs/servers If a server ID is specified, the guild is added to the cog's list of allowed servers Cogs are handled on a case-insensitive name basis, and as such if you replace a cog with another with the same name, it will keep the same settings as any cog previously setup with that name, regardless of capitalization. """ proper_name = cog_name(self.bot, cog) if not cog: return await ctx.send(_("No cog with that name is currently loaded")) cog = str(proper_name).lower() async with self.config.cogs() as cogs: if cog not in cogs: cogs[cog] = [] if not server_id: await fmt(ctx, _("**{cog}** now requires a whitelist to use"), cog=proper_name) elif not server_id: return await fmt( ctx, warning(_("**{cog}** already requires a whitelist to use")), cog=proper_name, ) if server_id: guild = self.bot.get_guild(server_id) if not guild: await ctx.send(warning(_("I couldn't find a server with that ID"))) if guild.id in cogs[cog]: return await fmt( ctx, warning(_("That server is already allowed to use **{cog}**")), cog=proper_name, ) cogs[cog].append(guild.id) await fmt( ctx, tick(_("**{guild_name}** is now allowed to use **{cog}**")), guild_name=escape(guild.name, mass_mentions=True, formatting=True), cog=proper_name, )
async def change_content(self): content = await self._prompt_user_input(i18n("Please respond with the new quote content")) self.quote.text = content await self.send(tick(i18n("Changed quote content successfully.")))
async def exit(self, save: bool = False): if save is True: await self.quote.save() await self.send(tick(i18n("Your changes have been carefully recorded and saved."))) raise StopLoop
async def stars_update(self, ctx: Context, message: StarboardMessage): """Forcefully update a starboard message""" await message.update_cached_message() await message.update_starboard_message() await ctx.send(tick(i18n("Message has been updated.")))