feat: added most of the functionality into the shortmute cog, time to see how broken it is!
This commit is contained in:
parent
9834c645f7
commit
63c764b6cc
2 changed files with 108 additions and 225 deletions
|
@ -2,8 +2,9 @@
|
|||
"author" : ["SeaswimmerTheFsh"],
|
||||
"install_msg" : "Thank you for installing Shortmute!\nYou can find the source code of this cog here: https://git.seaswimmer.cc/SeaswimmerTheFsh/GalaxyCogs",
|
||||
"name" : "Shortmute",
|
||||
"short" : "Allows staff members to shortmute individuals for up to 60 minutes.",
|
||||
"description" : "Allows staff members to shortmute individuals for up to 60 minutes, using Discord's Timeouts feature.",
|
||||
"short" : "Allows staff members to shortmute individuals for up to 30 minutes.",
|
||||
"description" : "Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.",
|
||||
"tags" : ["moderation, mutes, timeouts"],
|
||||
"end_user_data_statement": "This cog stores no end user data."
|
||||
"end_user_data_statement": "This cog stores no end user data.",
|
||||
"requirements": ["pytimeparse2"]
|
||||
}
|
||||
|
|
|
@ -1,237 +1,119 @@
|
|||
import contextlib
|
||||
import datetime
|
||||
from typing import List, Literal, Optional, Union
|
||||
|
||||
from datetime import datetime
|
||||
import discord
|
||||
import humanize
|
||||
from discord.http import Route
|
||||
from redbot.core import Config, commands, modlog
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.commands.converter import TimedeltaConverter
|
||||
|
||||
RequestType = Literal["discord_deleted_user", "owner", "user", "user_strict"]
|
||||
from pytimeparse2 import disable_dateutil, parse
|
||||
from discord import ui
|
||||
from redbot.core import Config, commands, app_commands, modlog
|
||||
|
||||
class Shortmute(commands.Cog):
|
||||
"""Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.
|
||||
Maintained by SeaswimmerTheFsh.
|
||||
Original source code by sravan1946."""
|
||||
"""Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature."""
|
||||
|
||||
def __init__(self, bot: Red) -> None:
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=2354731, force_registration=True)
|
||||
self.config = Config.get_conf(self, identifier=25781647388294, force_registration=True)
|
||||
self.config.register_guild(
|
||||
dm = True
|
||||
)
|
||||
|
||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||
@app_commands.command()
|
||||
@app_commands.rename(target='member')
|
||||
async def shortmute(self, interaction: discord.Interaction, target: discord.Member, duration: int, reason: str, evidence_link: str = None, evidence_image: discord.AppCommandOptionType.attachment = None):
|
||||
"""Shortmute someone for up to 30m.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
target: discord.Member
|
||||
The member to shortmute
|
||||
duration: int
|
||||
The duration of the shortmute
|
||||
reason: str
|
||||
The reason for the shortmute
|
||||
evidence_link: str = None
|
||||
An image link to evidence for the shortmute, do not use with evidence_image
|
||||
evidence_image: discord.AppCommandOptionType.attachment = None
|
||||
An image file used as evidence for the shortmute, do not use with evidence_link
|
||||
"""
|
||||
Thanks Sinbad!
|
||||
"""
|
||||
pre_processed = super().format_help_for_context(ctx)
|
||||
return f"{pre_processed}\n\nAuthors: {', '.join(self.__author__)}\nCog Version: {self.__version__}"
|
||||
|
||||
async def red_delete_data_for_user(
|
||||
self, *, requester: RequestType, user_id: int
|
||||
) -> None:
|
||||
# TODO: Replace this with the proper end user data removal handling.
|
||||
super().red_delete_data_for_user(requester=requester, user_id=user_id)
|
||||
|
||||
async def is_user_timed_out(self, member: discord.Member) -> bool:
|
||||
r = Route(
|
||||
"GET",
|
||||
"/guilds/{guild_id}/members/{user_id}",
|
||||
guild_id=member.guild.id,
|
||||
user_id=member.id,
|
||||
)
|
||||
try:
|
||||
data = await self.bot.http.request(r)
|
||||
except discord.NotFound:
|
||||
return False
|
||||
return data["communication_disabled_until"] is not None
|
||||
|
||||
async def pre_load(self):
|
||||
with contextlib.suppress(RuntimeError):
|
||||
await modlog.register_casetype(
|
||||
name="timeout",
|
||||
default_setting=True,
|
||||
image=":mute:",
|
||||
case_str="Timeout",
|
||||
)
|
||||
await modlog.register_casetype(
|
||||
name="untimeout",
|
||||
default_setting=True,
|
||||
image=":sound:",
|
||||
case_str="Untimeout",
|
||||
)
|
||||
|
||||
async def timeout_user(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
member: discord.Member,
|
||||
time: Optional[datetime.timedelta],
|
||||
reason: Optional[str] = None,
|
||||
) -> None:
|
||||
r = Route(
|
||||
"PATCH",
|
||||
"/guilds/{guild_id}/members/{user_id}",
|
||||
guild_id=ctx.guild.id,
|
||||
user_id=member.id,
|
||||
)
|
||||
|
||||
payload = {
|
||||
"communication_disabled_until": str(
|
||||
datetime.datetime.now(datetime.timezone.utc) + time
|
||||
)
|
||||
if time
|
||||
else None
|
||||
passed_info = {
|
||||
"target": target,
|
||||
"duration": duration,
|
||||
"reason": reason,
|
||||
"interaction": interaction
|
||||
}
|
||||
if evidence_image and evidence_link:
|
||||
await interaction.response.send_message(content="You've provided both the `evidence_image` and the `evidence_link` arguments! Please only use one or the other.")
|
||||
return
|
||||
elif evidence_link:
|
||||
evidence = evidence_link
|
||||
elif evidence_image:
|
||||
evidence = str(evidence_image)
|
||||
if duration == 1 or duration == -1:
|
||||
readable_duration = f"{duration} minute"
|
||||
passed_info.update({
|
||||
"readable_duration": readable_duration
|
||||
})
|
||||
else:
|
||||
readable_duration = f"{duration} minutes"
|
||||
passed_info.update({
|
||||
"readable_duration": readable_duration
|
||||
})
|
||||
if duration > 30:
|
||||
await interaction.response(content=f"{readable_duration} is longer than the 30 minutes you are allowed to shortmute users for.", ephemeral=True)
|
||||
return
|
||||
elif duration < 1:
|
||||
await interaction.response(content=f"Please shortmute the user for longer than {readable_duration}! The maximum duration is 30 minutes.", ephemeral=True)
|
||||
return
|
||||
embed = discord.Embed(title="Are you sure?", description=f"Moderator: {interaction.user.mention}\nTarget: {target.mention}\nDuration: `{readable_duration}`\nReason: `{reason}`", color=await self.bot.get_embed_color(None))
|
||||
if evidence:
|
||||
embed.set_image(evidence)
|
||||
passed_info.update({
|
||||
"evidence": evidence
|
||||
})
|
||||
await interaction.response.send_message(embed=embed, view=self.ShortmuteButtons(timeout=180, passed_info=passed_info), ephemeral=True)
|
||||
|
||||
await ctx.bot.http.request(r, json=payload, reason=reason)
|
||||
await modlog.create_case(
|
||||
bot=ctx.bot,
|
||||
guild=ctx.guild,
|
||||
created_at=datetime.datetime.now(datetime.timezone.utc),
|
||||
action_type="timeout" if time else "untimeout",
|
||||
user=member,
|
||||
moderator=ctx.author,
|
||||
reason=reason,
|
||||
until=(datetime.datetime.now(datetime.timezone.utc) + time)
|
||||
if time
|
||||
else None,
|
||||
channel=ctx.channel,
|
||||
)
|
||||
if await self.config.guild(member.guild).dm():
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
message = "You have been"
|
||||
message += (
|
||||
f" timed out for {humanize.naturaldelta(time)}"
|
||||
if time
|
||||
else " untimedout"
|
||||
)
|
||||
message += f" in {ctx.guild.name}"
|
||||
message += f" for reason: {reason}" if reason else ""
|
||||
await member.send(message)
|
||||
class ShortmuteButtons(ui.View):
|
||||
def __init__(self, timeout, passed_info: dict):
|
||||
super().__init__()
|
||||
self.timeout = timeout
|
||||
self.passed_info = passed_info
|
||||
self.config = Config.get_conf(None, cog_name='Shortmute', identifier=25781647388294)
|
||||
|
||||
async def timeout_role(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
role: discord.Role,
|
||||
time: datetime.timedelta,
|
||||
reason: Optional[str] = None,
|
||||
) -> List[discord.Member]:
|
||||
failed = []
|
||||
members = list(role.members)
|
||||
for member in members:
|
||||
@ui.button(label="Yes", style=discord.ButtonStyle.success)
|
||||
async def shortmute_button_yes(self, button: ui.Button, interaction: discord.Interaction):
|
||||
disable_dateutil()
|
||||
target = self.passed_info['target']
|
||||
duration = self.passed_info['duration']
|
||||
readable_duration = self.passed_info['readable_duration']
|
||||
reason = self.passed_info['reason']
|
||||
old_interaction = self.passed_info['interaction']
|
||||
timedelta = parse(f'{duration} minutes')
|
||||
edit_embed = discord.Embed(title="Shortmute confirmed!", description=f"Moderator: {old_interaction.user.mention}\nTarget: {target.mention}\nDuration: `{readable_duration}`\nReason: `{reason}`", color=await self.bot.get_embed_color(None))
|
||||
if self.passed_info.get('evidence'):
|
||||
evidence = self.passed_info['evidence']
|
||||
edit_embed.set_image(evidence)
|
||||
old_message = await old_interaction.edit_original_response(embed=edit_embed, view=None)
|
||||
await target.timeout(until=timedelta, reason=f"User shortmuted for {readable_duration} by {old_interaction.user.name} ({old_interaction.user.id}) for: {reason}")
|
||||
await interaction.response.send_message(content=f"{target.mention} shortmuted for {readable_duration} by {old_interaction.user.mention} for: `{reason}`")
|
||||
if await self.config.guild(old_interaction.guild_id).dm() is True:
|
||||
dm_embed = discord.Embed(title=f"You've been shortmuted in {old_interaction.guild.name}!", description=f"Moderator: {old_interaction.user.mention}\nTarget: {target.mention}\nDuration: `{readable_duration}`\nReason: `{reason}`", color=await self.bot.get_embed_color(None))
|
||||
if evidence:
|
||||
dm_embed.set_image(evidence)
|
||||
try:
|
||||
await self.timeout_user(ctx, member, time, reason)
|
||||
except discord.HTTPException:
|
||||
failed.append(member)
|
||||
return failed
|
||||
await target.send(embed=dm_embed)
|
||||
except discord.HTTPException as error:
|
||||
await old_message.edit(content="Could not message the target, user most likely has Direct Messages disabled.")
|
||||
|
||||
@commands.command(aliases=["tt"])
|
||||
@commands.guild_only()
|
||||
@commands.cooldown(1, 1, commands.BucketType.user)
|
||||
@commands.mod_or_permissions(administrator=True)
|
||||
async def timeout(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
member_or_role: Union[discord.Member, discord.Role],
|
||||
time: TimedeltaConverter(
|
||||
minimum=datetime.timedelta(minutes=1),
|
||||
maximum=datetime.timedelta(days=28),
|
||||
default_unit="minutes",
|
||||
allowed_units=["minutes", "seconds", "hours", "days"],
|
||||
) = None,
|
||||
*,
|
||||
reason: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Timeout users.
|
||||
`<member_or_role>` is the username/rolename, ID or mention. If provided a role,
|
||||
everyone with that role will be timedout.
|
||||
`[time]` is the time to mute for. Time is any valid time length such as `45 minutes`
|
||||
or `3 days`. If nothing is provided the timeout will be 60 seconds default.
|
||||
`[reason]` is the reason for the timeout. Defaults to `None` if nothing is provided.
|
||||
Examples:
|
||||
`[p]timeout @member 5m talks too much`
|
||||
`[p]timeout @member 10m`
|
||||
"""
|
||||
if not time:
|
||||
time = datetime.timedelta(seconds=60)
|
||||
timestamp = datetime.datetime.now(datetime.timezone.utc) + time
|
||||
timestamp = int(datetime.datetime.timestamp(timestamp))
|
||||
if isinstance(member_or_role, discord.Member):
|
||||
check = await is_allowed_by_hierarchy(ctx.bot, ctx.author, member_or_role)
|
||||
if not check:
|
||||
return await ctx.send("You cannot timeout this user due to hierarchy.")
|
||||
if member_or_role.permissions_in(ctx.channel).administrator:
|
||||
return await ctx.send("You can't timeout an administrator.")
|
||||
await self.timeout_user(ctx, member_or_role, time, reason)
|
||||
return await ctx.send(
|
||||
f"{member_or_role.mention} has been timed out till <t:{timestamp}:f>."
|
||||
)
|
||||
if isinstance(member_or_role, discord.Role):
|
||||
await ctx.send(
|
||||
f"Timeing out {len(member_or_role.members)} members till <t:{timestamp}:f>."
|
||||
)
|
||||
failed = await self.timeout_role(ctx, member_or_role, time, reason)
|
||||
return await ctx.send(f"Failed to timeout {len(failed)} members.")
|
||||
@ui.button(label="No", style=discord.ButtonStyle.danger)
|
||||
async def shortmute_button_no(self, button: ui.Button, interaction: discord.Interaction):
|
||||
message = await self.passed_info['interaction'].edit_original_response(content="Command cancelled.", view=None, embed=None)
|
||||
await message.delete(delay=3)
|
||||
|
||||
@commands.command(aliases=["utt"])
|
||||
@commands.guild_only()
|
||||
@commands.cooldown(1, 1, commands.BucketType.user)
|
||||
@commands.mod_or_permissions(administrator=True)
|
||||
async def untimeout(
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
member_or_role: Union[discord.Member, discord.Role],
|
||||
*,
|
||||
reason: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Untimeout users.
|
||||
`<member_or_role>` is the username/rolename, ID or mention. If
|
||||
provided a role, everyone with that role will be untimed.
|
||||
`[reason]` is the reason for the untimeout. Defaults to `None`
|
||||
if nothing is provided.
|
||||
"""
|
||||
if isinstance(member_or_role, discord.Member):
|
||||
is_timedout = await self.is_user_timed_out(member_or_role)
|
||||
if not is_timedout:
|
||||
return await ctx.send("This user is not timed out.")
|
||||
await self.timeout_user(ctx, member_or_role, None, reason)
|
||||
return await ctx.send(f"Removed timeout from {member_or_role.mention}")
|
||||
if isinstance(member_or_role, discord.Role):
|
||||
await ctx.send(
|
||||
f"Removing timeout from {len(member_or_role.members)} members."
|
||||
)
|
||||
members = list(member_or_role.members)
|
||||
for member in members:
|
||||
if await self.is_user_timed_out(member):
|
||||
await self.timeout_user(ctx, member, None, reason)
|
||||
return await ctx.send(f"Removed timeout from {len(members)} members.")
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def timeoutset(self, ctx: commands.Context):
|
||||
"""Manage timeout settings."""
|
||||
|
||||
@timeoutset.command(name="dm")
|
||||
async def timeoutset_dm(self, ctx: commands.Context):
|
||||
"""Change whether to DM the user when they are timed out."""
|
||||
current = await self.config.guild(ctx.guild).dm()
|
||||
await self.config.guild(ctx.guild).dm.set(not current)
|
||||
w = "Will not" if current else "Will"
|
||||
await ctx.send(f"I {w} DM the user when they are timed out.")
|
||||
|
||||
|
||||
# https://github.com/phenom4n4n/phen-cogs/blob/8727d6ee74b40709c7eb9300713dc22b88a17915/roleutils/utils.py#L34
|
||||
async def is_allowed_by_hierarchy(
|
||||
bot: Red, user: discord.Member, member: discord.Member
|
||||
) -> bool:
|
||||
return (
|
||||
user.guild.owner_id == user.id
|
||||
or user.top_role > member.top_role
|
||||
or await bot.is_owner(user)
|
||||
)
|
||||
@commands.command()
|
||||
async def shortmute_dm(self, ctx: commands.Context, enabled: bool = None):
|
||||
"""This command changes if the `/shortmute` slash command Direct Messages its' target."""
|
||||
old_value = await self.config.guild(ctx.guild.id).dm()
|
||||
if enabled:
|
||||
await self.config.guild(ctx.guild.id).dm.set(enabled)
|
||||
await ctx.send(content=f"Shortmute Direct Message setting changed!\nOld value: `{old_value}`\nNew value: `{enabled}`")
|
||||
elif old_value is True:
|
||||
await ctx.send(content="Shortmute Direct Messages are currently enabled!")
|
||||
elif old_value is False:
|
||||
await ctx.send(content="Shortmute Direct Messages are currently disabled!")
|
||||
|
|
Loading…
Reference in a new issue