Ruby-Cogs/mod/mod.py
2025-04-02 22:57:51 -04:00

836 lines
34 KiB
Python

import contextlib
import logging
import re
from datetime import datetime, timedelta, timezone
from typing import Literal, Optional, Tuple, Union
import discord
import TagScriptEngine as tse
from redbot.cogs.mod.mod import Mod as ModClass
from redbot.cogs.mod.utils import is_allowed_by_hierarchy
from redbot.core import Config, app_commands, checks, commands, modlog
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import bold, box, humanize_timedelta
from redbot.core.utils.mod import get_audit_reason
from ._tagscript import (
TagScriptConverter,
ban_message,
kick_message,
process_tagscript,
tempban_message,
unban_message,
)
log = logging.getLogger("red.flarecogs.mod")
from discord.ext import commands as dpy_commands
from discord.ext.commands import BadArgument
ID_REGEX = re.compile(r"([0-9]{15,20})")
USER_MENTION_REGEX = re.compile(r"<@!?([0-9]{15,21})>$")
# https://github.com/flaree/Red-DiscordBot/blob/FR-custom-bankick-msgs/redbot/core/commands/converter.py#L207
class RawUserIdConverter(dpy_commands.Converter):
"""
Converts ID or user mention to an `int`.
Useful for commands like ``[p]ban`` or ``[p]unban`` where the bot is not necessarily
going to share any servers with the user that a moderator wants to ban/unban.
This converter doesn't check if the ID/mention points to an actual user
but it won't match IDs and mentions that couldn't possibly be valid.
For example, the converter will not match on "123" because the number doesn't have
enough digits to be valid ID but, it will match on "12345678901234567" even though
there is no user with such ID.
"""
async def convert(self, ctx, argument: str) -> int:
# This is for the hackban and unban commands, where we receive IDs that
# are most likely not in the guild.
# Mentions are supported, but most likely won't ever be in cache.
if match := ID_REGEX.match(argument) or USER_MENTION_REGEX.match(argument):
return int(match.group(1))
raise BadArgument(("'{input}' doesn't look like a valid user ID.").format(input=argument))
class Mod(ModClass):
"""Mod with custom messages."""
modset = ModClass.modset.copy()
__version__ = "1.3.0"
def format_help_for_context(self, ctx: commands.Context):
pre_processed = super().format_help_for_context(ctx)
return f"{pre_processed}\nCog Version: {self.__version__}"
def __init__(self, bot: Red):
super().__init__(bot)
self.bot: Red = bot
self._config: Config = Config.get_conf(self, 95932766180343808, force_registration=True)
self._config.register_guild(
**{
"kick_message": kick_message,
"ban_message": ban_message,
"tempban_message": tempban_message,
"unban_message": unban_message,
"require_reason": False,
}
)
async def red_get_data_for_user(self, *, user_id: int):
# this cog does not story any data
return {}
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
user_id: int,
):
return None
@modset.command()
@commands.guild_only()
async def kickmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
"""Set the message sent when a user is kicked.
**Blocks:**
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
**Variables:**
- `{user}`: [member that was kicked.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{moderator}`: [modrator that kicked the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{reason}`: reason for the kick.
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
"""
guild = ctx.guild
await self._config.guild(guild).kick_message.set(message)
await ctx.send("Kick message updated:\n{}".format(box(str(message), lang="json")))
@modset.command()
@commands.guild_only()
async def banmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
"""Set the message sent when a user is banned.
**Blocks:**
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
**Variables:**
- `{user}`: [member that was banned.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{moderator}`: [modrator that banned the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{reason}`: reason for the ban.
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
- `{days}`: number of days of messages deleted.
"""
guild = ctx.guild
await self._config.guild(guild).ban_message.set(message)
await ctx.send("Ban message updated:\n{}".format(box(str(message), lang="json")))
@modset.command()
@commands.guild_only()
async def tempbanmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
"""Set the message sent when a user is tempbanned.
**Blocks:**
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
**Variables:**
- `{user}`: [member that was tempbanned.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{moderator}`: [modrator that tempbanned the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{reason}`: reason for the tempban.
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
- `{days}`: number of days of messages deleted.
- `{duration}`: duration of the tempban.
"""
guild = ctx.guild
await self._config.guild(guild).tempban_message.set(message)
await ctx.send("Tempban message updated:\n{}".format(box(str(message), lang="json")))
@modset.command()
@commands.guild_only()
async def unbanmessage(self, ctx: commands.Context, *, message: TagScriptConverter):
"""Set the message sent when a user is unbanned.
**Blocks:**
- [Assignment Block](https://seina-cogs.readthedocs.io/en/latest/tags/tse_blocks.html#assignment-block)
- [Embed Block](https://seina-cogs.readthedocs.io/en/latest/tags/parsing_blocks.html#embed-block)
**Variables:**
- `{user}`: [member that was tempbanned.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{moderator}`: [modrator that tempbanned the member.](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block)
- `{reason}`: reason for the tempban.
- `{guild}`: [server](https://seina-cogs.readthedocs.io/en/latest/tags/default_variables.html#server-block)
"""
guild = ctx.guild
await self._config.guild(guild).unban_message.set(message)
await ctx.send("Unban message updated:\n{}".format(box(str(message), lang="json")))
@modset.command(name="showmessages")
async def modset_showmessages(self, ctx: commands.Context):
"""Show the current messages for moderation commands."""
messageData = await self._config.guild(ctx.guild).all()
msg = "Kick Message: {kick_message}\n".format(kick_message=messageData["kick_message"])
msg += "Ban Message: {ban_message}\n".format(ban_message=messageData["ban_message"])
msg += "Tempban Message: {tempban_message}\n".format(
tempban_message=messageData["tempban_message"]
)
msg += "Unban Message: {unban_message}\n".format(
unban_message=messageData["unban_message"]
)
await ctx.send(box(msg))
@modset.command(name="reasons")
async def modset_require_reason(self, ctx: commands.Context, value: bool):
"""Set whether a reason is required for moderation actions."""
await self._config.guild(ctx.guild).require_reason.set(value)
await ctx.send(f"Reason requirement set to {value}")
kick = None
@commands.hybrid_command()
@app_commands.describe(member="The member to kick.", reason="The reason for kicking the user.")
@commands.guild_only()
@commands.bot_has_permissions(kick_members=True)
@checks.admin_or_permissions(kick_members=True)
async def kick(self, ctx: commands.Context, member: discord.Member, *, reason: str = None):
"""
Kick a user.
Examples:
- `[p]kick 428675506947227648 wanted to be kicked.`
This will kick the user with ID 428675506947227648 from the server.
- `[p]kick @Twentysix wanted to be kicked.`
This will kick Twentysix from the server.
If a reason is specified, it will be the reason that shows up
in the audit log.
"""
require_reason = await self._config.guild(ctx.guild).require_reason()
if require_reason and reason is None:
await ctx.send("You must provide a reason for this action.")
return
author = ctx.author
guild = ctx.guild
if author == member:
await ctx.send(
("I cannot let you do that. Self-harm is bad {emoji}").format(
emoji="\N{PENSIVE FACE}"
)
)
return
elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, member):
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 <= member.top_role or member == ctx.guild.owner:
await ctx.send(("I cannot do that due to Discord hierarchy rules."))
return
audit_reason = get_audit_reason(author, reason, shorten=True)
toggle = await self.config.guild(guild).dm_on_kickban()
if toggle:
with contextlib.suppress(discord.HTTPException):
em = discord.Embed(
title=bold(("You have been kicked from {guild}.").format(guild=guild)),
color=await self.bot.get_embed_color(member),
)
em.add_field(
name=("**Reason**"),
value=reason if reason is not None else ("No reason was given."),
inline=False,
)
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))
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
)
)
else:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"kick",
member,
author,
reason,
until=None,
channel=None,
)
message = await self._config.guild(ctx.guild).kick_message()
kwargs = process_tagscript(
message,
{
"user": tse.MemberAdapter(member),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
},
)
if not kwargs:
await self._config.guild(ctx.guild).kick_message.clear()
kwargs = process_tagscript(
kick_message,
{
"user": tse.MemberAdapter(member),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
},
)
await ctx.send(**kwargs)
tempban = None
@commands.hybrid_command()
@app_commands.describe(
member="The member to tempban.",
reason="The reason for tempbanning the user.",
duration="The duration of the tempban.",
days="The number of days of messages to delete.",
)
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def tempban(
self,
ctx: commands.Context,
member: discord.Member,
duration: Optional[commands.TimedeltaConverter] = None,
days: Optional[int] = None,
*,
reason: str = None,
):
"""Temporarily ban a user from this server.
`duration` is the amount of time the user should be banned for.
`days` is the amount of days of messages to cleanup on tempban.
Examples:
- `[p]tempban @Twentysix Because I say so`
This will ban Twentysix for the default amount of time set by an administrator.
- `[p]tempban @Twentysix 15m You need a timeout`
This will ban Twentysix for 15 minutes.
- `[p]tempban 428675506947227648 1d2h15m 5 Evil person`
This will ban the user with ID 428675506947227648 for 1 day 2 hours 15 minutes and will delete the last 5 days of their messages.
"""
require_reason = await self._config.guild(ctx.guild).require_reason()
if require_reason and reason is None:
await ctx.send("You must provide a reason for this action.")
return
guild = ctx.guild
author = ctx.author
if author == member:
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.config, guild, author, member):
await ctx.send(
(
"I cannot let you do that. You are "
"not higher than the user in the role "
"hierarchy."
)
)
return
elif guild.me.top_role <= member.top_role or member == guild.owner:
await ctx.send(("I cannot do that due to Discord hierarchy rules."))
return
guild_data = await self.config.guild(guild).all()
if duration is None:
duration = timedelta(seconds=guild_data["default_tempban_duration"])
unban_time = datetime.now(timezone.utc) + duration
if days is None:
days = guild_data["default_days"]
if not (0 <= days <= 7):
await ctx.send(("Invalid days. Must be between 0 and 7."))
return
invite = await self.get_invite_for_reinvite(ctx, int(duration.total_seconds() + 86400))
await self.config.member(member).banned_until.set(unban_time.timestamp())
async with self.config.guild(guild).current_tempbans() as current_tempbans:
current_tempbans.append(member.id)
with contextlib.suppress(discord.HTTPException):
# We don't want blocked DMs preventing us from banning
msg = ("You have been temporarily banned from {server_name} until {date}.").format(
server_name=guild.name, date=discord.utils.format_dt(unban_time)
)
if guild_data["dm_on_kickban"] and reason:
msg += ("\n\n**Reason:** {reason}").format(reason=reason)
if invite:
msg += ("\n\nHere is an invite for when your ban expires: {invite_link}").format(
invite_link=invite
)
await member.send(msg)
audit_reason = get_audit_reason(author, reason, shorten=True)
try:
await guild.ban(member, reason=audit_reason, delete_message_days=days)
except discord.Forbidden:
await ctx.send(("I can't do that for some reason."))
except discord.HTTPException:
await ctx.send(("Something went wrong while banning."))
else:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"tempban",
member,
author,
reason,
unban_time,
)
message = await self._config.guild(ctx.guild).tempban_message()
humanized_duration = humanize_timedelta(timedelta=duration)
kwargs = process_tagscript(
message,
{
"user": tse.MemberAdapter(member),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
"duration": tse.StringAdapter(humanized_duration),
"days": tse.IntAdapter(days),
},
)
if not kwargs:
await self._config.guild(ctx.guild).tempban_message.clear()
kwargs = process_tagscript(
tempban_message,
{
"user": tse.MemberAdapter(member),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
"duration": tse.StringAdapter(humanized_duration),
"days": tse.IntAdapter(days),
},
)
await ctx.send(**kwargs)
softban = None
@commands.hybrid_command()
@app_commands.describe(
member="The member to softban.", reason="The reason for softbanning the user."
)
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def softban(self, ctx: commands.Context, member: discord.Member, *, reason: str = None):
"""Kick a user and delete 1 day's worth of their messages."""
require_reason = await self._config.guild(ctx.guild).require_reason()
if require_reason and reason is None:
await ctx.send("You must provide a reason for this action.")
return
guild = ctx.guild
author = ctx.author
if author == member:
await ctx.send(
("I cannot let you do that. Self-harm is bad {emoji}").format(
emoji="\N{PENSIVE FACE}"
)
)
return
elif not await is_allowed_by_hierarchy(self.bot, self.config, guild, author, member):
await ctx.send(
(
"I cannot let you do that. You are "
"not higher than the user in the role "
"hierarchy."
)
)
return
audit_reason = get_audit_reason(author, reason, shorten=True)
invite = await self.get_invite_for_reinvite(ctx)
try: # We don't want blocked DMs preventing us from banning
msg = await member.send(
(
"You have been banned and "
"then unbanned as a quick way to delete your messages.\n"
"You can now join the server again. {invite_link}"
).format(invite_link=invite)
)
except discord.HTTPException:
msg = None
try:
await guild.ban(member, reason=audit_reason, delete_message_days=1)
except discord.errors.Forbidden:
await ctx.send(("My role is not high enough to softban that user."))
if msg is not None:
await msg.delete()
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
)
)
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
)
)
return
else:
log.info(
"{}({}) softbanned {}({}), deleting 1 day worth "
"of messages.".format(author.name, author.id, member.name, member.id)
)
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"softban",
member,
author,
reason,
until=None,
channel=None,
)
message = await self._config.guild(ctx.guild).kick_message()
kwargs = process_tagscript(
message,
{
"user": tse.MemberAdapter(member),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
},
)
if not kwargs:
await self._config.guild(ctx.guild).kick_message.clear()
kwargs = process_tagscript(
kick_message,
{
"user": tse.MemberAdapter(member),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
},
)
await ctx.send(**kwargs)
@commands.hybrid_command()
@app_commands.describe(
user="The member to ban.",
reason="The reason for banning the user.",
days="The number of days of messages to delete.",
)
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@commands.admin_or_permissions(ban_members=True)
async def ban(
self,
ctx: commands.Context,
user: discord.Member,
days: Optional[int] = None,
*,
reason: str = None,
):
"""Ban a user from this server and optionally delete days of messages.
`days` is the amount of days of messages to cleanup on ban.
Examples:
- `[p]ban 428675506947227648 7 Continued to spam after told to stop.`
This will ban the user with ID 428675506947227648 and it will delete 7 days worth of messages.
- `[p]ban @Twentysix 7 Continued to spam after told to stop.`
This will ban Twentysix and it will delete 7 days worth 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.
Minimum 0 days, maximum 7. If not specified, the defaultdays setting will be used instead.
"""
require_reason = await self._config.guild(ctx.guild).require_reason()
if require_reason and reason is None:
await ctx.send("You must provide a reason for this action.")
return
guild = ctx.guild
if days is None:
days = await self.config.guild(guild).default_days()
if isinstance(user, int):
user = self.bot.get_user(user) or discord.Object(id=user)
success, message = await self.ban_user(
user=user, ctx=ctx, days=days, reason=reason, create_modlog_case=True
)
if not success:
await ctx.send(message)
return
kwargs = process_tagscript(
message,
{
"user": tse.MemberAdapter(user),
"moderator": tse.MemberAdapter(ctx.author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
"days": tse.IntAdapter(int(days)),
},
)
if not kwargs:
await self._config.guild(ctx.guild).ban_message.clear()
kwargs = process_tagscript(
ban_message,
{
"user": tse.MemberAdapter(user),
"moderator": tse.MemberAdapter(ctx.author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
"days": tse.IntAdapter(int(days)),
},
)
await ctx.send(**kwargs)
ban_user = None
async def ban_user(
self,
user: Union[discord.Member, discord.User, discord.Object],
ctx: commands.Context,
days: int = 0,
reason: str = None,
create_modlog_case=False,
) -> Tuple[bool, str]:
author = ctx.author
guild = ctx.guild
removed_temp = False
if not (0 <= days <= 7):
return False, ("Invalid days. Must be between 0 and 7.")
if isinstance(user, discord.Member):
if author == user:
return (
False,
("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 (
False,
(
"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 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)),
color=await self.bot.get_embed_color(user),
)
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()
try:
await guild.fetch_ban(user)
except discord.NotFound:
pass
else:
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, shorten=True)
if removed_temp:
log.info(
"{}({}) upgraded the tempban for {} to a permaban.".format(
author.name, author.id, user.id
)
)
success_message = (
"User with ID {user(id)} was upgraded from a temporary to a permanent ban."
)
else:
username = user.name if hasattr(user, "name") else "Unknown"
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 = await self._config.guild(ctx.guild).ban_message()
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:
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:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
ban_type,
user,
author,
reason,
until=None,
channel=None,
)
return True, success_message
unban = None
@commands.hybrid_command()
@app_commands.describe(
user_id="The ID of the user to unban.", reason="The reason for unbanning the user."
)
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def unban(
self, ctx: commands.Context, user_id: RawUserIdConverter, *, reason: str = None
):
"""Unban a user from this server.
Requires specifying the target user's ID. To find this, you may either:
1. Copy it from the mod log case (if one was created), or
2. enable developer mode, go to Bans in this server's settings, right-
click the user and select 'Copy ID'."""
require_reason = await self._config.guild(ctx.guild).require_reason()
if require_reason and reason is None:
await ctx.send("You must provide a reason for this action.")
return
guild = ctx.guild
author = ctx.author
audit_reason = get_audit_reason(ctx.author, reason, shorten=True)
try:
ban_entry = await guild.fetch_ban(discord.Object(user_id))
except discord.NotFound:
await ctx.send(("It seems that user isn't banned!"))
return
try:
await guild.unban(ban_entry.user, reason=audit_reason)
except discord.HTTPException:
await ctx.send(("Something went wrong while attempting to unban that user."))
return
else:
await modlog.create_case(
self.bot,
guild,
ctx.message.created_at,
"unban",
ban_entry.user,
author,
reason,
until=None,
channel=None,
)
message = await self._config.guild(ctx.guild).unban_message()
kwargs = process_tagscript(
message,
{
"user": tse.MemberAdapter(ban_entry.user),
"moderator": tse.MemberAdapter(author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
},
)
if not kwargs:
await self._config.guild(ctx.guild).unban_message.clear()
kwargs = process_tagscript(
ban_message,
{
"user": tse.MemberAdapter(user),
"moderator": tse.MemberAdapter(ctx.author),
"reason": tse.StringAdapter(str(reason)),
"guild": tse.GuildAdapter(guild),
},
)
await ctx.send(**kwargs)
if await self.config.guild(guild).reinvite_on_unban():
user = ctx.bot.get_user(user_id)
if not user:
await ctx.send(
("I don't share another server with this user. I can't reinvite them.")
)
return
invite = await self.get_invite_for_reinvite(ctx)
if invite:
try:
await user.send(
(
"You've been unbanned from {server}.\n"
"Here is an invite for that server: {invite_link}"
).format(server=guild.name, invite_link=invite)
)
except discord.Forbidden:
await ctx.send(
(
"I failed to send an invite to that user. "
"Perhaps you may be able to send it for me?\n"
"Here's the invite link: {invite_link}"
).format(invite_link=invite)
)
except discord.HTTPException:
await ctx.send(
(
"Something went wrong when attempting to send that user "
"an invite. Here's the link so you can try: {invite_link}"
).format(invite_link=invite)
)