feat: added most of the functionality into the shortmute cog, time to see how broken it is!

This commit is contained in:
Seaswimmer 2023-08-08 00:02:24 -04:00
parent 9834c645f7
commit 63c764b6cc
No known key found for this signature in database
GPG key ID: 5019678FD9CF50D8
2 changed files with 108 additions and 225 deletions

View file

@ -2,8 +2,9 @@
"author" : ["SeaswimmerTheFsh"], "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", "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", "name" : "Shortmute",
"short" : "Allows staff members to shortmute individuals for up to 60 minutes.", "short" : "Allows staff members to shortmute individuals for up to 30 minutes.",
"description" : "Allows staff members to shortmute individuals for up to 60 minutes, using Discord's Timeouts feature.", "description" : "Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature.",
"tags" : ["moderation, mutes, timeouts"], "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"]
} }

View file

@ -1,237 +1,119 @@
import contextlib from datetime import datetime
import datetime
from typing import List, Literal, Optional, Union
import discord import discord
import humanize from pytimeparse2 import disable_dateutil, parse
from discord.http import Route from discord import ui
from redbot.core import Config, commands, modlog from redbot.core import Config, commands, app_commands, modlog
from redbot.core.bot import Red
from redbot.core.commands.converter import TimedeltaConverter
RequestType = Literal["discord_deleted_user", "owner", "user", "user_strict"]
class Shortmute(commands.Cog): class Shortmute(commands.Cog):
"""Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature. """Allows staff members to shortmute individuals for up to 30 minutes, using Discord's Timeouts feature."""
Maintained by SeaswimmerTheFsh.
Original source code by sravan1946."""
def __init__(self, bot: Red) -> None: def __init__(self, bot) -> None:
self.bot = bot 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( self.config.register_guild(
dm = True 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! passed_info = {
""" "target": target,
pre_processed = super().format_help_for_context(ctx) "duration": duration,
return f"{pre_processed}\n\nAuthors: {', '.join(self.__author__)}\nCog Version: {self.__version__}" "reason": reason,
"interaction": interaction
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
} }
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) class ShortmuteButtons(ui.View):
await modlog.create_case( def __init__(self, timeout, passed_info: dict):
bot=ctx.bot, super().__init__()
guild=ctx.guild, self.timeout = timeout
created_at=datetime.datetime.now(datetime.timezone.utc), self.passed_info = passed_info
action_type="timeout" if time else "untimeout", self.config = Config.get_conf(None, cog_name='Shortmute', identifier=25781647388294)
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)
async def timeout_role( @ui.button(label="Yes", style=discord.ButtonStyle.success)
self, async def shortmute_button_yes(self, button: ui.Button, interaction: discord.Interaction):
ctx: commands.Context, disable_dateutil()
role: discord.Role, target = self.passed_info['target']
time: datetime.timedelta, duration = self.passed_info['duration']
reason: Optional[str] = None, readable_duration = self.passed_info['readable_duration']
) -> List[discord.Member]: reason = self.passed_info['reason']
failed = [] old_interaction = self.passed_info['interaction']
members = list(role.members) timedelta = parse(f'{duration} minutes')
for member in members: 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: try:
await self.timeout_user(ctx, member, time, reason) await target.send(embed=dm_embed)
except discord.HTTPException: except discord.HTTPException as error:
failed.append(member) await old_message.edit(content="Could not message the target, user most likely has Direct Messages disabled.")
return failed
@commands.command(aliases=["tt"]) @ui.button(label="No", style=discord.ButtonStyle.danger)
@commands.guild_only() async def shortmute_button_no(self, button: ui.Button, interaction: discord.Interaction):
@commands.cooldown(1, 1, commands.BucketType.user) message = await self.passed_info['interaction'].edit_original_response(content="Command cancelled.", view=None, embed=None)
@commands.mod_or_permissions(administrator=True) await message.delete(delay=3)
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.")
@commands.command(aliases=["utt"]) @commands.command()
@commands.guild_only() async def shortmute_dm(self, ctx: commands.Context, enabled: bool = None):
@commands.cooldown(1, 1, commands.BucketType.user) """This command changes if the `/shortmute` slash command Direct Messages its' target."""
@commands.mod_or_permissions(administrator=True) old_value = await self.config.guild(ctx.guild.id).dm()
async def untimeout( if enabled:
self, await self.config.guild(ctx.guild.id).dm.set(enabled)
ctx: commands.Context, await ctx.send(content=f"Shortmute Direct Message setting changed!\nOld value: `{old_value}`\nNew value: `{enabled}`")
member_or_role: Union[discord.Member, discord.Role], elif old_value is True:
*, await ctx.send(content="Shortmute Direct Messages are currently enabled!")
reason: Optional[str] = None, elif old_value is False:
): await ctx.send(content="Shortmute Direct Messages are currently disabled!")
"""
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)
)