import logging import random import string from typing import Optional, cast import discord from redbot import VersionInfo, version_info from redbot.core import Config, checks, commands from redbot.core.i18n import Translator, cog_i18n default_settings = { "ENABLED": False, "ROLE": [], "AGREE_CHANNEL": None, "AGREE_MSG": None, "AGREE_KEY": None, "DELETE_KEY": False, } log = logging.getLogger("red.Trusty-cogs.autorole") _ = Translator("Autorole", __file__) listener = getattr(commands.Cog, "listener", None) # red 3.0 backwards compatibility support if listener is None: # thanks Sinbad def listener(name=None): return lambda x: x @cog_i18n(_) class Autorole(commands.Cog): """ Autorole commands. Rewritten for V3 from https://github.com/Lunar-Dust/Dusty-Cogs/blob/master/autorole/autorole.py """ __author__ = ["Lunar-Dust", "TrustyJAID"] __version__ = "1.3.2" def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, 45463543548) self.config.register_guild(**default_settings) self.users = {} def format_help_for_context(self, ctx: commands.Context) -> str: """ Thanks Sinbad! """ pre_processed = super().format_help_for_context(ctx) return f"{pre_processed}\n\nCog Version: {self.__version__}" async def red_delete_data_for_user(self, **kwargs): """ Nothing to delete """ return async def _no_perms(self, channel: Optional[discord.TextChannel] = None) -> None: m = _( "It appears that you haven't given this " "bot enough permissions to use autorole. " 'The bot requires the "Manage Roles" and ' 'the "Manage Messages" permissions in' "order to use autorole. You can change the " 'permissions in the "Roles" tab of the ' "guild settings." ) if channel is None: log.info(m) return if channel.permissions_for(channel.guild.me).send_messages: await channel.send(m) else: log.info(m + _("\n I also don't have permission to speak in #") + channel.name) async def get_colour(self, channel: discord.TextChannel) -> discord.Colour: try: return await self.bot.get_embed_colour(channel) except AttributeError: if await self.bot.db.guild(channel.guild).use_bot_color(): return channel.guild.me.colour else: return await self.bot.db.color() @commands.Cog.listener() async def on_message(self, message: discord.Message) -> None: guild = message.guild if not guild: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return user = cast(discord.Member, message.author) channel = message.channel agree_channel = cast( discord.TextChannel, guild.get_channel(await self.config.guild(guild).AGREE_CHANNEL()) ) if guild is None: return if agree_channel is None: return if channel.id != agree_channel.id: return if user.bot: return if user.id in self.users: if not guild.me.guild_permissions.manage_roles: await self._no_perms(agree_channel) return if self.users[user.id]["key"].lower() in message.content.lower(): perms = agree_channel.permissions_for(guild.me) roles_id = await self.config.guild(guild).ROLE() roles = [role for role in guild.roles if role.id in roles_id] for role in roles: await user.add_roles(role, reason=_("Agreed to the rules")) if perms.manage_messages and await self.config.guild(guild).DELETE_KEY(): try: await message.delete() except Exception: pass if self.users[user.id]["message"].guild: try: await self.users[user.id]["message"].delete() except Exception: pass elif perms.add_reactions: await message.add_reaction("✅") del self.users[user.id] async def _agree_maker(self, member: discord.Member) -> None: guild = member.guild self.last_guild = guild # await self._verify_json(None) key = await self.config.guild(guild).AGREE_KEY() if key is None: key = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) # <3 Stackoverflow http://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python/23728630#23728630 ch = cast( discord.TextChannel, guild.get_channel(await self.config.guild(guild).AGREE_CHANNEL()) ) msg = await self.config.guild(guild).AGREE_MSG() if msg is None: msg = "{mention} please enter {key} in {channel}" try: msg = msg.format( key=key, member=member, name=member.name, mention=member.mention, guild=guild.name, channel=ch.mention, ) except Exception: log.error("Error formatting agreement message", exc_info=True) try: msg = await member.send(msg) except discord.Forbidden: msg = await ch.send(msg) except discord.HTTPException: return self.users[member.id] = {"key": key, "message": msg} async def _auto_give(self, member: discord.Member) -> None: guild = member.guild roles_id = await self.config.guild(guild).ROLE() roles = [role for role in guild.roles if role.id in roles_id] if not guild.me.guild_permissions.manage_roles: await self._no_perms() return for role in roles: await member.add_roles(role, reason=_("Joined the server")) @commands.Cog.listener() async def on_member_join(self, member: discord.Member) -> None: if "h0nde" in member.name or "h0nda" in member.name: await member.ban(reason="Anti-spam measure.") guild = member.guild if await self.config.guild(guild).ENABLED(): if await self.config.guild(guild).AGREE_CHANNEL() is not None: await self._agree_maker(member) elif member.guild.name != "BibleBot": # Immediately give the new user the role await self._auto_give(member) @commands.Cog.listener() async def on_member_update(self, before: discord.Member, after: discord.Member) -> None: if before.guild.name == "BibleBot": if before.pending == True and after.pending == False: await after.add_roles(before.guild.get_role(366901317924290562)) @commands.guild_only() @commands.group(name="autorole") @commands.bot_has_permissions(manage_roles=True) async def autorole(self, ctx: commands.Context) -> None: """ Change settings for autorole Requires the manage roles permission """ pass @autorole.command(name="info") async def autorole_info(self, ctx: commands.Context) -> None: """ Display current autorole info """ guild = ctx.message.guild enabled = await self.config.guild(guild).ENABLED() roles = await self.config.guild(guild).ROLE() msg = await self.config.guild(guild).AGREE_MSG() key = await self.config.guild(guild).AGREE_KEY() ch_id = await self.config.guild(guild).AGREE_CHANNEL() delete = await self.config.guild(guild).DELETE_KEY() channel = guild.get_channel(ch_id) chn_name = channel.name if channel is not None else "None" chn_mention = channel.mention if channel is not None else "None" role_name_str = ", ".join(role.mention for role in guild.roles if role.id in roles) if not role_name_str: role_name_str = "None" if ctx.channel.permissions_for(ctx.me).embed_links: embed = discord.Embed(colour=await self.get_colour(ctx.channel)) embed.set_author(name=_("Autorole settings for ") + guild.name) embed.add_field(name=_("Current autorole state: "), value=str(enabled)) embed.add_field(name=_("Current Roles: "), value=str(role_name_str)) if msg: embed.add_field(name=_("Agreement message: "), value=str(msg)) if key: embed.add_field(name=_("Agreement key: "), value=str(key)) if channel: embed.add_field(name=_("Agreement channel: "), value=str(chn_mention)) await ctx.send(embed=embed) else: send_msg = ( "```" + _("Current autorole state: ") + f"{enabled}\n" + _("Current Roles: ") + f"{role_name_str}\n" + _("Agreement message: ") + f"{msg}\n" + _("Agreement key: ") + f"{key}\n" + _("Delete Agreement: ") + f"{delete}\n" + _("Agreement channel: ") + f"{chn_name}" + "```" ) await ctx.send(send_msg) @autorole.command() @checks.admin_or_permissions(manage_roles=True) async def toggle(self, ctx: commands.Context) -> None: """ Enables/Disables autorole """ guild = ctx.message.guild if await self.config.guild(guild).ROLE() is None: msg = _("You haven't set a " "role to give to new users!") await ctx.send(msg) else: if await self.config.guild(guild).ENABLED(): await self.config.guild(guild).ENABLED.set(False) await ctx.send(_("Autorole is now disabled.")) else: await self.config.guild(guild).ENABLED.set(True) await ctx.send(_("Autorole is now enabled.")) @autorole.command(name="add", aliases=["role"]) @checks.admin_or_permissions(manage_roles=True) async def role(self, ctx: commands.Context, *, role: discord.Role) -> None: """ Add a role for autorole to assign. You can use this command multiple times to add multiple roles. """ guild = ctx.message.guild roles = await self.config.guild(guild).ROLE() if ctx.author.top_role < role: msg = _( " is higher than your highest role. " "You can't assign autoroles higher than your own" ) await ctx.send(role.name + msg) if role.id in roles: await ctx.send(role.name + _(" is already in the autorole list.")) return if guild.me.top_role < role: msg = _(" is higher than my highest role" " in the Discord hierarchy.") await ctx.send(role.name + msg) return roles.append(role.id) await self.config.guild(guild).ROLE.set(roles) await ctx.send(role.name + _(" role added to the autorole.")) @autorole.command() @checks.admin_or_permissions(manage_roles=True) async def remove(self, ctx: commands.Context, *, role: discord.Role) -> None: """ Remove a role from the autorole. """ guild = ctx.message.guild roles = await self.config.guild(guild).ROLE() if role.id not in roles: await ctx.send(role.name + _(" is not in the autorole list.")) return roles.remove(role.id) await self.config.guild(guild).ROLE.set(roles) await ctx.send(role.name + _(" role removed from the autorole.")) @autorole.group() @checks.admin_or_permissions(manage_roles=True) async def agreement(self, ctx: commands.Context) -> None: """ Set the channel and message that will be used for accepting the rules. `channel` is the channel they must type the key in to get the role. `key` is the message they must type to gain access and must be in quotes. `msg` is the message DM'd to them when they join. `{key}` must be included in the message so a user knows what to type in the channel. Optional additions to the message include: `{channel}` Mentions the channel where they must include the agreement message. `{mention}` Mentions the user incase they have DM permissions turned off this should be used. `{name}` Says the member name if you don't want to ping them. `{guild}` Says the servers current name. Entering nothing will disable these. """ pass @agreement.command(name="channel") @checks.admin_or_permissions(manage_roles=True) async def set_agreement_channel( self, ctx: commands.Context, channel: discord.TextChannel = None ) -> None: """ Set the agreement channel Entering nothing will clear this. """ guild = ctx.message.guild if await self.config.guild(guild).ROLE() == []: await ctx.send(_("No roles have been set for autorole.")) return if not await self.config.guild(guild).ENABLED(): await ctx.send(_("Autorole has been disabled, enable it first.")) return if channel is None: await self.config.guild(guild).AGREE_CHANNEL.set(None) await ctx.send(_("Agreement channel cleared")) else: await self.config.guild(guild).AGREE_CHANNEL.set(channel.id) await ctx.send(_("Agreement channel set to ") + channel.mention) @agreement.command(name="delete") @checks.admin_or_permissions(manage_roles=True) async def set_agreement_delete(self, ctx: commands.Context) -> None: """ Toggle automatically deleting the agreement message. """ delete_key = await self.config.guild(ctx.guild).DELETE_KEY() await self.config.guild(ctx.guild).DELETE_KEY.set(not delete_key) if delete_key: await ctx.send(_("No longer automatically deleting agreement key.")) else: await ctx.send(_("Automatically deleting agreement key.")) @agreement.command(name="key") @checks.admin_or_permissions(manage_roles=True) async def set_agreement_key(self, ctx: commands.Context, *, key: str = None) -> None: """ Set the agreement key Entering nothing will clear this. """ guild = ctx.message.guild if await self.config.guild(guild).ROLE() == []: await ctx.send(_("No roles have been set for autorole.")) return if not await self.config.guild(guild).ENABLED(): await ctx.send(_("Autorole has been disabled, enable it first.")) return if key is None: await self.config.guild(guild).AGREE_KEY.set(None) await ctx.send(_("Agreement key cleared")) else: await self.config.guild(guild).AGREE_KEY.set(key) await ctx.send(_("Agreement key set to ") + key) @agreement.command(name="message", aliases=["msg"]) @checks.admin_or_permissions(manage_roles=True) async def set_agreement_msg(self, ctx: commands.Context, *, message: str = None) -> None: """ Set the agreement message `{key}` must be included in the message so a user knows what to type in the channel. Optional additions to the message include: `{channel}` Mentions the channel where they must include the agreement message. `{mention}` Mentions the user incase they have DM permissions turned off this should be used. `{name}` Says the member name if you don't want to ping them. `{guild}` Says the servers current name. Entering nothing will clear this. """ guild = ctx.message.guild if await self.config.guild(guild).ROLE() == []: await ctx.send(_("No roles have been set for autorole.")) return if not await self.config.guild(guild).ENABLED(): await ctx.send(_("Autorole has been disabled, enable it first.")) return if message is None: await self.config.guild(guild).AGREE_MSG.set(None) await ctx.send(_("Agreement message cleared")) else: await self.config.guild(guild).AGREE_MSG.set(message) await ctx.send(_("Agreement message set to ") + message) @agreement.command(name="setup") @checks.admin_or_permissions(manage_roles=True) async def agreement_setup( self, ctx: commands.Context, channel: discord.TextChannel = None, key: str = None, *, msg: str = None, ) -> None: """ Set the channel and message that will be used for accepting the rules. `channel` is the channel they must type the key in to get the role. `key` is the message they must type to gain access and must be in quotes. `msg` is the message DM'd to them when they join. `{key}` must be included in the message so a user knows what to type in the channel. Optional additions to the message include: `{channel}` Mentions the channel where they must include the agreement message. `{mention}` Mentions the user incase they have DM permissions turned off this should be used. `{name}` Says the member name if you don't want to ping them. `{guild}` Says the servers current name. Entering nothing will disable this. """ guild = ctx.message.guild if await self.config.guild(guild).ROLE() == []: await ctx.send(_("No roles have been set for autorole.")) return if not await self.config.guild(guild).ENABLED(): await ctx.send(_("Autorole has been disabled, enable it first.")) return if channel is None: await self.config.guild(guild).AGREE_CHANNEL.set(None) await self.config.guild(guild).AGREE_MSG.set(None) await self.config.guild(guild).AGREE_KEY.set(None) await ctx.send(_("Agreement channel cleared")) else: await self.config.guild(guild).AGREE_CHANNEL.set(channel.id) await self.config.guild(guild).AGREE_MSG.set(msg) await self.config.guild(guild).AGREE_KEY.set(key) await ctx.send(_("Agreement channel set to ") + channel.mention)