diff --git a/docs/cog_guides/mod.rst b/docs/cog_guides/mod.rst index 9c37d3644..c8dc7da91 100644 --- a/docs/cog_guides/mod.rst +++ b/docs/cog_guides/mod.rst @@ -215,11 +215,11 @@ modset deletenames **Description** -Delete all stored usernames and nicknames. +Delete all stored usernames, global display names, and server nicknames. **Arguments** -- ````: Whether to delete all stored usernames and nicknames. |bool-input| +- ````: Whether to delete all stored usernames, global display names, and server nicknames. |bool-input| .. _mod-command-modset-deleterepeats: @@ -469,7 +469,7 @@ modset tracknicknames **Description** -Toggle whether nickname changes should be tracked. +Toggle whether server nickname changes should be tracked. This setting will be overridden if trackallnames is disabled. @@ -527,7 +527,7 @@ names **Description** -Show previous names and nicknames of a member. +Show previous usernames, global display names, and server nicknames of a member. **Arguments** @@ -549,14 +549,14 @@ rename **Description** -Change a member's nickname. +Change a member's server nickname. -Leaving the nickname empty will remove it. +Leaving the nickname argument empty will remove it. **Arguments** * ````: |member-input| -* ``[nickname]``: The new nickname for the member. +* ``[nickname]``: The new server nickname for the member. .. _mod-command-slowmode: @@ -684,9 +684,9 @@ userinfo Show information about a user. This includes fields for status, discord join date, server -join date, voice state and previous names/nicknames. +join date, voice state and previous usernames/global display names/nicknames. -If the user has no roles, previous names or previous nicknames, +If the user has no roles, previous usernames, global display names, or server nicknames, these fields will be omitted. **Arguments** diff --git a/redbot/cogs/admin/admin.py b/redbot/cogs/admin/admin.py index 73e2e5f8d..22439bb8c 100644 --- a/redbot/cogs/admin/admin.py +++ b/redbot/cogs/admin/admin.py @@ -272,7 +272,9 @@ class Admin(commands.Cog): `[p]editrole colour Test #ff9900` """ author = ctx.author - reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name) + reason = _("{author} ({author.id}) changed the colour of role '{role.name}'").format( + author=author, role=role.name + ) if not self.pass_user_hierarchy_check(ctx, role): await ctx.send(_(ROLE_USER_HIERARCHY_ISSUE).format(role=role)) @@ -303,9 +305,9 @@ class Admin(commands.Cog): """ author = ctx.message.author old_name = role.name - reason = "{}({}) changed the name of role '{}' to '{}'".format( - author.name, author.id, old_name, name - ) + reason = _( + "{author} ({author.id}) changed the name of role '{old_name}' to '{name}'" + ).format(author=author, old_name=old_name, name=name) if not self.pass_user_hierarchy_check(ctx, role): await ctx.send(_(ROLE_USER_HIERARCHY_ISSUE).format(role=role)) diff --git a/redbot/cogs/audio/converters.py b/redbot/cogs/audio/converters.py index bb6180752..bb83e9360 100644 --- a/redbot/cogs/audio/converters.py +++ b/redbot/cogs/audio/converters.py @@ -113,7 +113,9 @@ async def global_unique_user_finder( return user maybe_matches = [] - async for user in AsyncIter(bot.users).filter(lambda u: u.name == arg or f"{u}" == arg): + async for user in AsyncIter(bot.users).filter( + lambda u: u.name == arg or u.global_name == arg or f"{u}" == arg + ): maybe_matches.append(user) if guild is not None: diff --git a/redbot/cogs/audio/core/commands/miscellaneous.py b/redbot/cogs/audio/core/commands/miscellaneous.py index b6c123c64..1d7214240 100644 --- a/redbot/cogs/audio/core/commands/miscellaneous.py +++ b/redbot/cogs/audio/core/commands/miscellaneous.py @@ -105,32 +105,30 @@ class MiscellaneousCommands(MixinMeta, metaclass=CompositeMetaClass): queue_tracks = player.queue requesters = {"total": 0, "users": {}} - async def _usercount(req_username): - if req_username in requesters["users"]: - requesters["users"][req_username]["songcount"] += 1 + async def _usercount(req_user_handle): + if req_user_handle in requesters["users"]: + requesters["users"][req_user_handle]["songcount"] += 1 requesters["total"] += 1 else: - requesters["users"][req_username] = {} - requesters["users"][req_username]["songcount"] = 1 + requesters["users"][req_user_handle] = {} + requesters["users"][req_user_handle]["songcount"] = 1 requesters["total"] += 1 async for track in AsyncIter(queue_tracks): - req_username = "{}#{}".format(track.requester.name, track.requester.discriminator) - await _usercount(req_username) + req_user_handle = str(track.requester) + await _usercount(req_user_handle) try: - req_username = "{}#{}".format( - player.current.requester.name, player.current.requester.discriminator - ) - await _usercount(req_username) + req_user_handle = str(player.current.requester) + await _usercount(req_user_handle) except AttributeError: return await self.send_embed_msg(ctx, title=_("There's nothing in the queue.")) - async for req_username in AsyncIter(requesters["users"]): - percentage = float(requesters["users"][req_username]["songcount"]) / float( + async for req_user_handle in AsyncIter(requesters["users"]): + percentage = float(requesters["users"][req_user_handle]["songcount"]) / float( requesters["total"] ) - requesters["users"][req_username]["percent"] = round(percentage * 100, 1) + requesters["users"][req_user_handle]["percent"] = round(percentage * 100, 1) top_queue_users = heapq.nlargest( 20, diff --git a/redbot/cogs/audio/core/events/dpy.py b/redbot/cogs/audio/core/events/dpy.py index 6c8934d90..e03eb7216 100644 --- a/redbot/cogs/audio/core/events/dpy.py +++ b/redbot/cogs/audio/core/events/dpy.py @@ -61,7 +61,7 @@ HUMANIZED_PERM = { "manage_nicknames": _("Manage Nicknames"), "manage_roles": _("Manage Roles"), "manage_webhooks": _("Manage Webhooks"), - "manage_emojis": _("Manage Emojis"), + "manage_expressions": _("Manage Expressions"), "use_application_commands": _("Use Application Commands"), "request_to_speak": _("Request to Speak"), "manage_events": _("Manage Events"), @@ -72,6 +72,10 @@ HUMANIZED_PERM = { "send_messages_in_threads": _("Send Messages in Threads"), "start_embedded_activities": _("Start Activities"), "moderate_members": _("Moderate Member"), + "use_soundboard": _("Use Soundboard"), + "create_expressions": _("Create Expressions"), + "use_external_sounds": _("Use External Sounds"), + "send_voice_messages": _("Send Voice Messages"), } DANGEROUS_COMMANDS = { diff --git a/redbot/cogs/cleanup/cleanup.py b/redbot/cogs/cleanup/cleanup.py index 196c074a8..5d148d48d 100644 --- a/redbot/cogs/cleanup/cleanup.py +++ b/redbot/cogs/cleanup/cleanup.py @@ -228,8 +228,8 @@ class Cleanup(commands.Cog): ) to_delete.append(ctx.message) - reason = "{}({}) deleted {} messages containing '{}' in channel #{}.".format( - author.name, + reason = "{} ({}) deleted {} messages containing '{}' in channel #{}.".format( + author, author.id, humanize_number(len(to_delete), override_locale="en_us"), text, @@ -295,10 +295,10 @@ class Cleanup(commands.Cog): to_delete.append(ctx.message) reason = ( - "{}({}) deleted {} messages" - " made by {}({}) in channel #{}." + "{} ({}) deleted {} messages" + " made by {} ({}) in channel #{}." "".format( - author.name, + author, author.id, humanize_number(len(to_delete), override_locale="en_US"), member or "???", @@ -353,8 +353,8 @@ class Cleanup(commands.Cog): channel=channel, number=None, after=after, delete_pinned=delete_pinned ) - reason = "{}({}) deleted {} messages in channel #{}.".format( - author.name, + reason = "{} ({}) deleted {} messages in channel #{}.".format( + author, author.id, humanize_number(len(to_delete), override_locale="en_US"), channel.name, @@ -409,8 +409,8 @@ class Cleanup(commands.Cog): ) to_delete.append(ctx.message) - reason = "{}({}) deleted {} messages in channel #{}.".format( - author.name, + reason = "{} ({}) deleted {} messages in channel #{}.".format( + author, author.id, humanize_number(len(to_delete), override_locale="en_US"), channel.name, @@ -462,8 +462,8 @@ class Cleanup(commands.Cog): channel=channel, before=mtwo, after=mone, delete_pinned=delete_pinned ) to_delete.append(ctx.message) - reason = "{}({}) deleted {} messages in channel #{}.".format( - author.name, + reason = "{} ({}) deleted {} messages in channel #{}.".format( + author, author.id, humanize_number(len(to_delete), override_locale="en_US"), channel.name, @@ -504,8 +504,8 @@ class Cleanup(commands.Cog): ) to_delete.append(ctx.message) - reason = "{}({}) deleted {} messages in channel #{}.".format( - author.name, author.id, len(to_delete), channel.name + reason = "{} ({}) deleted {} messages in channel #{}.".format( + author, author.id, len(to_delete), channel.name ) log.info(reason) @@ -585,10 +585,10 @@ class Cleanup(commands.Cog): to_delete.append(ctx.message) reason = ( - "{}({}) deleted {}" + "{} ({}) deleted {}" " command messages in channel #{}." "".format( - author.name, + author, author.id, humanize_number(len(to_delete), override_locale="en_US"), channel.name, @@ -670,15 +670,11 @@ class Cleanup(commands.Cog): else: channel_name = str(channel) - reason = ( - "{}({}) deleted {} messages " - "sent by the bot in {}." - "".format( - author.name, - author.id, - humanize_number(len(to_delete), override_locale="en_US"), - channel_name, - ) + reason = "{} ({}) deleted {} messages sent by the bot in {}.".format( + author, + author.id, + humanize_number(len(to_delete), override_locale="en_US"), + channel_name, ) log.info(reason) diff --git a/redbot/cogs/economy/economy.py b/redbot/cogs/economy/economy.py index 11bc1bb05..29732a930 100644 --- a/redbot/cogs/economy/economy.py +++ b/redbot/cogs/economy/economy.py @@ -434,7 +434,9 @@ class Economy(commands.Cog): if show_global and await bank.is_global(): # show_global is only applicable if bank is global bank_sorted = await bank.get_leaderboard(positions=top, guild=None) - base_embed.set_author(name=ctx.bot.user.name, icon_url=ctx.bot.user.display_avatar) + base_embed.set_author( + name=ctx.bot.user.display_name, icon_url=ctx.bot.user.display_avatar + ) else: bank_sorted = await bank.get_leaderboard(positions=top, guild=guild) if guild: diff --git a/redbot/cogs/general/general.py b/redbot/cogs/general/general.py index c981f7e6d..797640e12 100644 --- a/redbot/cogs/general/general.py +++ b/redbot/cogs/general/general.py @@ -355,7 +355,7 @@ class General(commands.Cog): joined_on = _( "{bot_name} joined this server on {bot_join}. That's over {since_join} days ago!" ).format( - bot_name=ctx.bot.user.name, + bot_name=ctx.bot.user.display_name, bot_join=guild.me.joined_at.strftime("%d %b %Y %H:%M:%S"), since_join=humanize_number((ctx.message.created_at - guild.me.joined_at).days), ) diff --git a/redbot/cogs/mod/events.py b/redbot/cogs/mod/events.py index 4d53c1218..2d7a3d64c 100644 --- a/redbot/cogs/mod/events.py +++ b/redbot/cogs/mod/events.py @@ -1,6 +1,7 @@ import logging from datetime import timezone from collections import defaultdict, deque +from typing import List, Optional import discord from redbot.core import i18n, modlog, commands @@ -158,6 +159,17 @@ class Events(MixinMeta): if not deleted: await self.check_mention_spam(message) + @staticmethod + def _update_past_names(name: str, name_list: List[Optional[str]]) -> None: + while None in name_list: # clean out null entries from a bug + name_list.remove(None) + if name in name_list: + # Ensure order is maintained without duplicates occurring + name_list.remove(name) + name_list.append(name) + while len(name_list) > 20: + name_list.pop(0) + @commands.Cog.listener() async def on_user_update(self, before: discord.User, after: discord.User): if before.name != after.name: @@ -165,14 +177,13 @@ class Events(MixinMeta): if not track_all_names: return async with self.config.user(before).past_names() as name_list: - while None in name_list: # clean out null entries from a bug - name_list.remove(None) - if before.name in name_list: - # Ensure order is maintained without duplicates occurring - name_list.remove(before.name) - name_list.append(before.name) - while len(name_list) > 20: - name_list.pop(0) + self._update_past_names(before.name, name_list) + if before.display_name != after.display_name: + track_all_names = await self.config.track_all_names() + if not track_all_names: + return + async with self.config.user(before).past_display_names() as name_list: + self._update_past_names(before.display_name, name_list) @commands.Cog.listener() async def on_member_update(self, before: discord.Member, after: discord.Member): @@ -185,10 +196,4 @@ class Events(MixinMeta): if (not track_all_names) or (not track_nicknames): return async with self.config.member(before).past_nicks() as nick_list: - while None in nick_list: # clean out null entries from a bug - nick_list.remove(None) - if before.nick in nick_list: - nick_list.remove(before.nick) - nick_list.append(before.nick) - while len(nick_list) > 20: - nick_list.pop(0) + self._update_past_names(before.nick, nick_list) diff --git a/redbot/cogs/mod/kickban.py b/redbot/cogs/mod/kickban.py index 600461e91..a6ab1edd5 100644 --- a/redbot/cogs/mod/kickban.py +++ b/redbot/cogs/mod/kickban.py @@ -177,21 +177,23 @@ class KickBanMixin(MixinMeta): if removed_temp: log.info( - "{}({}) upgraded the tempban for {} to a permaban.".format( - author.name, author.id, user.id - ) + "%s (%s) upgraded the tempban for %s to a permaban.", author, author.id, user.id ) success_message = _( "User with ID {user_id} was upgraded from a temporary to a permanent ban." ).format(user_id=user.id) else: - username = user.name if hasattr(user, "name") else "Unknown" + user_handle = str(user) if isinstance(user, discord.abc.User) else "Unknown" try: await guild.ban(user, reason=audit_reason, delete_message_seconds=days * 86400) log.info( - "{}({}) {}ned {}({}), deleting {} days worth of messages.".format( - author.name, author.id, ban_type, username, user.id, str(days) - ) + "%s (%s) %sned %s (%s), deleting %s days worth of messages.", + author, + author.id, + ban_type, + user_handle, + user.id, + days, ) success_message = _("Done. That felt good.") except discord.Forbidden: @@ -200,9 +202,12 @@ class KickBanMixin(MixinMeta): return False, _("User with ID {user_id} not found").format(user_id=user.id) except Exception: log.exception( - "{}({}) attempted to {} {}({}), but an error occurred.".format( - author.name, author.id, ban_type, username, user.id - ) + "%s (%s) attempted to %s %s (%s), but an error occurred.", + author, + author.id, + ban_type, + user_handle, + user.id, ) return False, _("An unexpected error occurred.") @@ -333,14 +338,16 @@ class KickBanMixin(MixinMeta): await member.send(embed=em) try: await guild.kick(member, reason=audit_reason) - log.info("{}({}) kicked {}({})".format(author.name, author.id, member.name, member.id)) + log.info("%s (%s) kicked %s (%s)", author, author.id, member, member.id) except discord.errors.Forbidden: await ctx.send(_("I'm not allowed to do that.")) except Exception: log.exception( - "{}({}) attempted to kick {}({}), but an error occurred.".format( - author.name, author.id, member.name, member.id - ) + "%s (%s) attempted to kick %s (%s), but an error occurred.", + author, + author.id, + member, + member.id, ) else: await modlog.create_case( @@ -531,9 +538,10 @@ class KickBanMixin(MixinMeta): tempbans.remove(user_id) upgrades.append(str(user_id)) log.info( - "{}({}) upgraded the tempban for {} to a permaban.".format( - author.name, author.id, user_id - ) + "%s (%s) upgraded the tempban for %s to a permaban.", + author, + author.id, + user_id, ) banned.append(user_id) else: @@ -541,7 +549,7 @@ class KickBanMixin(MixinMeta): await guild.ban( user, reason=audit_reason, delete_message_seconds=days * 86400 ) - log.info("{}({}) hackbanned {}".format(author.name, author.id, user_id)) + log.info("%s (%s) hackbanned %s", author, author.id, user_id) except discord.NotFound: errors[user_id] = _("User with ID {user_id} not found").format( user_id=user_id @@ -716,24 +724,32 @@ class KickBanMixin(MixinMeta): return except discord.HTTPException: log.exception( - "{}({}) attempted to softban {}({}), but an error occurred trying to ban them.".format( - author.name, author.id, member.name, member.id - ) + "%s (%s) attempted to softban %s (%s), but an error occurred trying to ban them.", + author, + author.id, + member, + member.id, ) return try: await guild.unban(member) except discord.HTTPException: log.exception( - "{}({}) attempted to softban {}({}), but an error occurred trying to unban them.".format( - author.name, author.id, member.name, member.id - ) + "%s (%s) attempted to softban %s (%s)," + " but an error occurred trying to unban them.", + author, + author.id, + member, + member.id, ) return else: log.info( - "{}({}) softbanned {}({}), deleting 1 day worth " - "of messages.".format(author.name, author.id, member.name, member.id) + "%s (%s) softbanned %s (%s), deleting 1 day worth of messages.", + author, + author.id, + member, + member.id, ) await modlog.create_case( self.bot, diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 103c00b7b..3915ce0ce 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -66,7 +66,7 @@ class Mod( default_member_settings = {"past_nicks": [], "perms_cache": {}, "banned_until": False} - default_user_settings = {"past_names": []} + default_user_settings = {"past_names": [], "past_display_names": []} def __init__(self, bot: Red): super().__init__() diff --git a/redbot/cogs/mod/names.py b/redbot/cogs/mod/names.py index 307fe5d7d..b98ae5b97 100644 --- a/redbot/cogs/mod/names.py +++ b/redbot/cogs/mod/names.py @@ -1,8 +1,9 @@ import datetime -from typing import cast +from typing import List, Tuple, cast import discord from redbot.core import commands, i18n +from redbot.core.utils.chat_formatting import bold, pagify from redbot.core.utils.common_filters import ( filter_invites, filter_various_mentions, @@ -20,23 +21,23 @@ 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 + async def get_names(self, member: discord.Member) -> Tuple[List[str], List[str], List[str]]: + user_data = await self.config.user(member).all() + usernames, display_names = user_data["past_names"], user_data["past_display_names"] + nicks = await self.config.member(member).past_nicks() + usernames = list(map(escape_spoilers_and_mass_mentions, filter(None, usernames))) + display_names = list(map(escape_spoilers_and_mass_mentions, filter(None, display_names))) + nicks = list(map(escape_spoilers_and_mass_mentions, filter(None, nicks))) + return usernames, display_names, nicks @commands.command() @commands.guild_only() @commands.bot_has_permissions(manage_nicknames=True) @commands.admin_or_permissions(manage_nicknames=True) async def rename(self, ctx: commands.Context, member: discord.Member, *, nickname: str = ""): - """Change a member's nickname. + """Change a member's server nickname. - Leaving the nickname empty will remove it. + Leaving the nickname argument empty will remove it. """ nickname = nickname.strip() me = cast(discord.Member, ctx.me) @@ -175,9 +176,9 @@ class ModInfo(MixinMeta): """Show information about a member. This includes fields for status, discord join date, server - join date, voice state and previous names/nicknames. + join date, voice state and previous usernames/global display names/nicknames. - If the member has no roles, previous names or previous nicknames, + If the member has no roles, previous usernames, global display names, or server nicknames, these fields will be omitted. """ author = ctx.author @@ -191,7 +192,7 @@ class ModInfo(MixinMeta): is_special = member.id == 96130341705637888 and guild.id == 133049272517001216 roles = member.roles[-1:0:-1] - names, nicks = await self.get_names_and_nicks(member) + usernames, display_names, nicks = await self.get_names(member) if is_special: joined_at = special_date @@ -273,22 +274,17 @@ class ModInfo(MixinMeta): 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, - ) + for single_form, plural_form, names in ( + (_("Previous Username"), _("Previous Usernames"), usernames), + (_("Previous Global Display Name"), _("Previous Global Display Names"), display_names), + (_("Previous Server Nickname"), _("Previous Server Nicknames"), nicks), + ): + if names: + data.add_field( + name=plural_form if len(names) > 1 else single_form, + value=filter_invites(", ".join(names)), + inline=False, + ) if voice_state and voice_state.channel: data.add_field( name=_("Current voice channel"), @@ -309,21 +305,20 @@ class ModInfo(MixinMeta): @commands.command() async def names(self, ctx: commands.Context, *, member: discord.Member): - """Show previous names and nicknames of a member.""" - names, nicks = await self.get_names_and_nicks(member) - msg = "" - if names: - msg += _("**Past 20 names**:") - msg += "\n" - msg += ", ".join(names) - if nicks: - if msg: - msg += "\n\n" - msg += _("**Past 20 nicknames**:") - msg += "\n" - msg += ", ".join(nicks) - if msg: - msg = filter_various_mentions(msg) - await ctx.send(msg) + """Show previous usernames, global display names, and server nicknames of a member.""" + usernames, display_names, nicks = await self.get_names(member) + parts = [] + for header, names in ( + (_("Past 20 usernames:"), usernames), + (_("Past 20 global display names:"), display_names), + (_("Past 20 server nicknames:"), nicks), + ): + if names: + parts.append(bold(header) + ", ".join(names)) + if parts: + # each name can have 32 characters, we store 3*20 names which totals to + # 60*32=1920 characters which is quite close to the message length limit + for msg in pagify(filter_various_mentions("\n\n".join(parts))): + await ctx.send(msg) else: await ctx.send(_("That member doesn't have any recorded name or nickname change.")) diff --git a/redbot/cogs/mod/settings.py b/redbot/cogs/mod/settings.py index 7ddf7976f..298ed10a5 100644 --- a/redbot/cogs/mod/settings.py +++ b/redbot/cogs/mod/settings.py @@ -418,7 +418,7 @@ class ModSettings(MixinMeta): @commands.guild_only() async def tracknicknames(self, ctx: commands.Context, enabled: bool = None): """ - Toggle whether nickname changes should be tracked. + Toggle whether server nickname changes should be tracked. This setting will be overridden if trackallnames is disabled. """ @@ -470,11 +470,11 @@ class ModSettings(MixinMeta): @commands.max_concurrency(1, commands.BucketType.default) @commands.is_owner() async def deletenames(self, ctx: commands.Context, confirmation: bool = False) -> None: - """Delete all stored usernames and nicknames. + """Delete all stored usernames, global display names, and server nicknames. Examples: - `[p]modset deletenames` - Did not confirm. Shows the help message. - - `[p]modset deletenames yes` - Deletes all stored usernames and nicknames. + - `[p]modset deletenames yes` - Deletes all stored usernames, global display names, and server nicknames. **Arguments** @@ -483,8 +483,8 @@ class ModSettings(MixinMeta): if not confirmation: await ctx.send( _( - "This will delete all stored usernames and nicknames the bot has stored." - "\nIf you're sure, type {command}" + "This will delete all stored usernames, global display names," + " and server nicknames the bot has stored.\nIf you're sure, type {command}" ).format(command=inline(f"{ctx.clean_prefix}modset deletenames yes")) ) return @@ -511,16 +511,23 @@ class ModSettings(MixinMeta): async for guild_id in AsyncIter(guilds_to_remove, steps=100): del mod_member_data[guild_id] - # Username data + # Username and global display name data async with self.config._get_base_group(self.config.USER).all() as mod_user_data: users_to_remove = [] async for user_id, user_data in AsyncIter(mod_user_data.items(), steps=100): if "past_names" in user_data: del user_data["past_names"] + if "past_display_names" in user_data: + del user_data["past_display_names"] if not user_data: users_to_remove.append(user_id) async for user_id in AsyncIter(users_to_remove, steps=100): del mod_user_data[user_id] - await ctx.send(_("Usernames and nicknames have been deleted from Mod config.")) + await ctx.send( + _( + "Usernames, global display names, and server nicknames" + " have been deleted from Mod config." + ) + ) diff --git a/redbot/cogs/trivia/session.py b/redbot/cogs/trivia/session.py index 09d491344..824d75e5f 100644 --- a/redbot/cogs/trivia/session.py +++ b/redbot/cogs/trivia/session.py @@ -323,7 +323,7 @@ class TriviaSession: if payout <= 0: return for winner in winners: - LOG.debug("Paying trivia winner: %d credits --> %s", payout, winner.name) + LOG.debug("Paying trivia winner: %d credits --> %s", payout, winner) try: await bank.deposit_credits(winner, payout) except errors.BalanceTooHigh as e: diff --git a/redbot/core/_events.py b/redbot/core/_events.py index 53e8a4ff2..7381d0b00 100644 --- a/redbot/core/_events.py +++ b/redbot/core/_events.py @@ -183,13 +183,13 @@ def init_events(bot, cli_flags): if guilds: rich_console.print( Columns( - [Panel(table_general_info, title=str(bot.user.name)), Panel(table_counts)], + [Panel(table_general_info, title=bot.user.display_name), Panel(table_counts)], equal=True, align="center", ) ) else: - rich_console.print(Columns([Panel(table_general_info, title=str(bot.user.name))])) + rich_console.print(Columns([Panel(table_general_info, title=bot.user.display_name)])) rich_console.print( "Loaded {} cogs with {} commands".format(len(bot.cogs), len(bot.commands)) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index e12000c55..bb77723b3 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -1794,6 +1794,8 @@ class Red( Checks if the user, message, context, or role should be considered immune from automated moderation actions. + Bot users are considered immune. + This will return ``False`` in direct messages. Parameters @@ -1812,22 +1814,22 @@ class Red( return False if isinstance(to_check, discord.Role): - ids_to_check = [to_check.id] + ids_to_check = {to_check.id} else: author = getattr(to_check, "author", to_check) + if author.bot: + return True + ids_to_check = set() try: - ids_to_check = [r.id for r in author.roles] + ids_to_check = {r.id for r in author.roles} except AttributeError: - # webhook messages are a user not member, - # cheaper than isinstance - if author.bot and author.discriminator == "0000": - return True # webhooks require significant permissions to enable. - else: - ids_to_check.append(author.id) + # cheaper than isinstance(author, discord.User) + pass + ids_to_check.add(author.id) immune_ids = await self._config.guild(guild).autoimmune_ids() - return any(i in immune_ids for i in ids_to_check) + return not ids_to_check.isdisjoint(immune_ids) @staticmethod async def send_filtered( diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 6b610cfbc..76aca94c0 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -2568,7 +2568,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): "This will delete all bank accounts for {scope}.\nIf you're sure, type " "`{prefix}bankset reset yes`" ).format( - scope=self.bot.user.name if await bank.is_global() else _("this server"), + scope=self.bot.user.display_name + if await bank.is_global() + else _("this server"), prefix=ctx.clean_prefix, ) ) @@ -2576,7 +2578,9 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): await bank.wipe_bank(guild=ctx.guild) await ctx.send( _("All bank accounts for {scope} have been deleted.").format( - scope=self.bot.user.name if await bank.is_global() else _("this server") + scope=self.bot.user.display_name + if await bank.is_global() + else _("this server") ) ) @@ -3855,7 +3859,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): "Global regional format: {regional_format}\n" "Default embed colour: {colour}" ).format( - bot_name=ctx.bot.user.name, + bot_name=ctx.bot.user.display_name, prefixes=prefix_string, guild_settings=guild_settings, locale=locale, diff --git a/redbot/core/modlog.py b/redbot/core/modlog.py index 7c23a67fc..c3f37a650 100644 --- a/redbot/core/modlog.py +++ b/redbot/core/modlog.py @@ -306,8 +306,8 @@ class Case: (note: it might not exist regardless of whether this attribute is `None`) or if it has never been created. last_known_username: Optional[str] - The last known username of the user. - `None` if the username of the user was never saved + The last known user handle (``username`` / ``username#1234``) of the user. + `None` if the handle of the user was never saved or if their data had to be anonymized. """ @@ -393,9 +393,9 @@ class Case: else: setattr(self, item, value) - # update last known username + # update last known user handle if not isinstance(self.user, int): - self.last_known_username = f"{self.user.name}#{self.user.discriminator}" + self.last_known_username = str(self.user) if isinstance(self.channel, discord.Thread): self.parent_channel_id = self.channel.parent_id @@ -503,7 +503,15 @@ class Case: # can't use _() inside f-string expressions, see bpo-36310 and red#3818 translated = _("Unknown or Deleted User") user = f"[{translated}] ({self.user})" + # Handle pomelo usernames stored before we updated our implementation + elif self.last_known_username.endswith("#0"): + user = f"{self.last_known_username[:-2]} ({self.user})" + # New usernames can't contain `#` and old usernames couldn't either. + elif len(self.last_known_username) <= 5 or self.last_known_username[-5] != "#": + user = f"{self.last_known_username} ({self.user})" + # Last known user handle is a legacy username with a discriminator else: + # isolate the name so that the direction of the discriminator and ID aren't changed # See usage explanation here: https://www.unicode.org/reports/tr9/#Formatting name = self.last_known_username[:-5] discriminator = self.last_known_username[-4:] @@ -511,8 +519,10 @@ class Case: f"\N{FIRST STRONG ISOLATE}{name}" f"\N{POP DIRECTIONAL ISOLATE}#{discriminator} ({self.user})" ) + elif self.user.discriminator == "0": + user = f"{self.user} ({self.user.id})" else: - # isolate the name so that the direction of the discriminator and ID do not get changed + # isolate the name so that the direction of the discriminator and ID aren't changed # See usage explanation here: https://www.unicode.org/reports/tr9/#Formatting user = escape_spoilers( filter_invites( @@ -1019,7 +1029,7 @@ async def create_case( channel: Optional[Union[discord.abc.GuildChannel, discord.Thread]] The channel the action was taken in last_known_username: Optional[str] - The last known username of the user + The last known user handle (``username`` / ``username#1234``) of the user Note: This is ignored if a Member or User object is provided in the user field diff --git a/redbot/core/utils/tunnel.py b/redbot/core/utils/tunnel.py index e66ecd804..140b76e9c 100644 --- a/redbot/core/utils/tunnel.py +++ b/redbot/core/utils/tunnel.py @@ -162,7 +162,7 @@ class Tunnel(metaclass=TunnelMeta): """ files = [] - max_size = 8 * 1000 * 1000 + max_size = 26214400 if m.attachments and sum(a.size for a in m.attachments) <= max_size: for a in m.attachments: if images_only and a.height is None: diff --git a/requirements/base.txt b/requirements/base.txt index 7f4414657..2117e20e6 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -28,7 +28,7 @@ click==8.1.3 # via -r base.in contextlib2==21.6.0 # via schema -discord-py==2.2.3 +discord-py==2.3.0 # via # -r base.in # red-lavalink