mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[Mod] Enhanced [p]hackban (#2164)
This commit is contained in:
parent
bcc50557a9
commit
937d2fe0f6
@ -2,14 +2,16 @@ import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
from collections import deque, defaultdict, namedtuple
|
||||
from typing import cast
|
||||
from typing import Optional, Union, cast
|
||||
|
||||
import discord
|
||||
|
||||
from discord.ext.commands.converter import Converter, Greedy
|
||||
from discord.ext.commands.errors import BadArgument
|
||||
from redbot.core import checks, Config, modlog, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.chat_formatting import box, escape
|
||||
from redbot.core.utils.chat_formatting import box, escape, pagify
|
||||
from .checks import mod_or_voice_permissions, admin_or_voice_permissions, bot_has_voice_permissions
|
||||
from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
||||
from .log import log
|
||||
@ -19,6 +21,18 @@ from redbot.core.utils.common_filters import filter_invites, filter_various_ment
|
||||
_ = T_ = Translator("Mod", __file__)
|
||||
|
||||
|
||||
class RawUserIds(Converter):
|
||||
async def convert(self, ctx, argument):
|
||||
# This is for the hackban command, where we receive IDs that
|
||||
# are most likely not in the guild.
|
||||
# As long as it's numeric and long enough, it makes a good candidate
|
||||
# to attempt a ban on
|
||||
if argument.isnumeric() and len(argument) >= 17:
|
||||
return int(argument)
|
||||
|
||||
raise BadArgument("{} doesn't look like a valid user ID.".format(argument))
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class Mod(commands.Cog):
|
||||
"""Moderation tools."""
|
||||
@ -386,131 +400,128 @@ class Mod(commands.Cog):
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def ban(
|
||||
self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
user: discord.Member,
|
||||
days: Optional[int] = 0,
|
||||
*,
|
||||
reason: str = None,
|
||||
):
|
||||
"""Ban a user from this server.
|
||||
|
||||
Deletes `<days>` worth of messages.
|
||||
|
||||
If `<days>` is not a number, it's treated as the first word of
|
||||
the reason. Minimum 0 days, maximum 7. Defaults to 0.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
if author == user:
|
||||
await ctx.send(
|
||||
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
If days is not a number, it's treated as the first word of the reason.
|
||||
Minimum 0 days, maximum 7. Defaults to 0."""
|
||||
result = await self.ban_user(
|
||||
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
await ctx.send(
|
||||
_(
|
||||
"I cannot let you do that. You are "
|
||||
"not higher than the user in the role "
|
||||
"hierarchy."
|
||||
)
|
||||
)
|
||||
return
|
||||
elif ctx.guild.me.top_role <= user.top_role or user == ctx.guild.owner:
|
||||
await ctx.send(_("I cannot do that due to discord hierarchy rules"))
|
||||
return
|
||||
|
||||
if days:
|
||||
if days.isdigit():
|
||||
days = int(days)
|
||||
else:
|
||||
if reason:
|
||||
reason = "{} {}".format(days, reason)
|
||||
else:
|
||||
reason = days
|
||||
days = 0
|
||||
else:
|
||||
days = 0
|
||||
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
|
||||
if days < 0 or days > 7:
|
||||
await ctx.send(_("Invalid days. Must be between 0 and 7."))
|
||||
return
|
||||
queue_entry = (guild.id, user.id)
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason, delete_message_days=days)
|
||||
log.info(
|
||||
"{}({}) banned {}({}), deleting {} days worth of messages".format(
|
||||
author.name, author.id, user.name, user.id, str(days)
|
||||
)
|
||||
)
|
||||
except discord.Forbidden:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("I'm not allowed to do that."))
|
||||
except Exception as e:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
print(e)
|
||||
else:
|
||||
if result is True:
|
||||
await ctx.send(_("Done. It was about time."))
|
||||
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"ban",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
elif isinstance(result, str):
|
||||
await ctx.send(result)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
||||
"""Pre-emptively ban a user from this server.
|
||||
async def hackban(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
user_ids: Greedy[RawUserIds],
|
||||
days: Optional[int] = 0,
|
||||
*,
|
||||
reason: str = None,
|
||||
):
|
||||
"""Preemptively bans user(s) from the server
|
||||
|
||||
User IDs need to be provided in order to ban
|
||||
using this command"""
|
||||
|
||||
banned = []
|
||||
errors = {}
|
||||
|
||||
async def show_results():
|
||||
text = _("Banned {} users from the server.".format(len(banned)))
|
||||
if errors:
|
||||
text += _("\nErrors:\n")
|
||||
text += "\n".join(errors.values())
|
||||
|
||||
for p in pagify(text):
|
||||
await ctx.send(p)
|
||||
|
||||
def remove_processed(ids):
|
||||
return [_id for _id in ids if _id not in banned and _id not in errors]
|
||||
|
||||
user_ids = list(set(user_ids)) # No dupes
|
||||
|
||||
A user ID needs to be provided in order to ban
|
||||
using this command.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
is_banned = False
|
||||
ban_list = await guild.bans()
|
||||
for entry in ban_list:
|
||||
if entry.user.id == user_id:
|
||||
is_banned = True
|
||||
break
|
||||
|
||||
if is_banned:
|
||||
await ctx.send(_("User is already banned."))
|
||||
if not user_ids:
|
||||
await ctx.send_help()
|
||||
return
|
||||
|
||||
if not (days >= 0 and days <= 7):
|
||||
await ctx.send(_("Invalid days. Must be between 0 and 7."))
|
||||
return
|
||||
|
||||
if not guild.me.guild_permissions.ban_members:
|
||||
return await ctx.send(_("I lack the permissions to do this."))
|
||||
|
||||
ban_list = await guild.bans()
|
||||
for entry in ban_list:
|
||||
for user_id in user_ids:
|
||||
if entry.user.id == user_id:
|
||||
errors[user_id] = _("User {} is already banned.".format(user_id))
|
||||
|
||||
user_ids = remove_processed(user_ids)
|
||||
|
||||
if not user_ids:
|
||||
await show_results()
|
||||
return
|
||||
|
||||
for user_id in user_ids:
|
||||
user = guild.get_member(user_id)
|
||||
if user is not None:
|
||||
# Instead of replicating all that handling... gets attr from decorator
|
||||
return await ctx.invoke(self.ban, user, None, reason=reason)
|
||||
user = discord.Object(id=user_id) # User not in the guild, but
|
||||
try:
|
||||
result = await self.ban_user(
|
||||
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
|
||||
)
|
||||
if result is True:
|
||||
banned.append(user_id)
|
||||
else:
|
||||
errors[user_id] = _("Failed to ban user {}: {}".format(user_id, result))
|
||||
except Exception as e:
|
||||
errors[user_id] = _("Failed to ban user {}: {}".format(user_id, e))
|
||||
|
||||
user_ids = remove_processed(user_ids)
|
||||
|
||||
if not user_ids:
|
||||
await show_results()
|
||||
return
|
||||
|
||||
for user_id in user_ids:
|
||||
user = discord.Object(id=user_id)
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
queue_entry = (guild.id, user_id)
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason)
|
||||
await guild.ban(user, reason=audit_reason, delete_message_days=days)
|
||||
log.info("{}({}) hackbanned {}".format(author.name, author.id, user_id))
|
||||
except discord.NotFound:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("User not found. Have you provided the correct user ID?"))
|
||||
errors[user_id] = _("User {} does not exist.".format(user_id))
|
||||
continue
|
||||
except discord.Forbidden:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("I lack the permissions to do this."))
|
||||
errors[user_id] = _("Could not ban {}: missing permissions.".format(user_id))
|
||||
continue
|
||||
else:
|
||||
await ctx.send(_("Done. The user will not be able to join this server."))
|
||||
banned.append(user_id)
|
||||
|
||||
user_info = await self.bot.get_user_info(user_id)
|
||||
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
@ -524,7 +535,9 @@ class Mod(commands.Cog):
|
||||
channel=None,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
errors["0"] = _("Failed to create modlog case: {}".format(e))
|
||||
|
||||
await show_results()
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@ -1423,6 +1436,69 @@ class Mod(commands.Cog):
|
||||
else:
|
||||
await ctx.send(_("That user doesn't have any recorded name or nickname change."))
|
||||
|
||||
async def ban_user(
|
||||
self,
|
||||
user: discord.Member,
|
||||
ctx: commands.Context,
|
||||
days: Optional[int] = 0,
|
||||
reason: str = None,
|
||||
create_modlog_case=False,
|
||||
) -> Union[str, bool]:
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
if author == user:
|
||||
return _("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
return _(
|
||||
"I cannot let you do that. You are "
|
||||
"not higher than the user in the role "
|
||||
"hierarchy."
|
||||
)
|
||||
elif guild.me.top_role <= user.top_role or user == guild.owner:
|
||||
return _("I cannot do that due to discord hierarchy rules")
|
||||
elif not (days >= 0 and days <= 7):
|
||||
return _("Invalid days. Must be between 0 and 7.")
|
||||
|
||||
audit_reason = get_audit_reason(author, reason)
|
||||
|
||||
queue_entry = (guild.id, user.id)
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason, delete_message_days=days)
|
||||
log.info(
|
||||
"{}({}) banned {}({}), deleting {} days worth of messages".format(
|
||||
author.name, author.id, user.name, user.id, str(days)
|
||||
)
|
||||
)
|
||||
except discord.Forbidden:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
return _("I'm not allowed to do that.")
|
||||
except Exception as e:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
return e
|
||||
|
||||
if create_modlog_case:
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"ban",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
return _(
|
||||
"The user was banned but an error occurred when trying to "
|
||||
"create the modlog entry: {}".format(e)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def get_names_and_nicks(self, user):
|
||||
names = await self.settings.user(user).past_names()
|
||||
nicks = await self.settings.member(user).past_nicks()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user