mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-12-07 18:02:31 -05:00
d.py 2.3 / pomelo changes (#6130)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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__()
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user