mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[Mod] Enhanced [p]hackban (#2164)
This commit is contained in:
parent
bcc50557a9
commit
937d2fe0f6
@ -2,14 +2,16 @@ import asyncio
|
|||||||
import contextlib
|
import contextlib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from collections import deque, defaultdict, namedtuple
|
from collections import deque, defaultdict, namedtuple
|
||||||
from typing import cast
|
from typing import Optional, Union, cast
|
||||||
|
|
||||||
import discord
|
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 import checks, Config, modlog, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
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 .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 redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, get_audit_reason
|
||||||
from .log import log
|
from .log import log
|
||||||
@ -19,6 +21,18 @@ from redbot.core.utils.common_filters import filter_invites, filter_various_ment
|
|||||||
_ = T_ = Translator("Mod", __file__)
|
_ = 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(_)
|
@cog_i18n(_)
|
||||||
class Mod(commands.Cog):
|
class Mod(commands.Cog):
|
||||||
"""Moderation tools."""
|
"""Moderation tools."""
|
||||||
@ -386,131 +400,128 @@ class Mod(commands.Cog):
|
|||||||
@commands.bot_has_permissions(ban_members=True)
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def ban(
|
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.
|
"""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."""
|
||||||
If `<days>` is not a number, it's treated as the first word of
|
result = await self.ban_user(
|
||||||
the reason. Minimum 0 days, maximum 7. Defaults to 0.
|
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
|
||||||
"""
|
|
||||||
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}")
|
|
||||||
)
|
)
|
||||||
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 result is True:
|
||||||
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:
|
|
||||||
await ctx.send(_("Done. It was about time."))
|
await ctx.send(_("Done. It was about time."))
|
||||||
|
elif isinstance(result, str):
|
||||||
try:
|
await ctx.send(result)
|
||||||
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)
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.bot_has_permissions(ban_members=True)
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
async def hackban(
|
||||||
"""Pre-emptively ban a user from this server.
|
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
|
author = ctx.author
|
||||||
guild = ctx.guild
|
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:
|
if not user_ids:
|
||||||
await ctx.send(_("User is already banned."))
|
await ctx.send_help()
|
||||||
return
|
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)
|
user = guild.get_member(user_id)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
# Instead of replicating all that handling... gets attr from decorator
|
# Instead of replicating all that handling... gets attr from decorator
|
||||||
return await ctx.invoke(self.ban, user, None, reason=reason)
|
try:
|
||||||
user = discord.Object(id=user_id) # User not in the guild, but
|
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)
|
audit_reason = get_audit_reason(author, reason)
|
||||||
queue_entry = (guild.id, user_id)
|
queue_entry = (guild.id, user_id)
|
||||||
self.ban_queue.append(queue_entry)
|
self.ban_queue.append(queue_entry)
|
||||||
try:
|
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))
|
log.info("{}({}) hackbanned {}".format(author.name, author.id, user_id))
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
self.ban_queue.remove(queue_entry)
|
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:
|
except discord.Forbidden:
|
||||||
self.ban_queue.remove(queue_entry)
|
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:
|
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)
|
user_info = await self.bot.get_user_info(user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
self.bot,
|
self.bot,
|
||||||
@ -524,7 +535,9 @@ class Mod(commands.Cog):
|
|||||||
channel=None,
|
channel=None,
|
||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
errors["0"] = _("Failed to create modlog case: {}".format(e))
|
||||||
|
|
||||||
|
await show_results()
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -1423,6 +1436,69 @@ class Mod(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("That user doesn't have any recorded name or nickname change."))
|
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):
|
async def get_names_and_nicks(self, user):
|
||||||
names = await self.settings.user(user).past_names()
|
names = await self.settings.user(user).past_names()
|
||||||
nicks = await self.settings.member(user).past_nicks()
|
nicks = await self.settings.member(user).past_nicks()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user