diff --git a/tools/tools.py b/tools/tools.py index eed752a..c9d6fe8 100644 --- a/tools/tools.py +++ b/tools/tools.py @@ -24,7 +24,7 @@ class Tools(commands.Cog): """Mod and Admin tools.""" async def red_delete_data_for_user(self, **kwargs): - """ Nothing to delete """ + """Nothing to delete""" return def __init__(self, bot): @@ -47,9 +47,7 @@ class Tools(commands.Cog): @access.command() async def compare(self, ctx, user: discord.Member, guild: int = None): - """Compare channel access with [user].""" - if user is None: - return + """Compare channel access with another user.""" if guild is None: guild = ctx.guild else: @@ -67,42 +65,35 @@ class Tools(commands.Cog): user_text_channels = [c for c in tcs if c.permissions_for(user).read_messages is True] user_voice_channels = [c for c in vcs if c.permissions_for(user).connect is True] - author_only_t = set(author_text_channels) - set( - user_text_channels - ) # text channels only the author has access to - author_only_v = set(author_voice_channels) - set( - user_voice_channels - ) # voice channels only the author has access to + # text channels only the author has access to + author_only_t = set(author_text_channels) - set(user_text_channels) + # voice channels only the author has access to + author_only_v = set(author_voice_channels) - set(user_voice_channels) - user_only_t = set(user_text_channels) - set(author_text_channels) # text channels only the user has access to - user_only_v = set(user_voice_channels) - set( - author_voice_channels - ) # voice channels only the user has access to + # text channels only the user has access to + user_only_t = set(user_text_channels) - set(author_text_channels) + # voice channels only the user has access to + user_only_v = set(user_voice_channels) - set(author_voice_channels) - common_t = list( - set([c for c in tcs]) - author_only_t - user_only_t - ) # text channels that author and user have in common - common_v = list( - set([c for c in vcs]) - author_only_v - user_only_v - ) # voice channels that author and user have in common + # text channels that author and user have in common + common_t = list(set([c for c in tcs]) - author_only_t - user_only_t) + # voice channels that author and user have in common + common_v = list(set([c for c in vcs]) - author_only_v - user_only_v) - msg = "```ini\n" - msg += "{} [TEXT CHANNELS IN COMMON]:\n\n{}\n\n".format(len(common_t), ", ".join([c.name for c in common_t])) - msg += "{} [TEXT CHANNELS {} HAS EXCLUSIVE ACCESS TO]:\n\n{}\n\n".format( - len(user_only_t), user.name.upper(), ", ".join([c.name for c in user_only_t]) - ) - msg += "{} [TEXT CHANNELS YOU HAVE EXCLUSIVE ACCESS TO]:\n\n{}\n\n\n".format( - len(author_only_t), ", ".join([c.name for c in author_only_t]) - ) - msg += "{} [VOICE CHANNELS IN COMMON]:\n\n{}\n\n".format(len(common_v), ", ".join([c.name for c in common_v])) - msg += "{} [VOICE CHANNELS {} HAS EXCLUSIVE ACCESS TO]:\n\n{}\n\n".format( - len(user_only_v), user.name.upper(), ", ".join([c.name for c in user_only_v]) - ) - msg += "{} [VOICE CHANNELS YOU HAVE EXCLUSIVE ACCESS TO]:\n\n{}\n\n".format( - len(author_only_v), ", ".join([c.name for c in author_only_v]) - ) - msg += "```" - for page in cf.pagify(msg, delims=["\n"], shorten_by=16): + text_common_access = ", ".join([c.name for c in common_t]) + text_user_exclusive_access = ", ".join([c.name for c in user_only_t]) + text_author_exclusive_access = ", ".join([c.name for c in author_only_t]) + voice_common_access = ", ".join([c.name for c in common_v]) + voice_user_exclusive_access = ", ".join([c.name for c in user_only_v]) + voice_author_exclusive_access = ", ".join([c.name for c in author_only_v]) + + msg = f"{len(common_t)} [TEXT CHANNELS IN COMMON]:\n\n{text_common_access}\n\n" + msg += f"{len(user_only_t)} [TEXT CHANNELS {user.name.upper()} HAS EXCLUSIVE ACCESS TO]:\n\n{text_user_exclusive_access}\n\n" + msg += f"{len(author_only_t)} [TEXT CHANNELS YOU HAVE EXCLUSIVE ACCESS TO]:\n\n{text_author_exclusive_access}\n\n\n" + msg += f"{len(common_v)} [VOICE CHANNELS IN COMMON]:\n\n{voice_common_access}\n\n" + msg += f"{len(user_only_v)} [VOICE CHANNELS {user.name.upper()} HAS EXCLUSIVE ACCESS TO]:\n\n{voice_user_exclusive_access}\n\n" + msg += f"{len(author_only_v)} [VOICE CHANNELS YOU HAVE EXCLUSIVE ACCESS TO]:\n\n{voice_author_exclusive_access}\n\n" + for page in cf.pagify(cf.box(msg, lang="ini"), delims=["\n"], shorten_by=16): await ctx.send(page) @access.command() @@ -122,13 +113,11 @@ class Tools(commands.Cog): return await ctx.send("User is not in that guild or I do not have access to that guild.") prefix = "You have" if user.id == ctx.author.id else user.name + " has" - msg = "```ini\n[{} access to {} out of {} text channels]\n\n".format( - prefix, len(can_access), len(text_channels) - ) - - msg += "[ACCESS]:\n{}\n\n".format(", ".join(can_access)) - msg += "[NO ACCESS]:\n{}\n```".format(", ".join(list(set(text_channels) - set(can_access)))) - for page in cf.pagify(msg, delims=["\n"], shorten_by=16): + no_access = ", ".join(list(set(text_channels) - set(can_access))) + msg = f"\n[{prefix} access to {len(can_access)} out of {len(text_channels)} text channels]\n\n" + msg += f"[ACCESS]:\n{', '.join(can_access)}\n\n" + msg += f"[NO ACCESS]:\n{no_access}" + for page in cf.pagify(cf.box(msg, lang="ini"), delims=["\n"], shorten_by=16): await ctx.send(page) @access.command() @@ -148,13 +137,11 @@ class Tools(commands.Cog): return await ctx.send("User is not in that guild or I do not have access to that guild.") prefix = "You have" if user.id == ctx.author.id else user.name + " has" - msg = "```ini\n[{} access to {} out of {} voice channels]\n\n".format( - prefix, len(can_access), len(voice_channels) - ) - - msg += "[ACCESS]:\n{}\n\n".format(", ".join(can_access)) - msg += "[NO ACCESS]:\n{}\n```".format(", ".join(list(set(voice_channels) - set(can_access)))) - for page in cf.pagify(msg, delims=["\n"], shorten_by=16): + no_access = ", ".join(list(set(voice_channels) - set(can_access))) + msg = f"\n[{prefix} access to {len(can_access)} out of {len(voice_channels)} voice channels]\n\n" + msg += f"[ACCESS]:\n{', '.join(can_access)}\n\n" + msg += f"[NO ACCESS]:\n{no_access}" + for page in cf.pagify(cf.box(msg, lang="ini"), delims=["\n"], shorten_by=16): await ctx.send(page) @commands.guild_only() @@ -178,20 +165,27 @@ class Tools(commands.Cog): msg += f"`{user_obj.user.id} - {user_name}`\n" banlist = sorted(msg) - embed_list = [] - for page in cf.pagify(msg, shorten_by=1400): - embed = discord.Embed( - description="**Total bans:** {}\n\n{}".format(bancount, page), - colour=await ctx.embed_colour(), - ) - embed_list.append(embed) - await menu(ctx, embed_list, DEFAULT_CONTROLS) + if ctx.channel.permissions_for(ctx.guild.me).embed_links: + embed_list = [] + for page in cf.pagify(msg, shorten_by=1400): + embed = discord.Embed( + description=f"**Total bans:** {bancount}\n\n{page}", + colour=await ctx.embed_colour(), + ) + embed_list.append(embed) + await menu(ctx, embed_list, DEFAULT_CONTROLS) + else: + text_list = [] + for page in cf.pagify(msg, shorten_by=1400): + text = f"**Total bans:** {bancount}\n{page}" + text_list.append(text) + await menu(ctx, text_list, DEFAULT_CONTROLS) @commands.guild_only() @commands.command() async def cid(self, ctx): - """Shows the channel ID.""" - await ctx.send("**#{0.name} ID:** {0.id}".format(ctx.channel)) + """Shows the channel id for the current channel.""" + await ctx.send(f"**#{ctx.channel.name} ID:** {ctx.channel.id}") @commands.guild_only() @commands.command() @@ -214,71 +208,60 @@ class Tools(commands.Cog): discord.VoiceChannel: "Voice Channel", discord.CategoryChannel: "Category", discord.StageChannel: "Stage Channel", - discord.Thread: "Thread Channel", + discord.Thread: "Thread", } - load = "```\nLoading channel info...```" - waiting = await ctx.send(load) - with sps(Exception): caller = inspect.currentframe().f_back.f_code.co_name.strip() - data = "```ini\n" + data = "" if caller == "invoke" or channel.guild != ctx.guild: - data += "[Server]: {}\n".format(channel.guild.name) - data += "[Name]: {}\n".format(cf.escape(str(channel))) - data += "[ID]: {}\n".format(channel.id) - data += "[Private]: {}\n".format(yesno[isinstance(channel, discord.abc.PrivateChannel)]) + data += f"[Server]: {channel.guild.name}\n" + data += f"[Name]: {cf.escape(str(channel))}\n" + data += f"[ID]: {channel.id}\n" + data += f"[Private]: {yesno[isinstance(channel, discord.abc.PrivateChannel)]}\n" if isinstance(channel, discord.TextChannel) and channel.topic != None: - data += "[Topic]: {}\n".format(channel.topic) + data += f"[Topic]: {channel.topic}\n" try: - data += "[Position]: {}\n".format(channel.position) + data += f"[Position]: {channel.position}\n" except AttributeError: # this is a thread - data += "[Parent Channel]: {} ({})\n".format(channel.parent.name, channel.parent.id) - data += "[Parent Position]: {}\n".format(channel.parent.position) + data += f"[Parent Channel]: {channel.parent.name} ({channel.parent.id})\n" + data += f"[Parent Position]: {channel.parent.position}\n" try: - data += "[Created]: {}\n".format(self._dynamic_time(channel.created_at)) + data += f"[Created]: {self._dynamic_time(channel.created_at)}\n" except AttributeError: # this is a thread - data += "[Updated]: {}\n".format(self._dynamic_time(channel.archive_timestamp)) - data += "[Type]: {}\n".format(typemap[type(channel)]) + data += f"[Updated]: {self._dynamic_time(channel.archive_timestamp)}\n" + data += f"[Type]: {typemap[type(channel)]}\n" if isinstance(channel, discord.TextChannel) and channel.is_news(): - data += "[News Channel]: {}\n".format(channel.is_news()) + data += f"[News Channel]: {yesno[channel.is_news()]}\n" if isinstance(channel, discord.VoiceChannel): - data += "[Users]: {}\n".format(len(channel.members)) - data += "[User limit]: {}\n".format(channel.user_limit) - data += "[Bitrate]: {}kbps\n".format(int(channel.bitrate / 1000)) - data += "```" - await asyncio.sleep(1) - await waiting.edit(content=data) + data += f"[Users]: {len(channel.members)}\n" + data += f"[User limit]: {channel.user_limit}\n" + data += f"[Bitrate]: {int(channel.bitrate / 1000)}kbps\n" + + await ctx.send(cf.box(data, lang="ini")) @commands.guild_only() @commands.command() - async def eid(self, ctx, emoji: str = None): + async def eid(self, ctx, emoji: discord.Emoji): """Get an id for an emoji.""" - if not isinstance(emoji, discord.Emoji): - return await ctx.send("I can't see that emoji in any of the servers I'm in.") - await ctx.send(f"**ID for {emoji}:** {emoji.id}") @commands.guild_only() @commands.command() - async def einfo(self, ctx, emoji: str = None): + async def einfo(self, ctx, emoji: discord.Emoji): """Emoji information.""" - if not isinstance(emoji, discord.Emoji): - return await ctx.send("I can't see that emoji in any of the servers I'm in.") - + yesno = {True: "Yes", False: "No"} + header = f"{str(emoji)}\n" m = ( - f"{str(emoji)}\n" - f"```ini\n" - f"[NAME]: {emoji.name}\n" - f"[GUILD]: {emoji.guild}\n" + f"[Name]: {emoji.name}\n" + f"[Guild]: {emoji.guild}\n" f"[URL]: {emoji.url}\n" - f"[ANIMATED]: {emoji.animated}" - "```" + f"[Animated]: {yesno[emoji.animated]}" ) - await ctx.send(m) + await ctx.send(header + cf.box(m, lang="ini")) @commands.guild_only() @commands.command() @@ -338,41 +321,46 @@ class Tools(commands.Cog): else: role = roles[response - 1] - awaiter = await ctx.send( - embed=discord.Embed(description="Getting member names...", colour=await ctx.embed_colour()) - ) - await asyncio.sleep(1.5) # taking time to retrieve the names users_in_role = "\n".join(sorted(m.display_name for m in guild.members if role in m.roles)) if len(users_in_role) == 0: - embed = discord.Embed( - description=cf.bold(f"0 users found in the {role.name} role."), - colour=await ctx.embed_colour(), - ) - await awaiter.edit(embed=embed) - return - try: - await awaiter.delete() - except discord.NotFound: - pass + if ctx.channel.permissions_for(ctx.guild.me).embed_links: + embed = discord.Embed( + description=cf.bold(f"0 users found in the {role.name} role."), + colour=await ctx.embed_colour(), + ) + return await ctx.send(embed=embed) + else: + return await ctx.send(cf.bold(f"0 users found in the {role.name} role.")) + embed_list = [] - for page in cf.pagify(users_in_role, delims=["\n"], page_length=200): - embed = discord.Embed( - description=cf.bold("{1} users found in the {0} role.\n").format( - role.name, len([m for m in guild.members if role in m.roles]) - ), - colour=await ctx.embed_colour(), - ) - embed.add_field(name="Users", value=page) - embed_list.append(embed) - final_embed_list = [] - for i, embed in enumerate(embed_list): - embed.set_footer(text=f"Page {i + 1}/{len(embed_list)}") - final_embed_list.append(embed) - if len(embed_list) == 1: - close_control = {"\N{CROSS MARK}": close_menu} - await menu(ctx, final_embed_list, close_control) + role_len = len([m for m in guild.members if role in m.roles]) + if ctx.channel.permissions_for(ctx.guild.me).embed_links: + for page in cf.pagify(users_in_role, delims=["\n"], page_length=200): + embed = discord.Embed( + description=cf.bold(f"{role_len} users found in the {role.name} role.\n"), + colour=await ctx.embed_colour(), + ) + embed.add_field(name="Users", value=page) + embed_list.append(embed) + final_embed_list = [] + for i, embed in enumerate(embed_list): + embed.set_footer(text=f"Page {i + 1}/{len(embed_list)}") + final_embed_list.append(embed) + if len(embed_list) == 1: + close_control = {"\N{CROSS MARK}": close_menu} + await menu(ctx, final_embed_list, close_control) + else: + await menu(ctx, final_embed_list, DEFAULT_CONTROLS) else: - await menu(ctx, final_embed_list, DEFAULT_CONTROLS) + for page in cf.pagify(users_in_role, delims=["\n"], page_length=200): + msg = f"**{role_len} users found in the {role.name} role.**\n" + msg += page + embed_list.append(msg) + if len(embed_list) == 1: + close_control = {"\N{CROSS MARK}": close_menu} + await menu(ctx, embed_list, close_control) + else: + await menu(ctx, embed_list, DEFAULT_CONTROLS) @commands.guild_only() @commands.command() @@ -394,18 +382,15 @@ class Tools(commands.Cog): ) await ctx.send(embed=embed) else: - await ctx.send(f"{user.display_name} joined this guild on {joined_on}.") + await ctx.send(f"**{user.display_name}** joined this guild on **{joined_on}**.") @commands.command(name="listguilds", aliases=["listservers", "guildlist", "serverlist"]) @checks.mod_or_permissions() async def listguilds(self, ctx): """List the guilds|servers the bot is in.""" - asciidoc = lambda m: "```asciidoc\n{}\n```".format(m) guilds = sorted(self.bot.guilds, key=lambda g: -g.member_count) - header = ("```\n" "The bot is in the following {} server{}:\n" "```").format( - len(guilds), "s" if len(guilds) > 1 else "" - ) - + plural = "s" if len(guilds) > 1 else "" + header = f"The bot is in the following {len(guilds)} server{plural}:\n" max_zpadding = max([len(str(g.member_count)) for g in guilds]) form = "{gid} :: {mems:0{zpadding}} :: {name}" all_forms = [ @@ -414,13 +399,13 @@ class Tools(commands.Cog): ] final = "\n".join(all_forms) - await ctx.send(header) + await ctx.send(cf.box(header)) page_list = [] for page in cf.pagify(final, delims=["\n"], page_length=1000): - page_list.append(asciidoc(page)) + page_list.append(cf.box(page, lang="asciidoc")) if len(page_list) == 1: - return await ctx.send(asciidoc(page)) + return await ctx.send(cf.box(page, lang="asciidoc")) await menu(ctx, page_list, DEFAULT_CONTROLS) @commands.guild_only() @@ -430,38 +415,40 @@ class Tools(commands.Cog): """ List the channels of the current server """ - asciidoc = lambda m: "```asciidoc\n{}\n```".format(m) - channels = ctx.guild.channels top_channels, category_channels = self.sort_channels(ctx.guild.channels) - topChannels_formed = "\n".join(self.channels_format(top_channels)) + top_channels_formed = "\n".join(self.channels_format(top_channels)) categories_formed = "\n\n".join([self.category_format(tup) for tup in category_channels]) - await ctx.send(f"{ctx.guild.name} has {len(channels)} channel{'s' if len(channels) > 1 else ''}.") + await ctx.send( + f"{ctx.guild.name} has {len(ctx.guild.channels)} channel{'s' if len(ctx.guild.channels) > 1 else ''}." + ) - for page in cf.pagify(topChannels_formed, delims=["\n"], shorten_by=16): - await ctx.send(asciidoc(page)) + for page in cf.pagify(top_channels_formed, delims=["\n"], shorten_by=16): + await ctx.send(cf.box(page, lang="asciidoc")) for page in cf.pagify(categories_formed, delims=["\n\n"], shorten_by=16): - await ctx.send(asciidoc(page)) + await ctx.send(cf.box(page, lang="asciidoc")) @commands.guild_only() @commands.command() @checks.mod_or_permissions(manage_guild=True) - async def newusers(self, ctx, count: int = 5, fm: str = "py"): - """Lists the newest 5 members.""" - guild = ctx.guild + async def newusers(self, ctx, count: int = 5, text_format: str = "py"): + """ + Lists the newest 5 members. + + `text_format` is the markdown language to use. Defaults to `py`. + """ count = max(min(count, 25), 5) - members = sorted(guild.members, key=lambda m: m.joined_at, reverse=True)[:count] + members = sorted(ctx.guild.members, key=lambda m: m.joined_at, reverse=True)[:count] - head1 = "{} newest members".format(count) - header = "{:>33}\n{}\n\n".format(head1, "-" * 57) + header = f"{count} newest members" + disp = "{:>33}\n{}\n\n".format(header, "-" * 57) - user_body = ( - " {mem} ({memid})\n" " {spcs}Joined Guild: {sp1}{join}\n" " {spcs}Account Created: {sp2}{created}\n\n" - ) + user_body = " {mem} ({memid})\n" + user_body += " {spcs}Joined Guild: {sp1}{join}\n" + user_body += " {spcs}Account Created: {sp2}{created}\n\n" - disp = header spcs = [" " * (len(m.name) // 2) for m in members] smspc = min(spcs, key=lambda it: len(it)) @@ -491,7 +478,7 @@ class Tools(commands.Cog): ) for page in cf.pagify(disp, delims=["\n\n"]): - await ctx.send(cf.box(page, lang=fm)) + await ctx.send(cf.box(page, lang=text_format)) @commands.guild_only() @commands.command() @@ -506,10 +493,10 @@ class Tools(commands.Cog): perms_we_dont = "" for x in sorted(perms): if "True" in str(x): - perms_we_have += "+\t{0}\n".format(str(x).split("'")[1]) + perms_we_have += "+ {0}\n".format(str(x).split("'")[1]) else: - perms_we_dont += "-\t{0}\n".format(str(x).split("'")[1]) - await ctx.send(cf.box("{0}{1}".format(perms_we_have, perms_we_dont), lang="diff")) + perms_we_dont += "- {0}\n".format(str(x).split("'")[1]) + await ctx.send(cf.box(f"{perms_we_have}{perms_we_dont}", lang="diff")) @commands.guild_only() @commands.command() @@ -519,9 +506,9 @@ class Tools(commands.Cog): if rolename is discord.Role: role = rolename else: - role = self._role_from_string(ctx.guild, rolename) + role = self.role_from_string(ctx.guild, rolename) if role is None: - await ctx.send(embed=discord.Embed(description="Cannot find role.", colour=await ctx.embed_colour())) + await ctx.send(f"Cannot find role: `{rolename}`") return await ctx.send(f"**{rolename} ID:** {role.id}") @@ -529,8 +516,6 @@ class Tools(commands.Cog): @commands.command() async def rinfo(self, ctx, *, rolename: discord.Role): """Shows role info.""" - channel = ctx.channel - guild = ctx.guild await ctx.trigger_typing() try: @@ -539,16 +524,18 @@ class Tools(commands.Cog): pass if not isinstance(rolename, discord.Role): - role = self._role_from_string(guild, rolename, guild.roles) + role = self.role_from_string(ctx.guild, rolename, ctx.guild.roles) else: role = rolename if role is None: await ctx.send("That role cannot be found.") return - if role is not None: - perms = iter(role.permissions) - perms_we_have = "" - perms_we_dont = "" + + perms = iter(role.permissions) + perms_we_have = "" + perms_we_dont = "" + + if ctx.channel.permissions_for(ctx.guild.me).embed_links: for x in sorted(perms): if "True" in str(x): perms_we_have += "{0}\n".format(str(x).split("'")[1]) @@ -558,53 +545,42 @@ class Tools(commands.Cog): perms_we_have = "None" if perms_we_dont == "": perms_we_dont = "None" - msg = discord.Embed(description="Gathering role stats...", colour=role.color) - if role.color is None: - role.color = discord.Colour(value=0x000000) - loadingmsg = await ctx.send(embed=msg) - em = discord.Embed(colour=role.colour) + role_color = role.color if role.color else discord.Colour(value=0x000000) + em = discord.Embed(colour=role_color) if caller == "invoke": em.add_field(name="Server", value=role.guild.name) em.add_field(name="Role Name", value=role.name) em.add_field(name="Created", value=self._dynamic_time(role.created_at)) - em.add_field(name="Users in Role", value=len([m for m in guild.members if role in m.roles])) + em.add_field(name="Users in Role", value=len([m for m in ctx.guild.members if role in m.roles])) em.add_field(name="ID", value=role.id) em.add_field(name="Color", value=role.color) em.add_field(name="Position", value=role.position) - em.add_field(name="Valid Permissions", value="{}".format(perms_we_have)) - em.add_field(name="Invalid Permissions", value="{}".format(perms_we_dont)) - if guild.icon: + em.add_field(name="Valid Permissions", value=perms_we_have) + em.add_field(name="Invalid Permissions", value=perms_we_dont) + if role.guild.icon: em.set_thumbnail(url=role.guild.icon.url) - try: - await loadingmsg.edit(embed=em) - except discord.HTTPException: - permss = "```diff\n" - role = self._role_from_string(guild, rolename, guild.roles) + await ctx.send(embed=em) + else: + role = self.role_from_string(ctx.guild, rolename, ctx.guild.roles) if role is None: await ctx.send("That role cannot be found.") return - if role is not None: - perms = iter(role.permissions) - perms_we_have2 = "" - perms_we_dont2 = "" - for x in sorted(perms): - if "True" in str(x): - perms_we_have2 += "+{0}\n".format(str(x).split("'")[1]) - else: - perms_we_dont2 += "-{0}\n".format(str(x).split("'")[1]) - await ctx.send( - "{}Name: {}\nCreated: {}\nUsers in Role : {}\nId : {}\nColor : {}\nPosition : {}\nValid Perms : \n{}\nInvalid Perms : \n{}```".format( - permss, - role.name, - self._dynamic_time(role.created_at), - len([m for m in guild.members if role in m.roles]), - role.id, - role.color, - role.position, - perms_we_have2, - perms_we_dont2, - ) - ) + + for x in sorted(perms): + if "True" in str(x): + perms_we_have += "+ {0}\n".format(str(x).split("'")[1]) + else: + perms_we_dont += "- {0}\n".format(str(x).split("'")[1]) + msg = "" + msg += f"Name: {role.name}\n" + msg += f"Created: {self._dynamic_time(role.created_at)}\n" + msg += f"Users in Role : {len([m for m in role.guild.members if role in m.roles])}\n" + msg += f"ID: {role.id}\n" + msg += f"Color: {role.color}\n" + msg += f"Position: {role.position}\n" + msg += f"Valid Perms: \n{perms_we_have}\n" + msg += f"Invalid Perms: \n{perms_we_dont}" + await ctx.send(cf.box(msg, lang="diff")) @commands.guild_only() @commands.command(aliases=["listroles"]) @@ -617,25 +593,28 @@ class Tools(commands.Cog): form.format(rpos=r.position, zpadding=max_zpadding, rid=r.id, rment=r.mention, rcolor=r.color) for r in ctx.guild.roles ] - rolelist = sorted(rolelist, reverse=True) rolelist = "\n".join(rolelist) embed_list = [] - for page in cf.pagify(rolelist, shorten_by=1400): - embed = discord.Embed( - description=f"**Total roles:** {len(ctx.guild.roles)}\n\n{page}", - colour=await ctx.embed_colour(), - ) - embed_list.append(embed) + if ctx.channel.permissions_for(ctx.guild.me).embed_links: + for page in cf.pagify(rolelist, shorten_by=1400): + embed = discord.Embed( + description=f"**Total roles:** {len(ctx.guild.roles)}\n\n{page}", + colour=await ctx.embed_colour(), + ) + embed_list.append(embed) + else: + for page in cf.pagify(rolelist, shorten_by=1400): + msg = f"**Total roles:** {len(ctx.guild.roles)}\n{page}" + embed_list.append(msg) + await menu(ctx, embed_list, DEFAULT_CONTROLS) @commands.command(hidden=True) async def sharedservers(self, ctx, user: discord.Member = None): """Shows shared server info. Defaults to author.""" - author = ctx.author - guild = ctx.guild if not user: - user = author + user = ctx.author mutual_guilds = user.mutual_guilds data = f"[Guilds]: {len(mutual_guilds)} shared\n" @@ -643,13 +622,13 @@ class Tools(commands.Cog): data += f"[In Guilds]: {cf.humanize_list(shared_servers, style='unit')}" for page in cf.pagify(data, ["\n"], page_length=1800): - await ctx.send(f"```ini\n{data}```") + await ctx.send(cf.box(data, lang="ini")) @commands.guild_only() @commands.command() async def sid(self, ctx): - """Show the server ID.""" - await ctx.send("**{0.name} ID:** {0.id}".format(ctx.guild)) + """Show the server id.""" + await ctx.send(f"**{ctx.guild.name} ID:** {ctx.guild.id}") @commands.guild_only() @commands.command(aliases=["ginfo"]) @@ -667,22 +646,45 @@ class Tools(commands.Cog): text_channels = [x for x in guild.channels if isinstance(x, discord.TextChannel)] voice_channels = [x for x in guild.channels if isinstance(x, discord.VoiceChannel)] - load = "```\nLoading guild info...```" - waiting = await ctx.send(load) + data = f"[Name]: {guild.name}\n" + data += f"[ID]: {guild.id}\n" + data += f"[Owner]: {guild.owner}\n" + data += f"[Users]: {online}/{total_users}\n" + data += f"[Text]: {len(text_channels)} channels\n" + data += f"[Voice]: {len(voice_channels)} channels\n" + data += f"[Emojis]: {len(guild.emojis)}\n" + data += f"[Stickers]: {len(guild.stickers)}\n" + data += f"[Roles]: {len(guild.roles)}\n" + data += f"[Created]: {self._dynamic_time(guild.created_at)}\n" - data = "```ini\n" - data += "[Name]: {}\n".format(guild.name) - data += "[ID]: {}\n".format(guild.id) - data += "[Owner]: {}\n".format(guild.owner) - data += "[Users]: {}/{}\n".format(online, total_users) - data += "[Text]: {} channels\n".format(len(text_channels)) - data += "[Voice]: {} channels\n".format(len(voice_channels)) - data += "[Emojis]: {}\n".format(len(guild.emojis)) - data += "[Stickers]: {}\n".format(len(guild.stickers)) - data += "[Roles]: {} \n".format(len(guild.roles)) - data += "[Created]: {}\n```".format(self._dynamic_time(guild.created_at)) - await asyncio.sleep(1) - await waiting.edit(content=data) + await ctx.send(cf.box(data, lang="ini")) + + @commands.guild_only() + @commands.command(aliases=["stickerinfo"]) + async def stinfo(self, ctx): + """ + Sticker information. + + Attach a sticker to the command message or provide a message link. + """ + stickers = ctx.message.stickers + + if not stickers: + return await ctx.send(f"Attach a sticker to the `{ctx.prefix}stinfo` command.") + + for sticker_item in stickers: + sticker = await sticker_item.fetch() + + msg = f"[Name]: {sticker.name}\n" + msg += f"[Guild]: {sticker.guild if sticker.guild != None else 'Guild name is unavailable'}\n" + msg += f"[ID]: {sticker.id}\n" + msg += f"[URL]: {str(sticker.url)}\n" + msg += f"[Format]: {sticker.format.file_extension if sticker.format.file_extension else 'lottie'}\n" + if sticker.description: + msg += f"[Description]: {sticker.description}\n" + msg += f"[Created]: {self._dynamic_time(sticker.created_at)}\n" + + await ctx.send(cf.box(msg, lang="ini")) @commands.guild_only() @commands.command() @@ -707,6 +709,40 @@ class Tools(commands.Cog): else: await menu(ctx, pages, DEFAULT_CONTROLS) + @commands.guild_only() + @commands.command() + async def uimages(self, ctx, user: discord.Member = None, embed=False): + """ + Shows user image urls. Defaults to author. + + `embed` is a True/False value for whether to display the info in an embed. + """ + if user is None: + user = ctx.author + + fetch_user = await self.bot.fetch_user(user.id) + + if not embed or not ctx.channel.permissions_for(ctx.guild.me).embed_links: + data = f"[Name]: {cf.escape(str(user))}\n" + data += f"[Avatar URL]: {user.avatar if user.avatar is not None else user.default_avatar}\n" + if user.guild_avatar: + data += f"[Server Avatar URL]: {user.guild_avatar}\n" + if fetch_user.banner: + data += f"[Banner URL]: {fetch_user.banner}\n" + + await ctx.send(cf.box(data, lang="ini")) + else: + embed = discord.Embed( + description=f"**{cf.escape(str(user))}**", + colour=await ctx.embed_colour(), + ) + if user.guild_avatar: + embed.add_field(name="Server Avatar", value=user.guild_avatar, inline=False) + embed.set_thumbnail(url=user.avatar if user.avatar is not None else user.default_avatar) + if fetch_user.banner: + embed.set_image(url=fetch_user.banner) + await ctx.send(embed=embed) + @commands.guild_only() @commands.command() async def uinfo(self, ctx, user: discord.Member = None): @@ -723,47 +759,42 @@ class Tools(commands.Cog): roles[0].name, ] + [f"{r.name:>{len(r.name)+17}}" for r in roles[1:]] except IndexError: - # if there are no roles then roles[0] will raise the IndexError here _roles = ["None"] seen = str(len(set([member.guild.name for member in self.bot.get_all_members() if member.id == user.id]))) - load = "```\nLoading user info...```" - waiting = await ctx.send(load) - - data = "```ini\n" - data += "[Name]: {}\n".format(cf.escape(str(user))) - data += "[ID]: {}\n".format(user.id) - data += "[Status]: {}\n".format(user.status) - data += "[Servers]: {} shared\n".format(seen) + data = f"[Name]: {cf.escape(str(user))}\n" + data += f"[ID]: {user.id}\n" + data += f"[Status]: {user.status}\n" + data += f"[Servers]: {seen} shared\n" if actplay := discord.utils.get(user.activities, type=discord.ActivityType.playing): - data += "[Playing]: {}\n".format(cf.escape(str(actplay.name))) + data += f"[Playing]: {cf.escape(str(actplay.name))}\n" if actlisten := discord.utils.get(user.activities, type=discord.ActivityType.listening): if isinstance(actlisten, discord.Spotify): - _form = "{} - {}".format(actlisten.artist, actlisten.title) + _form = f"{actlisten.artist} - {actlisten.title}" else: _form = actlisten.name - data += "[Listening]: {}\n".format(cf.escape(_form)) + data += f"[Listening]: {cf.escape(_form)}\n" if actwatch := discord.utils.get(user.activities, type=discord.ActivityType.watching): - data += "[Watching]: {}\n".format(cf.escape(str(actwatch.name))) + data += f"[Watching]: {cf.escape(str(actwatch.name))}\n" if actstream := discord.utils.get(user.activities, type=discord.ActivityType.streaming): - data += "[Streaming]: [{}]({})\n".format(cf.escape(str(actstream.name)), cf.escape(actstream.url)) + data += f"[Streaming]: [{cf.escape(str(actstream.name))}]({cf.escape(actstream.url)})\n" if actcustom := discord.utils.get(user.activities, type=discord.ActivityType.custom): if actcustom.name is not None: - data += "[Custom Status]: {}\n".format(cf.escape(str(actcustom.name))) + data += f"[Custom Status]: {cf.escape(str(actcustom.name))}\n" passed = (ctx.message.created_at - user.created_at).days - data += "[Created]: {}\n".format(self._dynamic_time(user.created_at)) + data += f"[Created]: {self._dynamic_time(user.created_at)}\n" joined_at = self.fetch_joined_at(user, ctx.guild) if caller != "invoke": - data += "[Joined]: {}\n".format(self._dynamic_time(joined_at)) - data += "[Roles]: {}\n".format("\n".join(_roles)) + role_list = "\n".join(_roles) + data += f"[Joined]: {self._dynamic_time(joined_at)}\n" + data += f"[Roles]: {role_list}\n" if len(_roles) > 1: data += "\n" - data += "[In Voice]: {}\n".format(user.voice.channel if user.voice is not None else None) - data += "[AFK]: {}\n".format(user.voice.afk if user.voice is not None else False) - data += "```" - await asyncio.sleep(1) - await waiting.edit(content=data) + data += f"[In Voice]: {user.voice.channel if user.voice is not None else None}\n" + data += f"[AFK]: {user.voice.afk if user.voice is not None else False}\n" + + await ctx.send(cf.box(data, lang="ini")) @commands.guild_only() @commands.command() @@ -808,31 +839,7 @@ class Tools(commands.Cog): await ctx.send("I could not find anything for this ID.") @staticmethod - def _dynamic_time(time): - try: - date_join = datetime.datetime.strptime(str(time), "%Y-%m-%d %H:%M:%S.%f%z") - except ValueError: - time = f"{str(time)}.0" - date_join = datetime.datetime.strptime(str(time), "%Y-%m-%d %H:%M:%S.%f%z") - date_now = discord.utils.utcnow() - since_join = date_now - date_join - - mins, secs = divmod(int(since_join.total_seconds()), 60) - hrs, mins = divmod(mins, 60) - days, hrs = divmod(hrs, 24) - mths, wks, days = Tools._count_months(days) - yrs, mths = divmod(mths, 12) - - m = f"{yrs}y {mths}mth {wks}w {days}d {hrs}h {mins}m {secs}s" - m2 = [x for x in m.split() if x[0] != "0"] - s = " ".join(m2[:2]) - if s: - return f"{s} ago" - else: - return "" - - @staticmethod - def _count_months(days): + def count_months(days): lens = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] cy = itertools.cycle(lens) months = 0 @@ -850,10 +857,68 @@ class Tools(commands.Cog): weeks, days = divmod(m_temp, 7) return months, weeks, days - def fetch_joined_at(self, user, guild): + def category_format(self, cat_chan_tuple: tuple): + cat = cat_chan_tuple[0] + chs = cat_chan_tuple[1] + + chfs = self.channels_format(chs) + if chfs != []: + ch_forms = ["\t" + f for f in chfs] + return "\n".join([f"{cat.name} :: {cat.id}"] + ch_forms) + else: + return "\n".join([f"{cat.name} :: {cat.id}"] + ["\tNo Channels"]) + + @staticmethod + def channels_format(channels: list): + if channels == []: + return [] + + channel_form = "{name} :: {ctype} :: {cid}" + + def type_name(channel): + return channel.__class__.__name__[:-7] + + name_justify = max([len(c.name[:24]) for c in channels]) + type_justify = max([len(type_name(c)) for c in channels]) + + return [ + channel_form.format( + name=c.name[:24].ljust(name_justify), + ctype=type_name(c).ljust(type_justify), + cid=c.id, + ) + for c in channels + ] + + def _dynamic_time(self, time): + try: + date_join = datetime.datetime.strptime(str(time), "%Y-%m-%d %H:%M:%S.%f%z") + except ValueError: + time = f"{str(time)}.0" + date_join = datetime.datetime.strptime(str(time), "%Y-%m-%d %H:%M:%S.%f%z") + date_now = discord.utils.utcnow() + since_join = date_now - date_join + + mins, secs = divmod(int(since_join.total_seconds()), 60) + hrs, mins = divmod(mins, 60) + days, hrs = divmod(hrs, 24) + mths, wks, days = self.count_months(days) + yrs, mths = divmod(mths, 12) + + m = f"{yrs}y {mths}mth {wks}w {days}d {hrs}h {mins}m {secs}s" + m2 = [x for x in m.split() if x[0] != "0"] + s = " ".join(m2[:2]) + if s: + return f"{s} ago" + else: + return "" + + @staticmethod + def fetch_joined_at(user, guild): return user.joined_at - def _role_from_string(self, guild, rolename, roles=None): + @staticmethod + def role_from_string(guild, rolename, roles=None): if roles is None: roles = guild.roles role = discord.utils.find(lambda r: r.name.lower() == str(rolename).lower(), roles) @@ -879,36 +944,3 @@ class Tools(commands.Cog): key=lambda t: t[0].position, ) return channels, category_channels - - def channels_format(self, channels: list): - if channels == []: - return [] - - channel_form = "{name} :: {ctype} :: {cid}" - - def type_name(channel): - return channel.__class__.__name__[:-7] - - name_justify = max([len(c.name[:24]) for c in channels]) - type_justify = max([len(type_name(c)) for c in channels]) - - return [ - channel_form.format( - name=c.name[:24].ljust(name_justify), - ctype=type_name(c).ljust(type_justify), - cid=c.id, - ) - for c in channels - ] - - def category_format(self, cat_chan_tuple: tuple): - - cat = cat_chan_tuple[0] - chs = cat_chan_tuple[1] - - chfs = self.channels_format(chs) - if chfs != []: - ch_forms = ["\t" + f for f in chfs] - return "\n".join([f"{cat.name} :: {cat.id}"] + ch_forms) - else: - return "\n".join([f"{cat.name} :: {cat.id}"] + ["\tNo Channels"])