Allow [p]ban to hackban and rename [p]hackban to [p]massban (#4422)

* ban revamp

* black & converters fix

* discord.object

* Update redbot/cogs/admin/admin.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* remove discord.user converter

* black

* .

* Update redbot/cogs/mod/kickban.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>

* incomplete

* massban support

* black

* Use 2-tuple to separate result and the message in `ban_user()`

This also fixes the issue with `True` being equal to `1` which caused a problem with previously returned types

* Whoops...

* trailing whitespace...

* I missed this one

* Update kickban.py

* Update kickban.py

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
This commit is contained in:
PhenoM4n4n 2020-10-15 14:20:20 -07:00 committed by GitHub
parent 47c4edf335
commit dc817aeeac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -2,7 +2,7 @@ import asyncio
import contextlib import contextlib
import logging import logging
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Optional, Union from typing import Optional, Tuple, Union
import discord import discord
from redbot.core import commands, i18n, checks, modlog from redbot.core import commands, i18n, checks, modlog
@ -62,67 +62,109 @@ class KickBanMixin(MixinMeta):
async def ban_user( async def ban_user(
self, self,
user: discord.Member, user: Union[discord.Member, discord.User, discord.Object],
ctx: commands.Context, ctx: commands.Context,
days: int = 0, days: int = 0,
reason: str = None, reason: str = None,
create_modlog_case=False, create_modlog_case=False,
) -> Union[str, bool]: ) -> Tuple[bool, str]:
author = ctx.author author = ctx.author
guild = ctx.guild guild = ctx.guild
if author == user: removed_temp = False
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.config, 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 (0 <= days <= 7):
return _("Invalid days. Must be between 0 and 7.")
toggle = await self.config.guild(guild).dm_on_kickban() if not (0 <= days <= 7):
if toggle: return False, _("Invalid days. Must be between 0 and 7.")
with contextlib.suppress(discord.HTTPException):
em = discord.Embed( if isinstance(user, discord.Member):
title=bold(_("You have been banned from {guild}.").format(guild=guild)) if author == user:
return (
False,
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}"),
) )
em.add_field( elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, user):
name=_("**Reason**"), return (
value=reason if reason is not None else _("No reason was given."), False,
inline=False, _(
"I cannot let you do that. You are "
"not higher than the user in the role "
"hierarchy."
),
) )
await user.send(embed=em) elif guild.me.top_role <= user.top_role or user == guild.owner:
return False, _("I cannot do that due to Discord hierarchy rules.")
toggle = await self.config.guild(guild).dm_on_kickban()
if toggle:
with contextlib.suppress(discord.HTTPException):
em = discord.Embed(
title=bold(_("You have been banned from {guild}.").format(guild=guild))
)
em.add_field(
name=_("**Reason**"),
value=reason if reason is not None else _("No reason was given."),
inline=False,
)
await user.send(embed=em)
ban_type = "ban"
else:
tempbans = await self.config.guild(guild).current_tempbans()
ban_list = [ban.user.id for ban in await guild.bans()]
if user.id in ban_list:
if user.id in tempbans:
async with self.config.guild(guild).current_tempbans() as tempbans:
tempbans.remove(user.id)
removed_temp = True
else:
return (
False,
_("User with ID {user_id} is already banned.").format(user_id=user.id),
)
ban_type = "hackban"
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)
try: if removed_temp:
await guild.ban(user, reason=audit_reason, delete_message_days=days)
log.info( log.info(
"{}({}) banned {}({}), deleting {} days worth of messages.".format( "{}({}) upgraded the tempban for {} to a permaban.".format(
author.name, author.id, user.name, user.id, str(days) author.name, author.id, user.id
) )
) )
except discord.Forbidden: success_message = _(
return _("I'm not allowed to do that.") "User with ID {user_id} was upgraded from a temporary to a permanent ban."
except Exception as e: ).format(user_id=user.id)
log.exception( else:
"{}({}) attempted to kick {}({}), but an error occurred.".format( username = user.name if hasattr(user, "name") else "Unknown"
author.name, author.id, user.name, user.id try:
await guild.ban(user, reason=audit_reason, delete_message_days=days)
log.info(
"{}({}) {}ned {}({}), deleting {} days worth of messages.".format(
author.name, author.id, ban_type, username, user.id, str(days)
)
) )
) success_message = _("Done. That felt good.")
return _("An unexpected error occurred.") except discord.Forbidden:
return False, _("I'm not allowed to do that.")
except discord.NotFound:
return False, _("User with ID {user_id} not found").format(user_id=user.id)
except Exception as e:
log.exception(
"{}({}) attempted to {} {}({}), but an error occurred.".format(
author.name, author.id, ban_type, username, user.id
)
)
return False, _("An unexpected error occurred.")
if create_modlog_case: if create_modlog_case:
await modlog.create_case( await modlog.create_case(
self.bot, self.bot,
guild, guild,
ctx.message.created_at.replace(tzinfo=timezone.utc), ctx.message.created_at.replace(tzinfo=timezone.utc),
"ban", ban_type,
user, user,
author, author,
reason, reason,
@ -130,7 +172,7 @@ class KickBanMixin(MixinMeta):
channel=None, channel=None,
) )
return True return True, success_message
async def check_tempban_expirations(self): async def check_tempban_expirations(self):
while self == self.bot.get_cog("Mod"): while self == self.bot.get_cog("Mod"):
@ -247,13 +289,15 @@ class KickBanMixin(MixinMeta):
async def ban( async def ban(
self, self,
ctx: commands.Context, ctx: commands.Context,
user: discord.Member, user: Union[discord.Member, RawUserIds],
days: Optional[int] = None, days: Optional[int] = None,
*, *,
reason: str = None, reason: str = None,
): ):
"""Ban a user from this server and optionally delete days of messages. """Ban a user from this server and optionally delete days of messages.
A user ID should be provided if the user is not a member of this server.
If days is not a number, it's treated as the first word of the reason. If days is not a number, it's treated as the first word of the reason.
Minimum 0 days, maximum 7. If not specified, defaultdays setting will be used instead.""" Minimum 0 days, maximum 7. If not specified, defaultdays setting will be used instead."""
@ -261,21 +305,20 @@ class KickBanMixin(MixinMeta):
guild = ctx.guild guild = ctx.guild
if days is None: if days is None:
days = await self.config.guild(guild).default_days() days = await self.config.guild(guild).default_days()
if isinstance(user, int):
user = self.bot.get_user(user) or discord.Object(id=user)
result = await self.ban_user( success_, message = await self.ban_user(
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
) )
if result is True: await ctx.send(message)
await ctx.send(_("Done. It was about time."))
elif isinstance(result, str):
await ctx.send(result)
@commands.command() @commands.command(aliases=["hackban"])
@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( async def massban(
self, self,
ctx: commands.Context, ctx: commands.Context,
user_ids: commands.Greedy[RawUserIds], user_ids: commands.Greedy[RawUserIds],
@ -283,7 +326,7 @@ class KickBanMixin(MixinMeta):
*, *,
reason: str = None, reason: str = None,
): ):
"""Preemptively bans user(s) from the server. """Mass bans user(s) from the server.
User IDs need to be provided in order to ban User IDs need to be provided in order to ban
using this command.""" using this command."""
@ -339,7 +382,7 @@ class KickBanMixin(MixinMeta):
# We need to check if a user is tempbanned here because otherwise they won't be processed later on. # We need to check if a user is tempbanned here because otherwise they won't be processed later on.
continue continue
else: else:
errors[user_id] = _("User {user_id} is already banned.").format( errors[user_id] = _("User with ID {user_id} is already banned.").format(
user_id=user_id user_id=user_id
) )
@ -358,14 +401,14 @@ class KickBanMixin(MixinMeta):
else: else:
# Instead of replicating all that handling... gets attr from decorator # Instead of replicating all that handling... gets attr from decorator
try: try:
result = await self.ban_user( success, reason = await self.ban_user(
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
) )
if result is True: if success:
banned.append(user_id) banned.append(user_id)
else: else:
errors[user_id] = _("Failed to ban user {user_id}: {reason}").format( errors[user_id] = _("Failed to ban user {user_id}: {reason}").format(
user_id=user_id, reason=result user_id=user_id, reason=reason
) )
except Exception as e: except Exception as e:
errors[user_id] = _("Failed to ban user {user_id}: {reason}").format( errors[user_id] = _("Failed to ban user {user_id}: {reason}").format(
@ -397,13 +440,13 @@ class KickBanMixin(MixinMeta):
await guild.ban(user, reason=audit_reason, delete_message_days=days) 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:
errors[user_id] = _("User {user_id} does not exist.").format( errors[user_id] = _("User with ID {user_id} not found").format(
user_id=user_id user_id=user_id
) )
continue continue
except discord.Forbidden: except discord.Forbidden:
errors[user_id] = _( errors[user_id] = _(
"Could not ban {user_id}: missing permissions." "Could not ban user with ID {user_id}: missing permissions."
).format(user_id=user_id) ).format(user_id=user_id)
continue continue
else: else: