async def avatar_handler(before: discord.Member, after: discord.Member) -> dict: """ Handler that returns the old avatar for thumbnail usage and the new avatar for the embed image """ return { "thumbnail_url": get_user_avatar_url(before)[0], "image": get_user_avatar_url(after)[0], "description": ":arrow_right: Old Avatar\n:arrow_down: New Avatar" } # todo: guild avatar listener if it exists
async def check(self, ctx: commands.Context, *, args: str = "") -> None: """ Checks a specific person reps, or your own if user is left blank """ if not args: user = ctx.author else: user = await self.bot.get_spaced_member(ctx, self.bot, args=args) if user is None: await self.bot.DefaultEmbedResponses.error_embed( self.bot, ctx, "We could not find that user!") return rep = None lb_pos = None async with self.bot.pool.acquire() as connection: all_rep = await connection.fetch( "SELECT member_id, reps FROM rep WHERE guild_id = $1 ORDER by reps DESC;", ctx.guild.id) all_rep = [ x for x in all_rep if ctx.channel.guild.get_member(x[0]) is not None ] member_record = next((x for x in all_rep if x[0] == user.id), None) if member_record: # If the user actually has reps rep = member_record[1] # Check member ID and then get reps prev = 0 lb_pos = 0 for record in all_rep: if record[1] != prev: lb_pos += 1 prev = record[1] # Else, increase the rank if record[0] == user.id: break # End loop if reached the user if not rep: rep = 0 embed = Embed(title=f"Rep info for {user.display_name} ({user})", color=Colour.from_rgb(139, 0, 139)) # could change to user.colour at some point, I prefer the purple for now though embed.add_field(name="Rep points", value=rep) embed.add_field( name="Leaderboard position", value=self.bot.ordinal(lb_pos) if lb_pos else "Nowhere :(") embed.set_footer( text=f"Requested by {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) embed.set_thumbnail(url=get_user_avatar_url(user, mode=1)[0]) await ctx.send(embed=embed)
async def _warnlist_member(self, ctx: commands.Context, member: discord.Member, page_num: int = 1) -> None: """ Handles getting the warns for a specific member """ async with self.bot.pool.acquire() as connection: warns = await connection.fetch( "SELECT * FROM warn WHERE member_id = ($1) AND guild_id = $2 ORDER BY id;", member.id, ctx.guild.id) if len(warns) > 0: embed = self.bot.EmbedPages( self.bot.PageTypes.WARN, warns, f"{member.display_name}'s warnings", Colour.from_rgb(177, 252, 129), self.bot, ctx.author, ctx.channel, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer= f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format)) await embed.set_page(int(page_num)) await embed.send() else: await ctx.send("No warnings recorded!")
async def make_starboard_embed(self, message: discord.Message, stars: int, emoji: discord.Emoji, color: discord.Color) -> discord.Embed: """ Turns the message into an Embed that can be sent in the starboard channel """ embed = discord.Embed(title=f"{stars} {emoji} (in #{message.channel.name})", color=color if color else self.bot.GOLDEN_YELLOW, description=message.content) embed.set_author(name=message.author.display_name, icon_url=get_user_avatar_url(message.author, mode=1)[0]) if message.embeds: embedded_data = message.embeds[0] if embedded_data.type == "image" and not self.is_url_spoiler(message.content, embedded_data.url): embed.set_image(url=embedded_data.url) else: embed.add_field(name="Message is an embed", value="Press the message link to view", inline=False) if message.reference: embed.add_field(name="In response to...", value=f"{message.reference.resolved.author.display_name}#{message.reference.resolved.author.discriminator}", inline=False) if message.attachments: attatched = message.attachments[0] is_spoiler = attatched.is_spoiler() if not is_spoiler and attatched.url.lower().endswith(("png", "jpeg", "jpg", "gif", "webp")): embed.set_image(url=attatched.url) elif is_spoiler: embed.add_field(name="Attachment", value=f"||[{attatched.filename}]({attatched.url})||", inline=False) else: embed.add_field(name="Attachment", value=f"[{attatched.filename}]({attatched.url})", inline=False) embed.add_field(name="Message link", value=f"[Click me!]({message.jump_url})") embed.set_footer(text=self.bot.correct_time().strftime("%H:%M on %m/%d/%Y")) return embed
async def set(self, ctx: commands.Context, user: discord.Member | discord.User, rep: str) -> None: """ Sets a specific members reps to a given value. """ if not rep.isdigit(): await self.bot.DefaultEmbedResponses.error_embed( self.bot, ctx, "The reputation points must be a number!") return new_reps = await self.set_rep(user.id, ctx.guild.id, int(rep)) await self.bot.DefaultEmbedResponses.success_embed( self.bot, ctx, f"{user.display_name}'s reputation points have been changed!", desc=f"{user.display_name} now has {new_reps} reputation points!", thumbnail_url=get_user_avatar_url(user)[0]) channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Reputation Points Set", color=Colour.from_rgb(177, 252, 129)) embed.add_field(name="Member", value=str(user)) embed.add_field(name="Staff", value=str(ctx.author)) embed.add_field(name="New Rep", value=new_reps) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed)
async def info(self, ctx: commands.Context, *, role: discord.Role | str) -> None: """ Displays various details about a specified role. Works with a role mention, role name or role ID. """ if type(role) is not discord.Role: role = await self.find_closest_role(ctx, role, verbosity=Verbosity.ALL) if len(role) > 1: return role = role[0] embed = Embed(title=f"Role info ~ {role.name}", colour=role.colour) embed.add_field(name="Created at", value=self.bot.correct_time(role.created_at)) embed.add_field(name="Members", value=len(role.members)) embed.add_field(name="Position", value=role.position) embed.add_field(name="Displays separately", value=role.hoist) embed.add_field(name="Mentionable", value=role.mentionable) embed.add_field(name="Colour", value=role.colour) embed.add_field(name="Role ID", value=role.id) icon_url = get_guild_icon_url(ctx.guild) if icon_url: embed.set_thumbnail(url=icon_url) embed.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.send(embed=embed)
async def get_leaderboard(self, ctx: commands.Context) -> None: async with self.bot.pool.acquire() as connection: leaderboard = await connection.fetch( "SELECT member_id, reps FROM rep WHERE guild_id = $1 ORDER BY reps DESC", ctx.guild.id) if len(leaderboard) == 0: await self.bot.DefaultEmbedResponses.error_embed( self.bot, ctx, f"There aren't any reputation points in {ctx.guild.name} yet! " ) return embed = self.bot.EmbedPages( self.bot.PageTypes.REP, leaderboard, f"{ctx.guild.name}'s Reputation Leaderboard", Colour.from_rgb(177, 252, 129), self.bot, ctx.author, ctx.channel, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format)) await embed.set_page(1) # Default first page await embed.send()
async def handle_unban(self, data: dict, reason: str = "", author: str = "", ctx: commands.Context = None) -> None: try: user = self.bot.get_user(data["member_id"]) if not user and ctx: user, in_guild = await self.get_member_obj( ctx, data["member_id"]) if not user: return guild = self.bot.get_guild(data["guild_id"]) await guild.unban(user, reason=reason) channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Unban", color=Colour.from_rgb(76, 176, 80)) embed.add_field(name="User", value=f"{user.mention} ({user.id})") embed.add_field(name="Moderator", value=str(self.bot.user if not author else author)) embed.add_field(name="Reason", value=reason) embed.set_thumbnail(url=get_user_avatar_url(user)[0]) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed) except Exception as e: print(e) pass # go away!
async def spotify_info(self, ctx: commands.Context, *, args: discord.Member | discord.User | str = "") -> None: if len(args) == 0: user = ctx.message.author else: if type(args) is not discord.Member: user = await self.bot.get_spaced_member(ctx, self.bot, args=args) else: user = args if user is None: fail_embed = Embed(title="Spotify info", description=f":x: **Sorry {ctx.author.display_name} we could not find that user!**", color=Colour.from_rgb(255, 7, 58)) fail_embed.set_footer(text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + ( self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.send(embed=fail_embed) return spotify_activity = None for i in range(0, len(user.activities)): if type(user.activities[i]).__name__ == "Spotify": spotify_activity = user.activities[i] break if spotify_activity is None: fail_embed = Embed(title=f"Spotify info for {user}", description="The user isn't currently listening to Spotify\n*(note that this can't be detected unless Spotify is visible on the status)*", colour=user.colour) fail_embed.set_footer(text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + ( self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.message.channel.send(embed=fail_embed) return duration = spotify_activity.duration.seconds minutes = duration//60 seconds = duration % 60 if seconds < 10: seconds = "0" + str(seconds) song_start = self.bot.correct_time(spotify_activity.start).strftime("%H:%M:%S") song_end = self.bot.correct_time(spotify_activity.end).strftime("%H:%M:%S") embed = Embed(title=f"Spotify info", colour=user.colour) embed.add_field(name="Track", value=f"{spotify_activity.title}") embed.add_field(name="Artist(s)", value=f"{spotify_activity.artist}") embed.add_field(name="Album", value=f"{spotify_activity.album}") embed.add_field(name="Track Length", value=f"{minutes}:{seconds}") embed.add_field(name="Time This Song Started", value=f"{song_start}") embed.add_field(name="Time This Song Will End", value=f"{song_end}") embed.add_field(name="Party ID (Premium Only)", value=f"{spotify_activity.party_id}") embed.add_field(name="Song's Spotify Link", value=f"https://open.spotify.com/track/{spotify_activity.track_id}", inline=False) embed.set_thumbnail(url=spotify_activity.album_cover_url) embed.set_author(name=f"{user}", icon_url=get_user_avatar_url(user, mode=1)[0]) embed.set_footer(text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + ( self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.message.channel.send(embed=embed)
async def serverinfo(self, ctx: commands.Context) -> None: """ Information about the server. """ guild = ctx.message.guild time_ = guild.created_at time_since = discord.utils.utcnow() - time_ join = Embed( title=f"**__{str(guild)}__**", description= f"Created at {self.bot.correct_time(time_).strftime(self.bot.ts_format)}. That's {time_since.days} days ago!", color=Colour.from_rgb(21, 125, 224)) icon_url = get_guild_icon_url(guild) if icon_url: join.set_thumbnail(url=icon_url) join.add_field( name="Users Online", value= f"{len([x for x in guild.members if x.status != Status.offline])}/{len(guild.members)}" ) if ctx.guild.rules_channel: # only community join.add_field(name="Rules Channel", value=f"{ctx.guild.rules_channel.mention}") join.add_field(name="Text Channels", value=f"{len(guild.text_channels)}") join.add_field(name="Voice Channels", value=f"{len(guild.voice_channels)}") join.add_field(name="Roles", value=f"{len(guild.roles)}") join.add_field(name="Owner", value=f"{str(guild.owner)}") join.add_field(name="Server ID", value=f"{guild.id}") join.add_field( name="Emoji slots filled", value=f"{len(ctx.guild.emojis)}/{ctx.guild.emoji_limit}") join.add_field( name="Sticker slots filled", value=f"{len(ctx.guild.stickers)}/{ctx.guild.sticker_limit}") join.add_field(name="Upload size limit", value=f"{guild.filesize_limit/1048576} MB") join.add_field( name="Boost level", value= f"{ctx.guild.premium_tier} ({ctx.guild.premium_subscription_count} boosts, {len(ctx.guild.premium_subscribers)} boosters)" ) join.add_field( name="Default Notification Level", value=f"{self.bot.make_readable(guild.default_notifications.name)}" ) join.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + (self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.send(embed=join)
async def avatar(self, ctx: commands.Context, member: discord.Member | discord.User = None) -> None: if not member: member = ctx.author avatar_urls = get_user_avatar_url(member, mode=2) if len(avatar_urls) == 1 or avatar_urls[0] == avatar_urls[1]: await ctx.send(avatar_urls[0]) else: await ctx.send(f"**ACCOUNT AVATAR:**\n{avatar_urls[0]}") await ctx.send(f"**SERVER AVATAR:**\n{avatar_urls[1]}")
async def kick(self, ctx: commands.Context, member: discord.Member, *, args: str = "") -> None: """ Kicks a given user. Kick members perm needed """ if ctx.me.top_role < member.top_role: await ctx.send( f"Can't ban {member.mention}, they have a higher role than the bot!" ) return reason = None if args: parsed_args = self.bot.flag_handler.separate_args( args, fetch=["reason"], blank_as_flag="reason") reason = parsed_args["reason"] if not reason: reason = f"No reason provided" try: # perhaps add some like `attempt_dm` thing in utils instead of this? await member.send( f"You have been kicked from {ctx.guild} ({reason})") except (discord.Forbidden, discord.HTTPException): await ctx.send( f"Could not DM {member.display_name} about their kick!") await member.kick(reason=reason) await ctx.send(f"{member.mention} has been kicked :boot:") channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Kick", color=Colour.from_rgb(220, 123, 28)) embed.add_field(name="Member", value=f"{member.mention} ({member.id})") embed.add_field(name="Reason", value=reason + f" (kicked by {ctx.author.name})") embed.set_thumbnail(url=get_user_avatar_url(member, mode=1)[0]) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed)
async def on_member_remove(self, member: discord.Member) -> None: channel = await self.get_log_channel(member.guild, "join_leave") if channel is None: return member_left = Embed(title=":information_source: User Left", color=Colour.from_rgb(218, 118, 39)) member_left.add_field( name="User", value=f"{member} ({member.id})\n {member.mention}") joined_at = member.joined_at if joined_at is not None: rn = discord.utils.utcnow() a = self.bot.correct_time(joined_at, timezone_="UTC") since_joined = (rn - a) since_str = "" props = [ "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds" ] for prop in props: if prop in dir( since_joined ): # datetime delta objects have no standard get method :( since_str += f"{since_joined.__getattribute__(prop)} {prop} " if since_joined.__getattribute__( prop) else "" user_joined = a.strftime(self.bot.ts_format) member_left.add_field(name="Joined", value=f"{user_joined} ({since_str} ago)" if joined_at else "Undetected") roles = [f"{role.mention}" for role in member.roles][1:] # 1: eliminates @@everyone roles_str = "" for role_ in roles: roles_str += f"{role_}, " roles_str = roles_str[:len(roles_str) - 2] member_left.add_field(name="Roles", value=roles_str if member.roles[1:] else "None", inline=False) member_left.set_thumbnail(url=get_user_avatar_url(member, mode=1)[0]) member_left.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=member_left)
async def warns(self, ctx: commands.Context, member: discord.Member = None) -> None: """ Shows a user their warnings, or shows staff members all/a single persons warnings """ is_staff = await self.bot.is_staff(ctx) if is_staff: if not member: # Show all warns async with self.bot.pool.acquire() as connection: warns = await connection.fetch( "SELECT * FROM warn WHERE guild_id = $1 ORDER BY id;", ctx.guild.id) if len(warns) > 0: embed = self.bot.EmbedPages( self.bot.PageTypes.WARN, warns, f"{ctx.guild.name if not member else member.display_name}'s warnings", Colour.from_rgb(177, 252, 129), self.bot, ctx.author, ctx.channel, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer= f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format)) await embed.set_page(1) await embed.send() else: await ctx.send("No warnings recorded!") else: # Show member's warns await self._warnlist_member( ctx, member, 1) # Last parameter is the page number to start on else: if not member or member.id == ctx.author.id: # Show ctx.author warns await self._warnlist_member(ctx, ctx.author, 1) else: await ctx.send( "You don't have permission to view other people's warns.")
async def botinfo(self, ctx: commands.Context) -> None: app_info = await self.bot.application_info() embed = Embed( title=f"Bot info for ({self.bot.user})", description=app_info.description if app_info.description else "", colour=0x87CEEB) embed.add_field(name="Uptime", value=self.bot.time_str( round(time.time() - self.bot.start_time))) embed.add_field(name="Discord.py version", value=discord.__version__, inline=False) embed.add_field(name="Python version", value=f"{platform.python_version()}", inline=False) embed.add_field(name="Commit in use", value=f"{self.commit_name} ({self.commit_hash})" if self.commit_hash else "Unknown", inline=False) if self.commit_url: embed.add_field(name="Commit link", value=self.commit_url, inline=False) if self.branch_name: embed.add_field(name="Currently checked out branch", value=self.branch_name, inline=False) embed.add_field(name="Host OS", value=f"{platform.system()}", inline=False) embed.add_field(name="Bot owner", value=f"{app_info.owner}", inline=False) embed.add_field(name="Public bot", value=f"{app_info.bot_public}", inline=False) if hasattr(app_info, "icon"): embed.set_thumbnail(url=app_info.icon.url) embed.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + (self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.send(embed=embed)
async def gcses(self, ctx: commands.Context) -> None: embed = Embed(title="Information on UK exams", color=Colour.from_rgb(148, 0, 211)) now = self.bot.correct_time() time_ = self.bot.correct_time( datetime(year=2022, month=5, day=16, hour=9, minute=0, second=0)) if now > time_: embed.description = "Exams have already started!" else: time_ = time_ - now m, s = divmod(time_.seconds, 60) h, m = divmod(m, 60) embed.description = f"{time_.days} days {h} hours {m} minutes {s} seconds remaining until the first exam (RS Paper 1)" embed.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + (self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.send(embed=embed)
async def showreactionroles(self, ctx: commands.Context) -> None: """ Shows all the current reaction roles in the guild """ async with self.bot.pool.acquire() as connection: data = await connection.fetch( "SELECT * FROM reaction_roles WHERE guild_id = $1;", ctx.guild.id) embed = discord.Embed( title=f":information_source: {ctx.guild.name} reaction roles", color=self.bot.INFORMATION_BLUE) embed.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) message_reactions = {} # ID -> str (to put in embed) message_channels = {} # ID -> discord.TextChannel for rr in data: role = ctx.guild.get_role(rr["role_id"]) emoji = rr["emoji"] or [ x for x in ctx.guild.emojis if x.id == rr["emoji_id"] ][0] if rr["message_id"] not in message_reactions.keys(): message_reactions[rr[ "message_id"]] = f"{emoji} -> {role.mention} {'(inverse)' if rr['inverse'] else ''}" else: message_reactions[rr[ "message_id"]] += f"\n{emoji} -> {role.mention} {'(inverse)' if rr['inverse'] else ''}" if rr["message_id"] not in message_channels.keys(): message_channels[rr["message_id"]] = self.bot.get_channel( rr["channel_id"]) for message_id in message_reactions: embed.add_field( name=f"{message_id} (in #{message_channels[message_id]})", value=message_reactions[message_id]) await ctx.reply(embed=embed)
async def list_server_roles(self, ctx: commands.Context) -> None: """ Lists all the roles on the server. """ embed = self.bot.EmbedPages( self.bot.PageTypes.ROLE_LIST, ctx.guild.roles[1:][::-1], f":information_source: Roles in {ctx.guild.name}", ctx.author.colour, self.bot, ctx.author, ctx.channel, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format)) await embed.set_page(1) await embed.send()
async def view(self, ctx: commands.Context) -> None: """ View all of the starboards in the current guild """ starboards = await self._get_starboards(ctx.guild.id) embed = self.bot.EmbedPages( self.bot.PageTypes.STARBOARD_LIST, starboards, f":information_source: {ctx.guild.name}'s starboards", self.bot.GOLDEN_YELLOW, self.bot, ctx.author, ctx.channel, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format), ) await embed.set_page(1) await embed.send()
async def quote(self, ctx: commands.Context, messageid: int, channel: discord.TextChannel | discord.Thread) -> None: """ Quote a message to remember it. """ try: msg = await channel.fetch_message(messageid) except Exception: await ctx.send(f"```{ctx.prefix}quote <message_id> [channel_id]```" ) return user = msg.author image = None repl = re.compile(r"/(\[.+?)(\(.+?\))/") edited = f" (edited at {msg.edited_at.isoformat(' ', 'seconds')})" if msg.edited_at else "" content = re.sub(repl, r"\1\2", msg.content) if msg.attachments: image = msg.attachments[0].url embed = Embed( title="Quote link", url=f"https://discordapp.com/channels/{channel.id}/{messageid}", color=user.color, timestamp=msg.created_at) if image: embed.set_image(url=image) embed.set_footer(text=f"Sent by {user.name}#{user.discriminator}", icon_url=get_user_avatar_url(user, mode=1)[0]) embed.description = f"❝ {content} ❞" + edited await ctx.send(embed=embed) try: await ctx.message.delete() except Exception as e: print(e)
async def list(self, ctx: commands.Context, page_num: int = 1) -> None: async with self.bot.pool.acquire() as connection: qotds = await connection.fetch( "SELECT * FROM qotd WHERE guild_id = $1 ORDER BY id", ctx.guild.id) if len(qotds) > 0: embed = self.bot.EmbedPages( self.bot.PageTypes.QOTD, qotds, f"{ctx.guild.name}'s QOTDs", Colour.from_rgb(177, 252, 129), self.bot, ctx.author, ctx.channel, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer= f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format)) await embed.set_page(int(page_num)) await embed.send() else: await ctx.send("No QOTD have been submitted in this guild before.")
async def userinfo(self, ctx: commands.Context, *, args: str = "") -> None: """ Information about you or a user """ author = ctx.author guild = ctx.guild if len(args) == 0: user = author else: user = await self.bot.get_spaced_member(ctx, self.bot, args=args) args = args.replace("<", "").replace(">", "").replace("@", "").replace("!", "") if user is None and args.isdigit(): user = await self.bot.fetch_user( int(args) ) # allows getting some limited info about a user that isn't a member of the guild if user is None: await ctx.send(embed=Embed( title="Userinfo", description= f":x: **Sorry {ctx.author.display_name} we could not find that user!**", color=Colour.from_rgb(255, 7, 58))) return is_member = isinstance(user, discord.Member) if is_member: statuses = "" if user.desktop_status != discord.Status.offline: statuses += f"{getattr(self.bot.EmojiEnum, str(user.desktop_status).upper())} Desktop " if user.web_status != discord.Status.offline: statuses += f"{getattr(self.bot.EmojiEnum, str(user.web_status).upper())} Web " if user.mobile_status != discord.Status.offline: statuses += f"{getattr(self.bot.EmojiEnum, str(user.mobile_status).upper())} Mobile" if not statuses: statuses = "in offline status" else: statuses = f"using {statuses}" data = Embed( description=f"Chilling {statuses}" if is_member else "", colour=user.colour) else: data = Embed(colour=user.colour) data.add_field(name="User ID", value=f"{user.id}") user_created = self.bot.correct_time(user.created_at).strftime( self.bot.ts_format) since_created = (ctx.message.created_at - user.created_at).days created_on = f"{user_created}\n({since_created} days ago)" data.add_field(name="Joined Discord on", value=created_on) if is_member: roles = user.roles[-1:0:-1] joined_at = user.joined_at if joined_at is not None: since_joined = (ctx.message.created_at - joined_at).days user_joined = self.bot.correct_time(joined_at).strftime( self.bot.ts_format) else: since_joined = "?" user_joined = "Unknown" voice_state = user.voice member_number = (sorted(guild.members, key=lambda m: m.joined_at or ctx.message. created_at).index(user) + 1) joined_on = f"{user_joined}\n({since_joined} days ago)" data.add_field(name="Joined this server on", value=joined_on) data.add_field(name="Position", value=f"#{member_number}/{len(guild.members)}") for activity in user.activities: if isinstance(activity, discord.Spotify): diff = discord.utils.utcnow( ) - activity.start # timedeltas have stupid normalisation of days, seconds, milliseconds because that make sense data.add_field( name="Listening to Spotify", value= f"{activity.title} by {activity.artist} on {activity.album} ({self.bot.time_str(diff.seconds + diff.days * 86400)} elapsed)", inline=False) elif isinstance(activity, discord.CustomActivity): data.add_field(name="Custom Status", value=f"{activity.name}", inline=False) else: """ It's worth noting that all activities normally have details attached, but Game objects do NOT have details Rationale: memory optimisation """ if activity.start: diff = discord.utils.utcnow() - activity.start diff = f"({self.bot.time_str(diff.seconds + diff.days * 86400)} elapsed)" else: diff = "" data.add_field( name=f"{type(activity).__name__}", value= f"{activity.name} {diff}\n{'' if not hasattr(activity, 'details') else activity.details}", inline=False) if roles: disp_roles = ", ".join([role.name for role in roles[:10]]) if len(roles) > 10: disp_roles += f" (+{len(roles) - 10} roles)" data.add_field(name="Roles", value=disp_roles, inline=False) else: data.add_field(name="Roles", value="No roles currently!") if voice_state and voice_state.channel: data.add_field( name="Current voice channel", value= f"{voice_state.channel.mention} ID: {voice_state.channel.id}", inline=False, ) data.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + (self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) flags = user.public_flags.all() # e.g. hypesquad stuff if flags: desc = [] for flag in flags: desc.append( self.bot.make_readable(flag.name) ) # PyCharm likes to complain about this but it's an enum so... it's perfectly valid desc = ", ".join(desc) data.add_field(name="Special flags", value=desc, inline=False) name = f"{user} ~ {user.display_name}" avatar = get_user_avatar_url(user, mode=1)[0] data.set_author(name=name, icon_url=avatar) data.set_thumbnail(url=avatar) await ctx.send(embed=data)
async def mute(self, ctx: commands.Context, member: discord.Member, *, args: str = "") -> None: """ Gives a given user the Muted role. Manage roles perm needed. """ role = get(member.guild.roles, id=await self.bot.get_config_key(member, "muted_role")) if not role: await ctx.send(":x: No muted role has been set!") return if role in member.roles: await ctx.send( f":x: **{member}** is already muted! Unmute them and mute them again to change their mute" ) return reason, timeperiod = None, None if args: parsed_args = self.bot.flag_handler.separate_args( args, fetch=["time", "reason"], blank_as_flag="reason") timeperiod = parsed_args["time"] reason = parsed_args["reason"] if timeperiod: await self.bot.tasks.submit_task( "unmute", datetime.utcnow() + timedelta(seconds=timeperiod), extra_columns={ "member_id": member.id, "guild_id": member.guild.id }) await member.add_roles(role, reason=reason if reason else f"No reason - muted by {ctx.author.name}") await ctx.send(f":ok_hand: **{member}** has been muted") # "you are muted " + timestring if not timeperiod: timestring = "indefinitely" else: time = (self.bot.correct_time() + timedelta(seconds=timeperiod) ) # + timedelta(hours = 1) timestring = "until " + time.strftime("%H:%M on %d/%m/%y") if not reason or reason is None: reasonstring = "an unknown reason (the staff member did not give a reason)" else: reasonstring = reason try: await member.send( f"You have been muted {timestring} for {reasonstring}.") except (discord.Forbidden, discord.HTTPException): await ctx.send( f"Could not DM {member.display_name} about their mute!") channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Member Muted", color=Colour.from_rgb(172, 32, 31)) embed.add_field(name="Member", value=f"{member.mention} ({member.id})") embed.add_field(name="Moderator", value=str(ctx.author)) embed.add_field(name="Reason", value=reason) embed.add_field(name="Expires", value=timestring.replace("until ", "") if timestring != "indefinitely" else "Never") embed.set_thumbnail(url=get_user_avatar_url(member, mode=1)[0]) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed)
async def ban(self, ctx: commands.Context, member: discord.Member | str, *, args: str = "") -> None: """ Bans a given user. Merged with previous command hackban Single bans work with user mention or user ID Mass bans work with user IDs currently, reason flag HAS to be specified if setting Ban members perm needed """ if not ctx.me.guild_permissions.ban_members: await ctx.send("Can't do that sorry :(") return invites = await ctx.guild.invites() reason = "No reason provided" massban = (ctx.invoked_with == "massban") timeperiod = tracker = None if args: parsed_args = self.bot.flag_handler.separate_args( args, fetch=["time", "reason"], blank_as_flag="reason" if not massban else None) timeperiod = parsed_args["time"] reason = parsed_args["reason"] if massban: members = ctx.message.content[ctx.message.content.index(" ") + 1:].split(" ") members = [ member for member in members if len(member) == 18 and str(member).isnumeric() ] # possibly rearrange at some point to allow checking if the member/user object can be obtained? tracker = await ctx.send( f"Processed bans for 0/{len(members)} members") else: members = [member] already_banned = [] not_found = [] could_not_notify = [] ban = 0 for ban, member_ in enumerate(members, start=1): if massban: await tracker.edit( content=f"Banning {ban}/{len(members)} users" + (f", {len(not_found)} users not found" if len(not_found) > 0 else "") + (f", {len(already_banned)} users already banned" if len(already_banned) > 0 else "")) if type(member_) is not discord.Member: member, in_guild = await self.get_member_obj(ctx, member_) else: member = member_ in_guild = True if in_guild: if ctx.me.top_role < member.top_role: await ctx.send( f"Can't ban {member.mention}, they have a higher role than the bot!" ) continue for invite in invites: if invite.inviter.id == member.id: await ctx.invoke(self.bot.get_command("revokeinvite"), invite_code=invite.code) if timeperiod: await self.bot.tasks.submit_task("unban", datetime.utcnow() + timedelta(seconds=timeperiod), extra_columns={ "member_id": member.id, "guild_id": ctx.guild.id }) if not member: not_found.append(member_) if not massban: await ctx.send(f"Couldn't find that user ({member_})!") return else: continue if await self.is_user_banned(ctx, member): already_banned.append(member.mention) if not massban: await ctx.send(f"{member.mention} is already banned!") return else: continue try: await member.send( f"You have been banned from {ctx.guild.name} ({reason})") except (discord.Forbidden, discord.HTTPException): if not massban: await ctx.send( f"Could not DM {member.mention} ({member.id}) about their ban!" ) else: could_not_notify.append(member) await ctx.guild.ban(member, reason=reason, delete_message_days=0) if not massban: await ctx.send(f"{member.mention} has been banned.") channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Ban" if in_guild else "Hackban", color=Colour.from_rgb(255, 255, 255)) embed.add_field(name="Member", value=f"{member.mention} ({member.id})") embed.add_field(name="Moderator", value=str(ctx.author)) embed.add_field(name="Reason", value=reason) embed.set_thumbnail(url=get_user_avatar_url(member, mode=1)[0]) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed) if massban: # chr(10) used for \n since you can't have backslash characters in f string fragments await tracker.edit( content=f"Processed bans for {ban}/{len(members)} users" + (f"\n__**These users weren't found**__:\n\n - {f'{chr(10)} - '.join(f'{a_not_found}' for a_not_found in not_found)}\n" if len(not_found) > 0 else "") + (f"\n__**These users are already banned**__:\n\n - {f'{chr(10)} - '.join(f'{a_already_banned}' for a_already_banned in already_banned)}" if len(already_banned) > 0 else "") + (f"\n__**These users couldn't be DMed about their ban**__:\n\n - {f'{chr(10)} - '.join(f'{a_unnotified}' for a_unnotified in could_not_notify)}" if len(could_not_notify) > 0 else ""))
async def config(self, ctx: commands.Context) -> None: """ View the current configuration settings of the guild """ if not (ctx.author.guild_permissions.administrator or await self.is_staff(ctx)): await self.bot.DefaultEmbedResponses.invalid_perms(self.bot, ctx) return if ctx.invoked_subcommand is None: # User is staff and no subcommand => send embed """ To get the config options 1. Copy self.CONFIG into a new variable 2. Foreach config option, append the option to the list under the specific option's key (this is the embed value) 3. Pass the dict into EmbedPages for formatting """ data = copy.deepcopy(self.CONFIG) config_dict = self.bot.configs[ctx.guild.id] for key in config_dict.keys(): if key not in data.keys(): continue # This clause ensures that variables, e.g. "bruhs", that are in the DB but not in self.CONFIG, do not appear if not config_dict[key] or data[key][0] not in [ Validation.Channel, Validation.Role ]: data[key].append(config_dict[key] if config_dict[key] is not None else "*N/A*") elif data[key][0] == Validation.Channel: channel = ctx.guild.get_channel_or_thread( config_dict[key] ) # note that this is gonna have to be looked at again if any other types of channels are to be allowed data[key].append(f"{channel.mention} ({config_dict[key]})" if channel else "*N/A*") elif data[key][0] == Validation.Role: role = ctx.guild.get_role(config_dict[key]) data[key].append(f"{role.mention} ({config_dict[key]})" if role else "*N/A*") p = ( await self.bot.get_used_prefixes(ctx) )[-1] # guild will be last if set, if not it'll fall back to global desc = f"Below are the configurable options for {ctx.guild.name}. To change one, do `{p}config set <key> <value>` where <key> is the option you'd like to change, e.g. `{p}config set qotd_limit 2`" embed = self.bot.EmbedPages( self.bot.PageTypes.CONFIG, data, f":tools: {ctx.guild.name} ({ctx.guild.id}) configuration", ctx.author.colour, self.bot, ctx.author, ctx.channel, desc=desc, thumbnail_url=get_guild_icon_url(ctx.guild), icon_url=get_user_avatar_url(ctx.author, mode=1)[0], footer= f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + self.bot.correct_time().strftime(self.bot.ts_format)) await embed.set_page(1) # Default first page await embed.send()
async def lurker_kick(self, ctx: commands.Context, days: str = "7") -> None: # days is specifically "7" as default and not 7 since if you specify an integer it barfs if you supply a non-int value """ Command that kicks people without a role, and joined 7 or more days ago. """ def check(m: discord.Message) -> bool: return m.channel == ctx.channel and m.author == ctx.author if not days.isnumeric(): await ctx.send("Specify a whole, non-zero number of days!") return days = int(days) time_ago = discord.utils.utcnow() - datetime.timedelta(days=days) members = [ x for x in ctx.guild.members if len(x.roles) <= 1 and x.joined_at < time_ago ] # Members with only the everyone role and more than 7 days ago if len(members) == 0: await ctx.send( f"There are no lurkers to kick that have been here {days} days or longer!" ) return question = await ctx.send( f"Do you want me to kick all lurkers that have been here {days} days or longer ({len(members)} members)? (Type either 'yes' or 'no')" ) try: response = await self.bot.wait_for("message", check=check, timeout=300) except asyncio.TimeoutError: await question.delete() return if response.content.lower() == "yes": for i in range(len(members)): member = members[i] await question.edit( content=f"Kicked {i}/{len(members)} lurkers :ok_hand:") await member.kick( reason="Auto-kicked following lurker kick command.") await question.edit( content= f"All {len(members)} lurkers that have been here more than {days} days have been kicked :ok_hand:" ) elif response.content.lower() == "no": await question.edit(content="No lurkers have been kicked :ok_hand:" ) else: await question.edit( content= "Unknown response, therefore no lurkers have been kicked :ok_hand:" ) channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Lurker-kick", color=Colour.from_rgb(220, 123, 28)) embed.add_field(name="Members", value=str(len(members))) embed.add_field(name="Reason", value="Auto-kicked from the -lurkers kick command") embed.add_field(name="Initiator", value=ctx.author.mention) embed.set_thumbnail(url=get_user_avatar_url(ctx.author, mode=1)[0]) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed)
async def prop_change_handler(self, before: discord.Member, after: discord.Member) -> dict: """ God handler which handles all the default logging embed behaviour Works for both member and user objects """ """ Property definitions """ user_updated_colour = Colour.from_rgb( 214, 174, 50) # Storing as var quicker than initialising each time watched_props = [{ "name": "display_name", "display_name": "Nickname", "colour": user_updated_colour, "custom_handler": self.disp_name_handler }, { "name": "roles", "display_name": "Roles", "colour": user_updated_colour, "custom_handler": self.embed_role_comparison }, { "name": "avatar", "display_name": "Avatar", "colour": user_updated_colour, "custom_handler": self.avatar_handler }, { "name": "name", "display_name": "Username", "colour": user_updated_colour, "custom_handler": None }, { "name": "discriminator", "display_name": "Discriminator", "colour": user_updated_colour, "custom_handler": None }] for prop in watched_props: thumbnail_set = False if hasattr(before, prop["name"]) and hasattr( after, prop["name"] ): # user objects don't have all the same properties as member objects if (getattr(before, prop["name"]) != getattr( after, prop["name"])) or ( prop["name"] == "avatar" and get_user_avatar_url(before)[0] != get_user_avatar_url(after)[0] ): # TODO: Fix up the edge case with avatars? log = Embed( title= f":information_source: {prop['display_name']} Updated", color=prop["colour"]) log.add_field(name="User", value=f"{after} ({after.id})", inline=True) if not prop["custom_handler"]: log.add_field( name=f"Old {prop['display_name'].lower()}", value=getattr(before, prop["name"])) log.add_field( name=f"New {prop['display_name'].lower()}", value=getattr(after, prop["name"])) else: """ Calls the custom embed handler as defined Custom embed handlers are expected to return dict type objects to be handled below """ result = await prop["custom_handler"](before, after) if result: # return None for no result if "fields" in result: for field in result["fields"]: log.add_field(name=field["name"], value=field["value"]) if "description" in result: log.description = result["description"] if "image" in result: log.set_image(url=f"{result['image']}") if "thumbnail_url" in result: log.set_thumbnail( url=f"{result['thumbnail_url']}") thumbnail_set = True else: continue if not thumbnail_set: log.set_thumbnail( url=get_user_avatar_url(after, mode=1)[0]) log.set_footer(text=self.bot.correct_time().strftime( self.bot.ts_format)) # Send `log` embed to all servers the user is part of, unless its a nickname change or role change (which are server specific) if prop["display_name"] in ["Nickname", "Roles"]: channel = await self.get_log_channel( before.guild, "member_update") if channel: await channel.send(embed=log) else: shared_guilds = [ x for x in self.bot.guilds if after in x.members ] for guild in shared_guilds: channel = await self.get_log_channel( guild, "member_update") if channel: await channel.send(embed=log)
async def award(self, ctx: commands.Context, *, args: discord.Member | discord.User | str) -> None: """ Gives the member a reputation point. Aliases are give and point """ if type(args) == discord.Member: user = args elif type(args) == discord.User: user = ctx.guild.get_member(args.id) else: # todo: sort this mess out user = await self.bot.get_spaced_member( ctx, self.bot, args=args ) if args else None # check so rep award doesn't silently fail when no string given if not user: failed = Embed(title=f":x: Sorry we could not find the user!" if args else "Rep Help", color=self.bot.ERROR_RED) if args: failed.add_field(name="Requested user", value=args) failed.add_field( name="Information", value= f"\nTo award rep to someone, type \n`{ctx.prefix}rep Member_Name`\nor\n`{ctx.prefix}rep @Member`\n" f"Pro tip: If e.g. fred roberto was recently active you can type `{ctx.prefix}rep fred`\n\nTo see the other available rep commands type `{ctx.prefix}help rep`", inline=False) failed.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + (self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) await ctx.send(embed=failed) return nick = user.display_name if ctx.author != user and not user.bot: # Check prevents self-rep and that receiver is not a bot award_banned_role = ctx.guild.get_role( await self.bot.get_config_key(ctx, "rep_award_banned")) receive_banned_role = ctx.guild.get_role( await self.bot.get_config_key(ctx, "rep_receive_banned")) award_banned = award_banned_role in ctx.author.roles receive_banned = receive_banned_role in user.roles award = not award_banned and not receive_banned title = f"You have been blocked from awarding reputation points" if award_banned else "" title += " and " if award_banned and receive_banned else "!" if award_banned and not receive_banned else "" title += f"{nick} has been blocked from receiving reputation points!" if receive_banned else "" title += f"\n\n{nick} did not receive a reputation point!" if not award else "" if award: reps = await self.modify_rep(user, 1) await self.bot.DefaultEmbedResponses.success_embed( self.bot, ctx, f"{nick} received a reputation point!", desc=f"{user.mention} now has {reps} reputation points!", thumbnail_url=get_user_avatar_url(user, mode=1)[0]) # Log rep channel_id = await self.bot.get_config_key(ctx, "log_channel") if channel_id is None: return channel = self.bot.get_channel(channel_id) embed = Embed(title="Reputation Points", color=Colour.from_rgb(177, 252, 129)) embed.add_field(name="From", value=f"{str(ctx.author)} ({ctx.author.id})") embed.add_field(name="To", value=f"{str(user)} ({user.id})") embed.add_field(name="New Rep", value=reps) embed.add_field(name="Awarded in", value=ctx.channel.mention) embed.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) await channel.send(embed=embed) else: # Rep cannot be given await self.bot.DefaultEmbedResponses.error_embed( self.bot, ctx, title, desc= "Contact a member of staff if you think you are seeing this by mistake.", thumbnail_url=get_user_avatar_url(user, mode=1)[0]) else: desc = "The bot overlords do not accept puny humans' rewards" if user.bot else "You cannot rep yourself, cheating bugger." await self.bot.DefaultEmbedResponses.error_embed( self.bot, ctx, f"Failed to award a reputation point to {nick}", desc=desc, thumbnail_url=get_user_avatar_url(user, mode=1)[0])
async def resultsday(self, ctx: commands.Context, hour: str = "") -> None: if ctx.invoked_with in ["resultsday", "gcseresults", "results", None]: which = "GCSE" else: which = "A-Level" if not hour: hour = 10 else: try: hour = int(hour) except ValueError: await ctx.send( "You must choose an integer between 0 and 23 for the command to work!" ) if not 0 <= hour < 24: await ctx.send("The hour must be between 0 and 23!") return if hour == 12: string = "noon" elif hour == 0: string = "0:00AM" elif hour >= 12: string = f"{hour - 12}PM" else: string = f"{hour}AM" rn = self.bot.correct_time() if which == "GCSE": time_ = self.bot.correct_time( datetime(year=2022, month=8, day=18, hour=hour, minute=0, second=0)) else: time_ = self.bot.correct_time( datetime(year=2022, month=8, day=11, hour=hour, minute=0, second=0)) embed = Embed( title= f"Countdown until {which} results day at {string} (on {time_.day}/{time_.month}/{time_.year})", color=Colour.from_rgb(148, 0, 211)) if rn > time_: embed.description = "Results have already been released!" else: time_ = time_ - rn m, s = divmod(time_.seconds, 60) h, m = divmod(m, 60) embed.description = f"{time_.days} days {h} hours {m} minutes {s} seconds remaining" embed.set_footer( text=f"Requested by: {ctx.author.display_name} ({ctx.author})\n" + (self.bot.correct_time()).strftime(self.bot.ts_format), icon_url=get_user_avatar_url(ctx.author, mode=1)[0]) icon_url = get_guild_icon_url(ctx.guild) if icon_url: embed.set_thumbnail(url=icon_url) await ctx.send(embed=embed)
async def on_member_join(self, member: discord.Member) -> None: guild = member.guild ichannel = await self.get_log_channel(guild, "join_leave") if ichannel is None: # If invite channel not set return old_invites = self.invites[guild.id] new_invites = await self.get_all_invites(guild) updated_invites = [] possible_joins_missed = False for invite in new_invites: found = False for old_invite in old_invites: if old_invite.code == invite.code: found = True if invite.uses > old_invite.uses: updated_invites.append(invite) if invite.uses - old_invite.uses != 1: possible_joins_missed = True if not found and invite.uses != 0: # else 0-use invites will be logged updated_invites.append(invite) # new invites self.invites[guild.id] = new_invites if ichannel: # still check & update invites in case channel is configured later invite_log = Embed(title="Invite data", color=Colour.from_rgb(0, 0, 255)) if len(updated_invites) == 1: invite_log.set_author( name= f"{updated_invites[0].inviter} ~ {updated_invites[0].inviter.display_name}" if updated_invites[0].inviter else f"{guild.name} ~ Vanity URL", icon_url=get_user_avatar_url(updated_invites[0].inviter, mode=1)[0]) invite_log.description = ":arrow_up: Inviter Avatar\n:arrow_right: Member Avatar" else: invite_log.description = ":arrow_right: Member Avatar" invite_log.add_field( name="Member", value=f"{member.mention} [{member.name}] ({member.id})") for x, invite in enumerate(updated_invites): invite_log.add_field( name=f"\nPossible Invite #{x + 1}\n\nInviter" if len(updated_invites) != 1 else "Inviter", value=invite.inviter.mention if invite.inviter else "Server (Vanity)", inline=len(updated_invites) == 1) invite_log.add_field(name="Code", value=invite.code) invite_log.add_field(name="Channel", value=invite.channel.mention) invite_log.add_field(name="Expires", value=self.bot.time_str(invite.max_age) if invite.max_age != 0 else "Never") invite_log.add_field( name="Uses", value=str(invite.uses) + (f"/{invite.max_uses}" if invite.max_uses != 0 else "")) invite_log.add_field(name="Invite Created", value=self.bot.correct_time( invite.created_at).strftime( self.bot.ts_format), inline=False) invite_log.add_field(name="Account created", value=self.bot.correct_time( member.created_at).strftime( self.bot.ts_format), inline=False) if not updated_invites: invite_log.add_field(name="Invite used", value="Server Discovery") invite_log.set_thumbnail( url=get_user_avatar_url(member, mode=1)[0]) invite_log.set_footer( text=self.bot.correct_time().strftime(self.bot.ts_format)) if ( invite_log.to_dict() not in self.previous_inv_log_embeds ) or not updated_invites: # limits log spam e.g. if connection drops #if possible_joins_missed or len(updated_invites) != 1: # await get(guild.text_channels, name="invite-logs").send("*WARNING: Due to a bot glitch or other reason, the below data may be inaccurate due to potentially missed previous joins.*") self.previous_inv_log_embeds.append(invite_log.to_dict()) await ichannel.send(embed=invite_log)