add thread feature for punished users

This commit is contained in:
Brandon 2022-05-30 17:44:54 -04:00
parent bc56b1b8be
commit a19214f882
4 changed files with 152 additions and 15 deletions

View file

@ -182,6 +182,7 @@ def permissions_for_roles(channel, *roles):
for target, overwrite in channel.overwrites.items():
# Handle default role first, if present
allow, deny = overwrite.pair()
allow, deny = allow.value, deny.value
if overwrite == default:
base.handle_overwrite(allow=allow, deny=deny)

View file

@ -0,0 +1,83 @@
# NOTE: this file contains backports or unintroduced features of next versions of dpy (as for 1.7.3)
import discord
from discord.http import Route
class THREAD_TYPES:
PUBLIC_THREAD = 11
PRIVATE_THREAD = 12
async def create_thread(
bot,
channel: discord.TextChannel,
name: str,
archive: int = 1440,
invitable: bool = False,
thread_type: int = THREAD_TYPES.PRIVATE_THREAD,
rate_limit: int = 0,
):
"""
Creates a new thread in the channel from the message
Args:
channel (TextChannel): The channel the thread will be apart of
name (str): The name of the thread
archive (int, Optional): The archive duration. Can be 60, 1440, 4320, and 10080 seconds
invitable (bool, Optional): Whether non moderators can add other non-moderators to a thread. Only used for private threads
thread_type (int, Optional): The type of thread (public or private)
rate_limit(int, Optional): Set the rate limit for users, from 0-21600 seconds
Returns:
int: The channel ID of the newly created thread
Note:
The guild must be boosted for longer thread durations then a day. The archive parameter will automatically be scaled down if the feature is not present.
Raises HTTPException 400 if thread creation fails
"""
guild = channel.guild
if archive > 4320 and "THREE_DAY_THREAD_ARCHIVE" not in guild.features:
archive = 1440
elif archive == 10080 and "SEVEN_DAY_THREAD_ARCHIVE" not in guild.features:
archive = 4320
if thread_type == THREAD_TYPES.PRIVATE_THREAD and "PRIVATE_THREADS" not in guild.features:
raise AttributeError("Your guild requires Level 2 Boost to use private threads.")
fields = {
"name": name,
"auto_archive_duration": archive,
"type": thread_type,
"invitable": invitable,
"rate_limit_per_user": rate_limit,
}
reason = "Punish Thread Creation"
r = Route(
"POST",
"/channels/{channel_id}/threads",
channel_id=channel.id,
)
return (await bot.http.request(r, json=fields, reason=reason))["id"]
async def add_user_thread(bot, channel: int, member: discord.Member):
"""
Add a user to a thread
Args:
channel (int): The channel id that represents the thread
member (Member): The member to add to the thread
"""
reason = "Punish Add Member"
r = Route(
"POST",
"/channels/{channel_id}/thread-members/{user_id}",
channel_id=channel,
user_id=member.id,
)
return await bot.http.request(r, reason=reason)

View file

@ -7,6 +7,7 @@ import discord
from .utils import *
from .memoizer import Memoizer
from .discord_thread_feature import add_user_thread, create_thread
# general
import asyncio
@ -16,6 +17,7 @@ import inspect
import logging
import time
import textwrap
from typing import Union
log = logging.getLogger("red.punish")
@ -59,6 +61,7 @@ class Punish(commands.Cog):
"ROLE_ID": None,
"NITRO_ID": None,
"CHANNEL_ID": None,
"use_threads": False,
}
self.config.register_guild(**default_guild)
@ -377,29 +380,41 @@ class Punish(commands.Cog):
async def punishset(self, ctx):
pass
@punishset.command(name="threads")
async def punishset_threads(self, ctx, use_threads: bool):
"""
Have punished users put into their own threads when punished
These threads are private and will be seen by moderators with the Manage Threads permission and the punished user.
"""
if "PRIVATE_THREADS" not in ctx.guild.features and use_threads:
await ctx.send(
error("Your guild must be boosted to Level 2 to use private threads, which this feature requires.")
)
return
await self.config.guild(ctx.guild).use_threads.set(use_threads)
await ctx.tick()
@punishset.command(name="remove-roles")
async def punishset_remove_role_list(self, ctx, *, rolelist=None):
async def punishset_remove_role_list(self, ctx, *rolelist: Union[discord.Role, str]):
"""Set what roles to remove when punishing.
COMMA SEPARATED LIST (e.g. Admin,Staff,Mod), Can also use role IDs as well.
To get current remove role list, run command with no roles.
Add role_list_clear as the role to clear the guild's remove role list.
List of roles can be mentions, names, or role IDs. Role name with spaces must be put in quotes
"""
guild = ctx.guild
role_list = await self.config.guild(guild).REMOVE_ROLE_LIST()
punished = await self.config.guild(guild).PUNISHED()
current_roles = resolve_role_list(guild, role_list)
if rolelist is None:
if not rolelist:
if current_roles:
names_list = format_list(*(r.name for r in current_roles))
await ctx.send(f"Current list of roles removed when a user is punished: {names_list}")
await ctx.send(f"Current list of roles removed when a user is punished: `{names_list}`")
else:
await ctx.send("No roles defined for removal.")
return
elif "role_list_clear" in rolelist.lower():
elif "role_list_clear" in rolelist:
await ctx.send("Remove role list cleared.")
await self.config.guild(guild).REMOVE_ROLE_LIST.set([])
return
@ -408,7 +423,10 @@ class Punish(commands.Cog):
notfound_names = set()
punish_role = await self.get_role(guild, quiet=True)
for lookup in rolelist.split(","):
for lookup in rolelist:
if not isinstance(lookup, str):
found_roles.add(lookup)
continue
lookup = lookup.strip()
role = role_from_string(guild, lookup)
@ -444,10 +462,10 @@ class Punish(commands.Cog):
await self.config.guild(guild).REMOVE_ROLE_LIST.set([r.id for r in found_roles])
fmt_list = format_list(*(r.name for r in found_roles))
await ctx.send(f"Will remove these roles when a user is punished: {fmt_list}.{extra}")
await ctx.send(f"Will remove these roles when a user is punished: `{fmt_list}.{extra}`")
@punishset.command(name="nitro-role")
async def punishset_nitro_role(self, ctx, *, role: str = None):
async def punishset_nitro_role(self, ctx, *, role: Union[discord.Role, str] = None):
"""
Set nitro booster role so its not removed when punishing.
If your server doesn't have a nitro role, run this command with the role string `no_nitro_role`
@ -456,7 +474,7 @@ class Punish(commands.Cog):
current = await self.config.guild(guild).NITRO_ID()
current = role_from_string(guild, current)
if role and role.lower() == "no_nitro_role":
if role and isinstance(role, str) and role.lower() == "no_nitro_role":
await self.config.guild(guild).NITRO_ID.set(role)
await ctx.send("No nitro role set.")
return
@ -468,7 +486,8 @@ class Punish(commands.Cog):
await ctx.send("No nitro role defined.")
return
role = role_from_string(guild, role)
if isinstance(role, str):
role = role_from_string(guild, role)
if not role:
await ctx.send("Role not found!")
return
@ -1066,6 +1085,7 @@ class Punish(commands.Cog):
hierarchy_allowed = ctx.author.top_role > member.top_role
case_min_length = await self.config.guild(guild).CASE_MIN_LENGTH()
nitro_role = await self.config.guild(guild).NITRO_ID()
use_threads = await self.config.guild(guild).use_threads()
if nitro_role is None:
await ctx.send(f"Please set the nitro role using `{ctx.prefix}punishset nitro-role`")
@ -1088,7 +1108,7 @@ class Punish(commands.Cog):
# double check it actually worked
isolated = await isolate.config.guild(guild).ISOLATED()
if str(member.id) in isolated:
await ctx.send(error("Couldn't remove isolation from user, please do it manually."))
await ctx.send(error("Couldn't remove isolation from user, please do so manually."))
return
if duration and duration.lower() in ["forever", "inf", "infinite"]:
@ -1275,6 +1295,38 @@ class Punish(commands.Cog):
if not quiet:
await ctx.send(msg)
# create thread for user to talk in, if this is a new case
if use_threads and not current:
if case_number is None:
thread_name = f"{reason[:50]} - {str(member)[:50]}"
else:
thread_name = f"Case {case_number} - {member.name}"
thread_name = thread_name[:101] # 100 character name limit
channel = await self.config.guild(guild).CHANNEL_ID()
channel = guild.get_channel(channel)
if channel is None:
await ctx.send(error("Punish channel not found!"))
else:
try:
thread_id = await create_thread(self.bot, channel, thread_name, archive=10080)
# add punished user to thread
await add_user_thread(self.bot, thread_id, member)
# add moderator who sanctioned the action to the thread
await add_user_thread(self.bot, thread_id, ctx.author)
except AttributeError:
await ctx.send(
error(
"Your guild no longer has Level 2 boost, private threads and punish threads will not function."
)
)
except Exception as e:
await ctx.send(
error(
f"I could not create the thread for the user, please make sure I have the `Manage Threads` permission on the punish channel and the punish role is setup correctly (punished user can view the punish channel)!"
)
)
return True
# Functions related to unpunishing

View file

@ -182,6 +182,7 @@ def permissions_for_roles(channel, *roles):
for target, overwrite in channel.overwrites.items():
# Handle default role first, if present
allow, deny = overwrite.pair()
allow, deny = allow.value, deny.value
if overwrite == default:
base.handle_overwrite(allow=allow, deny=deny)