async def process_kwargs( self, author: UnionUser, channel: UnionChannel, jump_url: str, *, content: Optional[str], attachments: List[discord.Attachment], ) -> Dict[str, Any]: guild: Optional[discord.Guild] = getattr(channel, "guild", None) oga = author if guild: author = guild.get_member(author.id) or self.bot.get_user( author.id) assert author if not await self.bot.allowed_by_whitelist_blacklist(author): raise RiftError(_("You are not permitted to use the bot here.")) is_owner = await self.bot.is_owner(author) me = (guild or channel).me # type: ignore author_perms = self.permissions(channel, author, is_owner) bot_perms = self.permissions(channel, me) both_perms = discord.Permissions(author_perms.value & bot_perms.value) if guild and content and not is_owner and not await self.bot.is_automod_immune( author): assert isinstance(channel, discord.TextChannel) filt: Optional["Filter"] = self.bot.get_cog( "Filter") # type: ignore if filt and await filt.filter_hits(content, channel): raise RiftError(_("Your message was filtered.")) embed: Optional[discord.Embed] if await self.bot.embed_requested(channel, getattr(channel, "recipient", author), command=self.rift): embed = discord.Embed(colour=oga.colour or await self.bot.get_embed_color(channel), url=jump_url) ogg: Optional[discord.Guild] if ogg := getattr(oga, "guild", None): assert isinstance(oga, discord.Member) if oga.top_role != ogg.default_role: embed.title = filter_invites(f"{oga.top_role} in {ogg}") else: embed.title = filter_invites(f"in {ogg}") embed.set_author( name=filter_invites(str(author)), icon_url=author.avatar_url_as(size=32), )
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 "" ) max_zpadding = max([len(str(g.member_count)) for g in guilds]) form = "{gid} :: {mems:0{zpadding}} :: {name}" all_forms = [ form.format( gid=g.id, mems=g.member_count, name=filter_invites(cf.escape(g.name)), zpadding=max_zpadding ) for g in guilds ] final = "\n".join(all_forms) await ctx.send(header) page_list = [] for page in cf.pagify(final, delims=["\n"], page_length=1000): page_list.append(asciidoc(page)) if len(page_list) == 1: return await ctx.send(asciidoc(page)) await menu(ctx, page_list, DEFAULT_CONTROLS)
async def get_names_and_nicks(self, member: Union[discord.Member, discord.User]): try: mod = self.bot.cogs["Mod"] except KeyError: return [], [] names = await mod.settings.user(member).past_names() nicks = ( await mod.settings.member(member).past_nicks() if isinstance(member, discord.Member) else [] ) if names: names = [filter_invites(escape(name, mass_mentions=True)) for name in names if name] if nicks: nicks = [filter_invites(escape(nick, mass_mentions=True)) for nick in nicks if nick] return names, nicks
async def process_message(self, rift, message, destination): if isinstance(destination, discord.Message): send_coro = destination.edit else: send_coro = destination.send channel = ( message.author if isinstance(message.channel, discord.DMChannel) else message.channel ) send = channel == rift.source destination = rift.destination if send else rift.source author = message.author me = ( destination.dm_channel.me if isinstance(destination, discord.User) else destination.guild.me ) is_owner = await self.bot.is_owner(author) author_perms = self.permissions(destination, author, is_owner) bot_perms = self.permissions(destination, me) content = message.content if not is_owner: if not author_perms.administrator: content = common_filters.filter_invites(content) if not author_perms.mention_everyone: content = common_filters.filter_mass_mentions(content) attachments = message.attachments files = [] embed = None if attachments and author_perms.attach_files and bot_perms.attach_files: overs = await asyncio.gather(*(self.save_attach(file, files) for file in attachments)) overs = list(filter(bool, overs)) if overs: if bot_perms.embed_links: embed = await self.get_embed(destination, overs) else: content += ( "\n\n" + _("Attachments:") + "\n" + "\n".join(f"({self.xbytes(a.size)}) {a.url}" for a in attachments) ) if not any((content, files, embed)): raise RiftError(_("No content to send.")) if not is_owner or not send: content = f"{author}: {content}" return await send_coro(content=content, files=files, embed=embed)
async def process_message(self, rift, message, destination): if isinstance(destination, discord.Message): send_coro = destination.edit else: send_coro = destination.send channel = ( message.author if isinstance(message.channel, discord.DMChannel) else message.channel ) send = channel == rift.source destination = rift.destination if send else rift.source source = rift.source if send else rift.destination author = message.author me = ( destination.dm_channel.me if isinstance(destination, discord.User) else destination.guild.me ) is_owner = await self.bot.is_owner(author) author_perms = self.permissions(destination, author, is_owner) bot_perms = self.permissions(destination, me) content = message.content if not is_owner: if not author_perms.administrator: content = common_filters.filter_invites(content) if not author_perms.mention_everyone: content = common_filters.filter_mass_mentions(content) attachments = message.attachments files = [] embed = None if attachments and author_perms.attach_files and bot_perms.attach_files: overs = await asyncio.gather(*(self.save_attach(file, files) for file in attachments)) overs = list(filter(bool, overs)) if overs: content += ( "\n\n" + _("Attachments:") + "\n" + "\n".join(f"({self.xbytes(a.size)}) {a.url}" for a in attachments) ) if not any((content, files, embed)): raise RiftError(_("No content to send.")) msg_embed = await self.create_message_embed(ctx=message, source=source, content=content, files=attachments) return await send_coro(embed=msg_embed)
class ModInfo(MixinMeta): """ Commands regarding names, userinfo, etc. """ async def get_names_and_nicks(self, user): names = await self.config.user(user).past_names() nicks = await self.config.member(user).past_nicks() if names: names = [ escape_spoilers_and_mass_mentions(name) for name in names if name ] if nicks: nicks = [ escape_spoilers_and_mass_mentions(nick) for nick in nicks if nick ] return names, nicks @commands.command() @commands.guild_only() @commands.bot_has_permissions(manage_nicknames=True) @checks.admin_or_permissions(manage_nicknames=True) async def rename(self, ctx: commands.Context, member: discord.Member, *, nickname: str = ""): """Change a member's nickname. Leaving the nickname empty will remove it. """ nickname = nickname.strip() me = cast(discord.Member, ctx.me) if not nickname: nickname = None elif not 2 <= len(nickname) <= 32: await ctx.send( _("Nicknames must be between 2 and 32 characters long.")) return if not ((me.guild_permissions.manage_nicknames or me.guild_permissions.administrator) and me.top_role > member.top_role and member != ctx.guild.owner): await ctx.send( _("I do not have permission to rename that member. They may be higher than or " "equal to me in the role hierarchy.")) elif ctx.author != member and not await is_allowed_by_hierarchy( self.bot, self.config, ctx.guild, ctx.author, member): await ctx.send( _("I cannot let you do that. You are " "not higher than the user in the role " "hierarchy.")) else: try: await member.edit(reason=get_audit_reason(ctx.author, None), nick=nickname) except discord.Forbidden: # Just in case we missed something in the permissions check above await ctx.send( _("I do not have permission to rename that member.")) except discord.HTTPException as exc: if exc.status == 400: # BAD REQUEST await ctx.send(_("That nickname is invalid.")) else: await ctx.send(_("An unexpected error has occured.")) else: await ctx.send(_("Done.")) def handle_custom(self, user): a = [ c for c in user.activities if c.type == discord.ActivityType.custom ] if not a: return None, discord.ActivityType.custom a = a[0] c_status = None if not a.name and not a.emoji: return None, discord.ActivityType.custom elif a.name and a.emoji: c_status = _("Custom: {emoji} {name}").format(emoji=a.emoji, name=a.name) elif a.emoji: c_status = _("Custom: {emoji}").format(emoji=a.emoji) elif a.name: c_status = _("Custom: {name}").format(name=a.name) return c_status, discord.ActivityType.custom def handle_playing(self, user): p_acts = [ c for c in user.activities if c.type == discord.ActivityType.playing ] if not p_acts: return None, discord.ActivityType.playing p_act = p_acts[0] act = _("Playing: {name}").format(name=p_act.name) return act, discord.ActivityType.playing def handle_streaming(self, user): s_acts = [ c for c in user.activities if c.type == discord.ActivityType.streaming ] if not s_acts: return None, discord.ActivityType.streaming s_act = s_acts[0] if isinstance(s_act, discord.Streaming): act = _("Streaming: [{name}{sep}{game}]({url})").format( name=discord.utils.escape_markdown(s_act.name), sep=" | " if s_act.game else "", game=discord.utils.escape_markdown(s_act.game) if s_act.game else "", url=s_act.url, ) else: act = _("Streaming: {name}").format(name=s_act.name) return act, discord.ActivityType.streaming def handle_listening(self, user): l_acts = [ c for c in user.activities if c.type == discord.ActivityType.listening ] if not l_acts: return None, discord.ActivityType.listening l_act = l_acts[0] if isinstance(l_act, discord.Spotify): act = _("Listening: [{title}{sep}{artist}]({url})").format( title=discord.utils.escape_markdown(l_act.title), sep=" | " if l_act.artist else "", artist=discord.utils.escape_markdown(l_act.artist) if l_act.artist else "", url=f"https://open.spotify.com/track/{l_act.track_id}", ) else: act = _("Listening: {title}").format(title=l_act.name) return act, discord.ActivityType.listening def handle_watching(self, user): w_acts = [ c for c in user.activities if c.type == discord.ActivityType.watching ] if not w_acts: return None, discord.ActivityType.watching w_act = w_acts[0] act = _("Watching: {name}").format(name=w_act.name) return act, discord.ActivityType.watching def handle_competing(self, user): w_acts = [ c for c in user.activities if c.type == discord.ActivityType.competing ] if not w_acts: return None, discord.ActivityType.competing w_act = w_acts[0] act = _("Competing in: {competing}").format(competing=w_act.name) return act, discord.ActivityType.competing def get_status_string(self, user): string = "" for a in [ self.handle_custom(user), self.handle_playing(user), self.handle_listening(user), self.handle_streaming(user), self.handle_watching(user), self.handle_competing(user), ]: status_string, status_type = a if status_string is None: continue string += f"{status_string}\n" return string @commands.command() @commands.guild_only() @commands.bot_has_permissions(embed_links=True) async def userinfo(self, ctx, *, member: discord.Member = None): """Show information about a member. This includes fields for status, discord join date, server join date, voice state and previous names/nicknames. If the member has no roles, previous names or previous nicknames, these fields will be omitted. """ author = ctx.author guild = ctx.guild if not member: member = author # A special case for a special someone :^) special_date = datetime.datetime(2016, 1, 10, 6, 8, 4, 443000, datetime.timezone.utc) is_special = member.id == 96130341705637888 and guild.id == 133049272517001216 roles = member.roles[-1:0:-1] names, nicks = await self.get_names_and_nicks(member) if is_special: joined_at = special_date elif joined_at := member.joined_at: joined_at = joined_at.replace(tzinfo=datetime.timezone.utc) user_created = int( member.created_at.replace( tzinfo=datetime.timezone.utc).timestamp()) voice_state = member.voice member_number = (sorted( guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(member) + 1) created_on = "<t:{0}>\n(<t:{0}:R>)".format(user_created) if joined_at is not None: joined_on = "<t:{0}>\n(<t:{0}:R>)".format( int(joined_at.timestamp())) else: joined_on = _("Unknown") if any(a.type is discord.ActivityType.streaming for a in member.activities): statusemoji = "\N{LARGE PURPLE CIRCLE}" elif member.status.name == "online": statusemoji = "\N{LARGE GREEN CIRCLE}" elif member.status.name == "offline": statusemoji = "\N{MEDIUM WHITE CIRCLE}\N{VARIATION SELECTOR-16}" elif member.status.name == "dnd": statusemoji = "\N{LARGE RED CIRCLE}" elif member.status.name == "idle": statusemoji = "\N{LARGE ORANGE CIRCLE}" activity = _("Chilling in {} status").format(member.status) status_string = self.get_status_string(member) if roles: role_str = ", ".join([x.mention for x in roles]) # 400 BAD REQUEST (error code: 50035): Invalid Form Body # In embed.fields.2.value: Must be 1024 or fewer in length. if len(role_str) > 1024: # Alternative string building time. # This is not the most optimal, but if you're hitting this, you are losing more time # to every single check running on users than the occasional user info invoke # We don't start by building this way, since the number of times we hit this should be # infinitesimally small compared to when we don't across all uses of Red. continuation_string = _( "and {numeric_number} more roles not displayed due to embed limits." ) available_length = 1024 - len( continuation_string) # do not attempt to tweak, i18n role_chunks = [] remaining_roles = 0 for r in roles: chunk = f"{r.mention}, " chunk_size = len(chunk) if chunk_size < available_length: available_length -= chunk_size role_chunks.append(chunk) else: remaining_roles += 1 role_chunks.append( continuation_string.format(numeric_number=remaining_roles)) role_str = "".join(role_chunks) else: role_str = None data = discord.Embed(description=status_string or activity, colour=member.colour) data.add_field(name=_("Joined Discord on"), value=created_on) data.add_field(name=_("Joined this server on"), value=joined_on) if role_str is not None: data.add_field(name=_("Roles") if len(roles) > 1 else _("Role"), value=role_str, inline=False) if names: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(names)) data.add_field( name=_("Previous Names") if len(names) > 1 else _("Previous Name"), value=val, inline=False, ) if nicks: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(nicks)) data.add_field( name=_("Previous Nicknames") if len(nicks) > 1 else _("Previous Nickname"), value=val, inline=False, ) if voice_state and voice_state.channel: data.add_field( name=_("Current voice channel"), value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) data.set_footer(text=_("Member #{} | User ID: {}").format( member_number, member.id)) name = str(member) name = " ~ ".join((name, member.nick)) if member.nick else name name = filter_invites(name) avatar = member.avatar_url_as(static_format="png") data.set_author(name=f"{statusemoji} {name}", url=avatar) data.set_thumbnail(url=avatar) await ctx.send(embed=data)
async def userinfo(self, ctx, *, user: discord.Member = None): """Show userinfo with some more detail.""" mod = self.bot.get_cog("Mod") async with ctx.typing(): author = ctx.author guild = ctx.guild if not user: user = author sharedguilds = { guild async for guild in AsyncIter(self.bot.guilds, steps=100) if user in guild.members } roles = user.roles[-1:0:-1] names, nicks = await mod.get_names_and_nicks(user) joined_at = user.joined_at since_created = int((ctx.message.created_at - user.created_at).days) if joined_at is not None: since_joined = int((ctx.message.created_at - joined_at).days) user_joined = joined_at.strftime("%d %b %Y %H:%M") else: since_joined = "?" user_joined = "Unknown" user_created = user.created_at.strftime("%d %b %Y %H:%M") voice_state = user.voice member_number = ( sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index( user ) + 1 ) created_on = "{}\n({} day{} ago)".format( user_created, since_created, "" if since_created == 1 else "s" ) joined_on = "{}\n({} day{} ago)".format( user_joined, since_joined, "" if since_joined == 1 else "s" ) if user.is_on_mobile(): statusemoji = ( self.status_emojis["mobile"] if self.status_emojis["mobile"] else "\N{MOBILE PHONE}" ) elif any(a.type is discord.ActivityType.streaming for a in user.activities): statusemoji = ( self.status_emojis["streaming"] if self.status_emojis["streaming"] else "\N{LARGE PURPLE CIRCLE}" ) elif user.status.name == "online": statusemoji = ( self.status_emojis["online"] if self.status_emojis["online"] else "\N{LARGE GREEN CIRCLE}" ) elif user.status.name == "offline": statusemoji = ( self.status_emojis["offline"] if self.status_emojis["offline"] else "\N{MEDIUM WHITE CIRCLE}" ) elif user.status.name == "dnd": statusemoji = ( self.status_emojis["dnd"] if self.status_emojis["dnd"] else "\N{LARGE RED CIRCLE}" ) elif user.status.name == "idle": statusemoji = ( self.status_emojis["away"] if self.status_emojis["away"] else "\N{LARGE ORANGE CIRCLE}" ) else: statusemoji = "\N{MEDIUM BLACK CIRCLE}\N{VARIATION SELECTOR-16}" activity = "Chilling in {} status".format(user.status) status_string = mod.get_status_string(user) if roles: role_str = ", ".join([x.mention for x in roles]) # 400 BAD REQUEST (error code: 50035): Invalid Form Body # In embed.fields.2.value: Must be 1024 or fewer in length. if len(role_str) > 1024: # Alternative string building time. # This is not the most optimal, but if you're hitting this, you are losing more time # to every single check running on users than the occasional user info invoke # We don't start by building this way, since the number of times we hit this should be # infintesimally small compared to when we don't across all uses of Red. continuation_string = ( "and {numeric_number} more roles not displayed due to embed limits." ) available_length = 1024 - len( continuation_string ) # do not attempt to tweak, i18n role_chunks = [] remaining_roles = 0 for r in roles: chunk = f"{r.mention}, " chunk_size = len(chunk) if chunk_size < available_length: available_length -= chunk_size role_chunks.append(chunk) else: remaining_roles += 1 role_chunks.append(continuation_string.format(numeric_number=remaining_roles)) role_str = "".join(role_chunks) else: role_str = None data = discord.Embed( description=(status_string or activity) + f"\n\n{len(sharedguilds)} shared servers." if len(sharedguilds) > 1 else f"\n\n{len(sharedguilds)} shared server.", colour=user.colour, ) data.add_field(name="Joined Discord on", value=created_on) data.add_field(name="Joined this server on", value=joined_on) if role_str is not None: data.add_field(name="Roles", value=role_str, inline=False) if names: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(names)) data.add_field(name="Previous Names", value=val, inline=False) if nicks: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(nicks)) data.add_field(name="Previous Nicknames", value=val, inline=False) if voice_state and voice_state.channel: data.add_field( name="Current voice channel", value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) data.set_footer(text="Member #{} | User ID: {}".format(member_number, user.id)) name = str(user) name = " ~ ".join((name, user.nick)) if user.nick else name name = filter_invites(name) avatar = user.avatar_url_as(static_format="png") data.title = f"{statusemoji} {name}" data.set_thumbnail(url=avatar) flags = [f.name for f in user.public_flags.all()] badges = "" badge_count = 0 if flags: for badge in sorted(flags): if badge == "verified_bot": emoji1 = self.badge_emojis["verified_bot"] emoji2 = self.badge_emojis["verified_bot2"] if emoji1: emoji = f"{emoji1}{emoji2}" else: emoji = None else: emoji = self.badge_emojis[badge] if emoji: badges += f"{emoji} {badge.replace('_', ' ').title()}\n" else: badges += f"\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16} {badge.replace('_', ' ').title()}\n" badge_count += 1 if badges: data.add_field(name="Badges" if badge_count > 1 else "Badge", value=badges) if "Economy" in self.bot.cogs: balance_count = 1 bankstat = f"**Bank**: {str(humanize_number(await bank.get_balance(user)))} {await bank.get_currency_name(ctx.guild)}\n" if "Unbelievaboat" in self.bot.cogs: cog = self.bot.get_cog("Unbelievaboat") state = await cog.walletdisabledcheck(ctx) if not state: balance_count += 1 balance = await cog.walletbalance(user) bankstat += f"**Wallet**: {str(humanize_number(balance))} {await bank.get_currency_name(ctx.guild)}\n" if "Adventure" in self.bot.cogs: cog = self.bot.get_cog("Adventure") if getattr(cog, "_separate_economy", False): global adventure_bank if adventure_bank is None: try: from adventure import bank as adventure_bank except: pass if adventure_bank: adventure_currency = await adventure_bank.get_balance(user) balance_count += 1 bankstat += f"**Adventure**: {str(humanize_number(adventure_currency))} {await adventure_bank.get_currency_name(ctx.guild)}" data.add_field(name="Balances" if balance_count > 1 else "Balance", value=bankstat) await ctx.send(embed=data)
async def userinfo(self, ctx, *, user: discord.Member = None): """Show information about a user. This includes fields for status, discord join date, server join date, voice state and previous names/nicknames. If the user has no roles, previous names or previous nicknames, these fields will be omitted. """ author = ctx.author guild = ctx.guild if not user: user = author # A special case for a special someone :^) special_date = datetime(2016, 1, 10, 6, 8, 4, 443000) is_special = user.id == 96130341705637888 and guild.id == 133049272517001216 roles = user.roles[-1:0:-1] names, nicks = await self.get_names_and_nicks(user) joined_at = user.joined_at if not is_special else special_date since_created = (ctx.message.created_at - user.created_at).days if joined_at is not None: since_joined = (ctx.message.created_at - joined_at).days user_joined = joined_at.strftime("%d %b %Y %H:%M") else: since_joined = "?" user_joined = _("Unknown") user_created = user.created_at.strftime("%d %b %Y %H:%M") voice_state = user.voice member_number = (sorted( guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1) created_on = _("{}\n({} days ago)").format(user_created, since_created) joined_on = _("{}\n({} days ago)").format(user_joined, since_joined) if any(a.type is discord.ActivityType.streaming for a in user.activities): statusemoji = "\N{LARGE PURPLE CIRCLE}" elif user.status.name == "online": statusemoji = "\N{LARGE GREEN CIRCLE}" elif user.status.name == "offline": statusemoji = "\N{MEDIUM WHITE CIRCLE}\N{VARIATION SELECTOR-16}" elif user.status.name == "dnd": statusemoji = "\N{LARGE RED CIRCLE}" elif user.status.name == "idle": statusemoji = "\N{LARGE ORANGE CIRCLE}" activity = _("Chilling in {} status").format(user.status) status_string = self.get_status_string(user) if roles: role_str = ", ".join([x.mention for x in roles]) # 400 BAD REQUEST (error code: 50035): Invalid Form Body # In embed.fields.2.value: Must be 1024 or fewer in length. if len(role_str) > 1024: # Alternative string building time. # This is not the most optimal, but if you're hitting this, you are losing more time # to every single check running on users than the occasional user info invoke # We don't start by building this way, since the number of times we hit this should be # infintesimally small compared to when we don't across all uses of Red. continuation_string = _( "and {numeric_number} more roles not displayed due to embed limits." ) available_length = 1024 - len( continuation_string) # do not attempt to tweak, i18n role_chunks = [] remaining_roles = 0 for r in roles: chunk = f"{r.mention}, " chunk_size = len(chunk) if chunk_size < available_length: available_length -= chunk_size role_chunks.append(chunk) else: remaining_roles += 1 role_chunks.append( continuation_string.format(numeric_number=remaining_roles)) role_str = "".join(role_chunks) else: role_str = None data = discord.Embed(description=status_string or activity, colour=user.colour) data.add_field(name=_("Joined Discord on"), value=created_on) data.add_field(name=_("Joined this server on"), value=joined_on) if role_str is not None: data.add_field(name=_("Roles"), value=role_str, inline=False) if names: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(names)) data.add_field(name=_("Previous Names"), value=val, inline=False) if nicks: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(nicks)) data.add_field(name=_("Previous Nicknames"), value=val, inline=False) if voice_state and voice_state.channel: data.add_field( name=_("Current voice channel"), value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) data.set_footer( text=_("Member #{} | User ID: {}").format(member_number, user.id)) name = str(user) name = " ~ ".join((name, user.nick)) if user.nick else name name = filter_invites(name) avatar = user.avatar_url_as(static_format="png") data.set_author(name=f"{statusemoji} {name}", url=avatar) data.set_thumbnail(url=avatar) await ctx.send(embed=data)
async def process_discord_message(self, message, destination): author = message.author if not await self.bot.allowed_by_whitelist_blacklist(author): return is_owner = await self.bot.is_owner(author) if isinstance(destination, discord.Message): channel = destination.channel else: channel = getattr(destination, "dm_channel", destination) guild = getattr(channel, "guild", None) me = (guild or channel).me if not is_owner and guild: dest_author = guild.get_member(author.id) if dest_author: is_automod_immune = await self.bot.is_automod_immune(dest_author) else: is_automod_immune = False else: is_automod_immune = True author_perms = self.permissions(destination, author, is_owner) bot_perms = self.permissions(destination, me) both_perms = discord.Permissions(author_perms.value & bot_perms.value) content = message.content if not is_automod_immune: filt: "Filter" = self.bot.get_cog("Filter") if filt and await filt.filter_hits(content, destination): raise RiftError("Your message was filtered at the destination.") attachments = message.attachments embed = None if attachments and author_perms.attach_files: if bot_perms.embed_links: embed = await self.get_embed(destination, attachments) else: if content: content = f"{content}\n\n{_('Attachments:')}\n" else: content = _("Attachments:") content += "\n".join(f"({self.xbytes(a.size)}) {a.url}" for a in attachments) if not content and not embed: raise RiftError(_("No content to send.")) allowed_mentions = discord.AllowedMentions() if not is_owner: top_role = getattr(author, "top_role", None) if top_role and top_role != top_role.guild.default_role: content = f"[{author.top_role}] {author}\n>>> {content}" else: content = f"{author}\n>>> {content}" if not both_perms.administrator: content = common_filters.filter_invites(content) if not both_perms.mention_everyone: allowed_mentions = discord.AllowedMentions(users=True, everyone=True, roles=True) else: allowed_mentions = discord.AllowedMentions(users=True) try: if isinstance(destination, discord.Message): coro = destination.edit else: coro = destination.send return await coro(content=content, embed=embed, allowed_mentions=allowed_mentions) except discord.Forbidden: if not channel.permissions_for(me).send_messages: # we can't send here anymore, may as well remove it self.rifts.remove_vertices(getattr(channel, "recipient", channel)) raise
if embed: embed = self.get_embed(attachments, embed=embed) else: if content: content = f"{content}\n\n{_('Attachments:')}\n" else: content = _("Attachments:") content += "\n".join(f"({self.xbytes(a.size)}) {a.url}" for a in attachments) if not content and not embed: raise RiftError(_("Nothing to send.")) if is_owner: allowed_mentions = discord.AllowedMentions.all() else: if content and not both_perms.administrator: content = filter_invites(content) if both_perms.mention_everyone: allowed_mentions = discord.AllowedMentions.all() else: allowed_mentions = discord.AllowedMentions(users=True) return { "allowed_mentions": allowed_mentions, "embed": embed, "content": content } async def try_or_remove(self, coro: Awaitable[T], channel: UnionChannel) -> T: guild: Optional[discord.Guild] = getattr(channel, "guild", None) if guild: me = guild.me
content = f"{content}\n\n{_('Attachments:')}\n" else: content = _("Attachments:") content += "\n".join(f"({self.xbytes(a.size)}) {a.url}" for a in attachments) if not content and not embed: raise RiftError(_("No content to send.")) allowed_mentions = discord.AllowedMentions() if not is_owner: top_role = getattr(author, "top_role", None) if top_role and top_role != top_role.guild.default_role: content = f"[{author.top_role}] {author}\n>>> {content}" else: content = f"{author}\n>>> {content}" if not both_perms.administrator: content = common_filters.filter_invites(content) if not both_perms.mention_everyone: allowed_mentions = discord.AllowedMentions(users=True, everyone=True, roles=True) else: allowed_mentions = discord.AllowedMentions(users=True) try: if isinstance(destination, discord.Message): coro = destination.edit else: coro = destination.send return await coro(content=content, embed=embed, allowed_mentions=allowed_mentions) except discord.Forbidden:
async def _user_info(self, ctx: commands.Context, member: Union[discord.Member, discord.User]): in_guild = isinstance(member, discord.Member) embed = discord.Embed( title=filter_invites(str(member)), colour=member.colour if member.colour.value != 0 else discord.Embed.Empty, description=await self.build_description(member), ).set_thumbnail( # discord.py 1.0.0 changes this to an Asset instance, which causes PyCharm to start # flipping the f**k out since it doesn't match the expected str type, despite # working perfectly fine. url=str(member.avatar_url_as(static_format="png")) ) now = datetime.utcnow() member_n = ( (sorted(member.guild.members, key=lambda m: m.joined_at or now).index(member) + 1) if in_guild else None ) embed.set_footer( text=translate( "member_footer" if member_n is not None else "user_footer", n=member_n, id=member.id ) ) if in_guild: if member.joined_at is None: joined_guild = translate("unknown_join_date") else: joined_guild = translate( "joined_server", delta=Humanize(member.joined_at - ctx.message.created_at, add_direction=True), absolute=Humanize(member.joined_at, DATETIME_FORMAT, tzinfo=get_localzone()), ) else: joined_guild = None joined_discord = translate( "joined_discord", delta=Humanize(member.created_at - ctx.message.created_at, add_direction=True), absolute=Humanize(member.created_at, DATETIME_FORMAT, tzinfo=get_localzone()), ) embed.add_field( name=translate("account_age"), value="\n".join(x for x in [joined_discord, joined_guild] if x is not None), ) if in_guild: roles = list(reversed([mention(x) for x in member.roles if not x.is_default()])) cap = 40 if len(roles) > cap: roles = [*roles[:cap], translate("more_roles", num=len(roles) - cap)] if roles: embed.add_field( name=translate("server_roles"), value=format_list(roles, locale=translate.locale.babel), inline=False, ) names, nicks = await self.get_names_and_nicks(member) if names: embed.add_field(name=translate("past_names"), value=", ".join(names), inline=False) if nicks: embed.add_field(name=translate("past_nicks"), value=", ".join(nicks), inline=False) await ctx.send(embed=embed)
async def userinfo(self, ctx, *, user: discord.Member = None): """Show userinfo with some more detail.""" mod = self.bot.get_cog("Mod") if mod is None: return await ctx.send("This requires the mod cog to be loaded.") author = ctx.author guild = ctx.guild if not user: user = author sharedguilds = { guild async for guild in AsyncIter(self.bot.guilds) if user in guild.members } roles = user.roles[-1:0:-1] names, nicks = await mod.get_names_and_nicks(user) joined_at = user.joined_at since_created = (ctx.message.created_at - user.created_at).days if joined_at is not None: since_joined = (ctx.message.created_at - joined_at).days user_joined = joined_at.strftime("%d %b %Y %H:%M") else: since_joined = "?" user_joined = "Unknown" user_created = user.created_at.strftime("%d %b %Y %H:%M") voice_state = user.voice member_number = (sorted( guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1) created_on = "{}\n({} days ago)".format(user_created, since_created) joined_on = "{}\n({} days ago)".format(user_joined, since_joined) if any(a.type is discord.ActivityType.streaming for a in user.activities): statusemoji = "\N{LARGE PURPLE CIRCLE}" elif user.status.name == "online": statusemoji = "\N{LARGE GREEN CIRCLE}" elif user.status.name == "offline": statusemoji = "\N{MEDIUM WHITE CIRCLE}" elif user.status.name == "dnd": statusemoji = "\N{LARGE RED CIRCLE}" elif user.status.name == "idle": statusemoji = "\N{LARGE ORANGE CIRCLE}" activity = "Chilling in {} status".format(user.status) status_string = mod.get_status_string(user) if roles: role_str = ", ".join([x.mention for x in roles]) # 400 BAD REQUEST (error code: 50035): Invalid Form Body # In embed.fields.2.value: Must be 1024 or fewer in length. if len(role_str) > 1024: # Alternative string building time. # This is not the most optimal, but if you're hitting this, you are losing more time # to every single check running on users than the occasional user info invoke # We don't start by building this way, since the number of times we hit this should be # infintesimally small compared to when we don't across all uses of Red. continuation_string = ( "and {numeric_number} more roles not displayed due to embed limits." ) available_length = 1024 - len( continuation_string) # do not attempt to tweak, i18n role_chunks = [] remaining_roles = 0 for r in roles: chunk = f"{r.mention}, " chunk_size = len(chunk) if chunk_size < available_length: available_length -= chunk_size role_chunks.append(chunk) else: remaining_roles += 1 role_chunks.append( continuation_string.format(numeric_number=remaining_roles)) role_str = "".join(role_chunks) else: role_str = None data = discord.Embed( description=(status_string or activity) + f"\n\n{len(sharedguilds)} shared servers.", colour=user.colour, ) data.add_field(name="Joined Discord on", value=created_on) data.add_field(name="Joined this server on", value=joined_on) if role_str is not None: data.add_field(name="Roles", value=role_str, inline=False) if names: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(names)) data.add_field(name="Previous Names", value=val, inline=False) if nicks: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(nicks)) data.add_field(name="Previous Nicknames", value=val, inline=False) if voice_state and voice_state.channel: data.add_field( name="Current voice channel", value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) data.set_footer( text="Member #{} | User ID: {}".format(member_number, user.id)) name = str(user) name = " ~ ".join((name, user.nick)) if user.nick else name name = filter_invites(name) avatar = user.avatar_url_as(static_format="png") data.set_author(name=f"{statusemoji} {name}", url=avatar) data.set_thumbnail(url=avatar) flags = await discord_py(user) badges = "" for badge in sorted(flags): if badge == "verified_bot": emoji1 = discord.utils.get(self.bot.emojis, id=EMOJIS["verified_bot"]) emoji2 = discord.utils.get(self.bot.emojis, id=EMOJIS["verified_bot2"]) if emoji1: emoji = f"{emoji1}{emoji2}" else: emoji = None else: emoji = discord.utils.get(self.bot.emojis, id=EMOJIS[badge]) if emoji: badges += f"{emoji} {badge.replace('_', ' ').title()}\n" else: badges += f"\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16} {badge.replace('_', ' ').title()}\n" if badges: data.add_field(name="Badges", value=badges) await ctx.send(embed=data)
async def build_user_info_embed(self): """Stolen from core red cogs and adapted where needed..""" user = self.member message = self.message fortnite_guild_id = await self.config.fortnite_guild_id() fortnite_guild = self.bot.get_guild(fortnite_guild_id) user = fortnite_guild.get_member(user.id) roles = user.roles[-1:0:-1] activity = "Chilling in {} status".format(user.status) if user.activity is None: # Default status pass elif user.activity.type == discord.ActivityType.playing: activity = "Playing {}".format(user.activity.name) elif user.activity.type == discord.ActivityType.streaming: activity = "Streaming [{}]({})".format(user.activity.name, user.activity.url) elif user.activity.type == discord.ActivityType.listening: activity = "Listening to {}".format(user.activity.name) elif user.activity.type == discord.ActivityType.watching: activity = "Watching {}".format(user.activity.name) if roles: roles = ", ".join([x.name for x in roles]) else: roles = None joined_at = user.joined_at since_created = (message.created_at - user.created_at).days if joined_at is not None: since_joined = (message.created_at - joined_at).days user_joined = joined_at.strftime("%d %b %Y %H:%M") else: since_joined = "?" user_joined = "Unknown" user_created = user.created_at.strftime("%d %b %Y %H:%M") voice_state = user.voice member_number = (sorted( fortnite_guild.members, key=lambda m: m.joined_at or message.created_at).index(user) + 1) created_on = "{}\n({} days ago)".format(user_created, since_created) joined_on = "{}\n({} days ago)".format(user_joined, since_joined) embed = discord.Embed(color=discord.Color.green(), description=activity) embed.add_field(name="Joined Discord on", value=created_on) embed.add_field(name="Joined Official Fortnite server on", value=joined_on) if roles is not None: embed.add_field(name="Roles", value=roles, inline=False) if voice_state and voice_state.channel: embed.add_field( name="Current voice channel", value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) embed.set_footer( text=("Member #{} | User ID: {}").format(member_number, user.id)) name = str(user) name = " ~ ".join((name, user.nick)) if user.nick else name name = filter_invites(name) if user.avatar: avatar = user.avatar_url_as(static_format="png") embed.set_author(name=name, url=avatar) embed.set_thumbnail(url=avatar) else: embed.set_author(name=name) return embed
async def userinfo(self, ctx, *, user: discord.Member = None): """Show information about a user. This includes fields for status, discord join date, server join date, voice state and previous names/nicknames. If the user has no roles, previous names or previous nicknames, these fields will be omitted. """ author = ctx.author guild = ctx.guild if not user: user = author roles = user.roles[-1:0:-1] names, nicks = await self.get_names_and_nicks(user) joined_at = user.joined_at since_created = (ctx.message.created_at - user.created_at).days if joined_at is not None: since_joined = (ctx.message.created_at - joined_at).days user_joined = joined_at.strftime("%d %b %Y %H:%M") else: since_joined = "?" user_joined = _("Unknown") user_created = user.created_at.strftime("%d %b %Y %H:%M") voice_state = user.voice member_number = (sorted( guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1) created_on = _("{}\n({} days ago)").format(user_created, since_created) joined_on = _("{}\n({} days ago)").format(user_joined, since_joined) activity = _("Chilling in {} status").format(user.status) if user.activity is None: # Default status pass elif user.activity.type == discord.ActivityType.playing: activity = _("Playing {}").format(user.activity.name) elif user.activity.type == discord.ActivityType.streaming: activity = _("Streaming [{}]({})").format(user.activity.name, user.activity.url) elif user.activity.type == discord.ActivityType.listening: activity = _("Listening to {}").format(user.activity.name) elif user.activity.type == discord.ActivityType.watching: activity = _("Watching {}").format(user.activity.name) if roles: role_str = ", ".join([x.mention for x in roles]) # 400 BAD REQUEST (error code: 50035): Invalid Form Body # In embed.fields.2.value: Must be 1024 or fewer in length. if len(role_str) > 1024: # Alternative string building time. # This is not the most optimal, but if you're hitting this, you are losing more time # to every single check running on users than the occasional user info invoke # We don't start by building this way, since the number of times we hit this should be # infintesimally small compared to when we don't across all uses of Red. continuation_string = _( "and {numeric_number} more roles not displayed due to embed limits." ) available_length = 1024 - len( continuation_string) # do not attempt to tweak, i18n role_chunks = [] remaining_roles = 0 for r in roles: chunk = f"{r.mention}, " chunk_size = len(chunk) if chunk_size < available_length: available_length -= chunk_size role_chunks.append(chunk) else: remaining_roles += 1 role_chunks.append( continuation_string.format(numeric_number=remaining_roles)) role_str = "".join(role_chunks) else: role_str = None data = discord.Embed(description=activity, colour=user.colour) data.add_field(name=_("Joined Discord on"), value=created_on) data.add_field(name=_("Joined this server on"), value=joined_on) if role_str is not None: data.add_field(name=_("Roles"), value=role_str, inline=False) if names: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(names)) data.add_field(name=_("Previous Names"), value=val, inline=False) if nicks: # May need sanitizing later, but mentions do not ping in embeds currently val = filter_invites(", ".join(nicks)) data.add_field(name=_("Previous Nicknames"), value=val, inline=False) if voice_state and voice_state.channel: data.add_field( name=_("Current voice channel"), value="{0.mention} ID: {0.id}".format(voice_state.channel), inline=False, ) data.set_footer( text=_("Member #{} | User ID: {}").format(member_number, user.id)) name = str(user) name = " ~ ".join((name, user.nick)) if user.nick else name name = filter_invites(name) if user.avatar: avatar = user.avatar_url_as(static_format="png") data.set_author(name=name, url=avatar) data.set_thumbnail(url=avatar) else: data.set_author(name=name) await ctx.send(embed=data)