Skip to content

Commit ccf3ee2

Browse files
authored
Merge branch 'main' into main
2 parents 6d2323b + f73dd0f commit ccf3ee2

File tree

23 files changed

+635
-180
lines changed

23 files changed

+635
-180
lines changed

appeals/bugreport.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async def column_bug():
4040
# Insert a row
4141
guild = SomeTable(num1=guild_id, num2=guild_id, num3=guild_id)
4242
await guild.save()
43-
res = await SomeTable.select().first()
43+
res: dict = await SomeTable.select().first()
4444
print("PICCOLO")
4545
print("num1", res["num1"], type(res["num1"]))
4646
print("num2", res["num2"], type(res["num2"]))
@@ -68,7 +68,7 @@ async def update_bug():
6868
number = 123
6969
guild = SomeTable(num1=number, num2=number, num3=number)
7070
await guild.save()
71-
res = await SomeTable.select().first()
71+
res: dict = await SomeTable.select().first()
7272
print("BEFORE")
7373
print("num1", res["num1"])
7474
print("num2", res["num2"], type(res["num2"]))

appeals/commands/admin.py

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,6 @@
1515
log = logging.getLogger("red.vrt.appeals.commands.admin")
1616

1717

18-
class MessageParser:
19-
def __init__(self, argument):
20-
if "-" not in argument:
21-
raise commands.BadArgument("Invalid format, must be `channelID-messageID`")
22-
try:
23-
cid, mid = [i.strip() for i in argument.split("-")]
24-
except ValueError:
25-
raise commands.BadArgument("Invalid format, must be `channelID-messageID`")
26-
try:
27-
self.channel_id = int(cid)
28-
except ValueError:
29-
raise commands.BadArgument("Channel ID must be an integer")
30-
try:
31-
self.message_id = int(mid)
32-
except ValueError:
33-
raise commands.BadArgument("Message ID must be an integer")
34-
35-
3618
class Admin(MixinMeta):
3719
async def no_appealguild(self, ctx: commands.Context):
3820
txt = (
@@ -161,7 +143,7 @@ async def view_appeal_settings(self, ctx: commands.Context):
161143

162144
def cname(cid: int):
163145
if cid:
164-
channel = self.bot.get_channel(cid)
146+
channel: discord.TextChannel = self.bot.get_channel(cid)
165147
if channel:
166148
return channel.mention
167149
else:
@@ -185,7 +167,9 @@ def cname(cid: int):
185167
f"**Appeal Message**: {appeal_msg}\n"
186168
f"**Alert Channel**: {cname(appealguild.alert_channel)}\n"
187169
f"**Appeal Limit**: {appealguild.appeal_limit}\n"
188-
f"**Alert Roles**: {', '.join([r.mention for r in roles]) if roles else 'None set'}\n"
170+
f"**Discussion Threads**: {'Enabled' if appealguild.discussion_threads else 'Disabled'}\n"
171+
f"**Vote Emojis**: {'Enabled' if appealguild.vote_emojis else 'Disabled'}\n"
172+
f"**Alert Roles**: {', '.join([r for r in roles]) if roles else 'None set'}\n"
189173
f"**Questions**: {await AppealQuestion.count().where(AppealQuestion.guild == ctx.guild.id)}"
190174
)
191175
embed = discord.Embed(
@@ -254,13 +238,13 @@ async def approve_appeal(self, ctx: commands.Context, submission_id: int, *, rea
254238
if not member:
255239
member = await self.bot.get_or_fetch_user(submission.user_id)
256240
# Send the submission to the approved channel and then delete from the pending channel
257-
approved_channel = ctx.guild.get_channel(appealguild.approved_channel)
241+
approved_channel: discord.TextChannel = ctx.guild.get_channel(appealguild.approved_channel)
258242
if approved_channel:
259243
new_message = await approved_channel.send(embed=submission.embed(member))
260244
await AppealSubmission.update({AppealSubmission.message_id: new_message.id}).where(
261245
(AppealSubmission.id == submission_id) & (AppealSubmission.guild == ctx.guild.id)
262246
)
263-
pending_channel = ctx.guild.get_channel(appealguild.pending_channel)
247+
pending_channel: discord.TextChannel = ctx.guild.get_channel(appealguild.pending_channel)
264248
if pending_channel:
265249
if not pending_channel.permissions_for(ctx.guild.me).manage_messages:
266250
await ctx.send(f"I do not have permissions to delete messages from {pending_channel.mention}")
@@ -269,6 +253,8 @@ async def approve_appeal(self, ctx: commands.Context, submission_id: int, *, rea
269253
message = await pending_channel.fetch_message(submission.message_id)
270254
if message.thread:
271255
await message.thread.delete()
256+
elif thread := ctx.guild.get_thread(submission.discussion_thread):
257+
await thread.delete()
272258
await message.delete()
273259
except discord.NotFound:
274260
await ctx.send(f"Submission message not found in {pending_channel.mention}")
@@ -349,16 +335,18 @@ async def deny_appeal(self, ctx: commands.Context, submission_id: int, *, reason
349335
submission.status = "denied"
350336
submission.reason = reason or ""
351337
await ctx.send(f"Successfully denied submission ID: {submission_id}")
352-
member = ctx.guild.get_member(submission.user_id) or self.bot.get_user(submission.user_id)
338+
member = ctx.guild.get_member(submission.user_id)
339+
if not member:
340+
member = await self.bot.get_or_fetch_user(submission.user_id)
353341
# Send the submission to the denied channel and then delete from the pending channel
354-
denied_channel = ctx.guild.get_channel(appealguild.denied_channel)
342+
denied_channel: discord.TextChannel = ctx.guild.get_channel(appealguild.denied_channel)
355343
if denied_channel:
356344
new_embed = submission.embed(member)
357345
new_message = await denied_channel.send(embed=new_embed)
358346
await AppealSubmission.update({AppealSubmission.message_id: new_message.id}).where(
359347
(AppealSubmission.id == submission_id) & (AppealSubmission.guild == ctx.guild.id)
360348
)
361-
pending_channel = ctx.guild.get_channel(appealguild.pending_channel)
349+
pending_channel: discord.TextChannel = ctx.guild.get_channel(appealguild.pending_channel)
362350
if pending_channel:
363351
if not pending_channel.permissions_for(ctx.guild.me).manage_messages:
364352
await ctx.send(f"I do not have permissions to delete messages from {pending_channel.mention}")
@@ -367,6 +355,8 @@ async def deny_appeal(self, ctx: commands.Context, submission_id: int, *, reason
367355
message = await pending_channel.fetch_message(submission.message_id)
368356
if message.thread:
369357
await message.thread.delete()
358+
elif thread := ctx.guild.get_thread(submission.discussion_thread):
359+
await thread.delete()
370360
await message.delete()
371361
except discord.NotFound:
372362
await ctx.send(f"Submission message not found in {pending_channel.mention}")
@@ -396,7 +386,7 @@ async def delete_appeal(self, ctx: commands.Context, submission_id: int):
396386
)
397387
if not submission:
398388
return await ctx.send("No submission found with that ID.")
399-
channel = ctx.guild.get_channel(getattr(appealguild, f"{submission.status}_channel"))
389+
channel: discord.TextChannel = ctx.guild.get_channel(getattr(appealguild, f"{submission.status}_channel"))
400390
if channel:
401391
if not channel.permissions_for(ctx.guild.me).manage_messages:
402392
await ctx.send(f"I do not have permissions to delete messages from {channel.mention}")
@@ -463,6 +453,16 @@ async def set_channels(
463453
if not appealguild:
464454
return await self.no_appealguild(ctx)
465455

456+
permissions = {
457+
"View Channel": channel.permissions_for(ctx.guild.me).view_channel,
458+
"Send Messages": channel.permissions_for(ctx.guild.me).send_messages,
459+
"Embed Links": channel.permissions_for(ctx.guild.me).embed_links,
460+
"Create Threads": channel.permissions_for(ctx.guild.me).create_public_threads,
461+
}
462+
for perm, has_perm in permissions.items():
463+
if not has_perm:
464+
return await ctx.send(f"I don't have the `{perm}` permission in {channel.mention}!")
465+
466466
if channel_type == "pending":
467467
if channel.id == appealguild.pending_channel:
468468
return await ctx.send("That channel is already set as the pending appeals channel.")
@@ -508,19 +508,21 @@ async def create_appeal_message(self, ctx: commands.Context, channel: discord.Te
508508

509509
@ensure_db_connection()
510510
@appealset.command(name="appealmessage")
511-
async def set_appeal_message(self, ctx: commands.Context, message: MessageParser):
511+
async def set_appeal_message(self, ctx: commands.Context, message: discord.Message):
512512
"""
513513
Set the message where users will appeal from
514514
Message format: `channelID-messageID`
515515
"""
516516
appealguild = await AppealGuild.objects().get(AppealGuild.id == ctx.guild.id)
517517
if not appealguild:
518518
return await self.no_appealguild(ctx)
519-
channel = ctx.guild.get_channel(message.channel_id)
519+
channel = ctx.guild.get_channel(message.channel.id)
520520
if not channel:
521521
return await ctx.send("Invalid channel ID provided.")
522+
if not isinstance(channel, discord.TextChannel):
523+
return await ctx.send("The provided channel ID is not a text channel.")
522524
try:
523-
msg = await channel.fetch_message(message.message_id)
525+
msg = await channel.fetch_message(message.channel.id)
524526
except discord.NotFound:
525527
return await ctx.send("Invalid message ID provided.")
526528
except discord.Forbidden:
@@ -546,11 +548,11 @@ async def add_appeal_question(self, ctx: commands.Context, *, question: str):
546548
count = await AppealQuestion.count().where(AppealQuestion.guild == appealguild)
547549
if count >= 24:
548550
return await ctx.send("You can only have up to 24 questions in the appeal form.")
549-
question = AppealQuestion(
551+
question_obj = AppealQuestion(
550552
guild=ctx.guild.id,
551553
question=question,
552554
)
553-
await question.save()
555+
await question_obj.save()
554556
await ctx.send("Successfully added the question to the appeal form.")
555557
await self.refresh(ctx)
556558

@@ -778,15 +780,16 @@ async def set_alert_roles(self, ctx: commands.Context, *, role: discord.Role | i
778780
These roles will be pinged in the appeal server, NOT the target server.
779781
"""
780782
rid = role.id if isinstance(role, discord.Role) else role
783+
role_name = role.mention if isinstance(role, discord.Role) else f"<@&{rid}>"
781784
appealguild = await AppealGuild.objects().get(AppealGuild.id == ctx.guild.id)
782785
if not appealguild:
783786
return await self.no_appealguild(ctx)
784787
if rid in appealguild.alert_roles:
785788
appealguild.alert_roles.remove(rid)
786-
await ctx.send(f"Removed {role.name} from the alert roles.")
789+
await ctx.send(f"Removed {role_name} from the alert roles.")
787790
else:
788791
appealguild.alert_roles.append(rid)
789-
await ctx.send(f"Added {role.name} to the alert roles.")
792+
await ctx.send(f"Added {role_name} to the alert roles.")
790793
await AppealGuild.update({AppealGuild.alert_roles: appealguild.alert_roles}).where(
791794
AppealGuild.id == ctx.guild.id
792795
)
@@ -804,9 +807,11 @@ async def set_alert_channel(self, ctx: commands.Context, *, channel: discord.Tex
804807
if not appealguild:
805808
return await self.no_appealguild(ctx)
806809
if isinstance(channel, int):
807-
channel = self.bot.get_channel(channel)
810+
channel: discord.TextChannel = self.bot.get_channel(channel)
808811
if not channel:
809812
return await ctx.send("Invalid channel ID provided.")
813+
if not isinstance(channel, discord.TextChannel):
814+
return await ctx.send("You must provide a valid text channel.")
810815
if channel:
811816
await AppealGuild.update({AppealGuild.alert_channel: channel.id}).where(AppealGuild.id == ctx.guild.id)
812817
return await ctx.send(f"Successfully set the alert channel to {channel.mention}")
@@ -827,7 +832,7 @@ async def refresh(self, ctx: discord.Guild | commands.Context) -> str | None:
827832
await ctx.send(f"Appeal system not ready yet: {reason}")
828833
return reason
829834
appealguild = await AppealGuild.objects().get(AppealGuild.id == guild.id)
830-
channel = guild.get_channel(appealguild.appeal_channel)
835+
channel: discord.TextChannel = guild.get_channel(appealguild.appeal_channel)
831836
message = await channel.fetch_message(appealguild.appeal_message)
832837
view = AppealView(custom_id=f"{appealguild.id}")
833838
await message.edit(view=view)
@@ -915,3 +920,33 @@ async def set_button_emoji(
915920
)
916921
await ctx.send("Successfully removed the button emoji", view=view)
917922
await self.refresh(ctx)
923+
924+
@ensure_db_connection()
925+
@appealset.command(name="discussionthreads")
926+
async def toggle_discussion_threads(self, ctx: commands.Context):
927+
"""Toggle whether appeals will have a discussion thread created for them"""
928+
appealguild = await AppealGuild.objects().get(AppealGuild.id == ctx.guild.id)
929+
if not appealguild:
930+
return await self.no_appealguild(ctx)
931+
if appealguild.discussion_threads:
932+
appealguild.discussion_threads = False
933+
await ctx.send("Successfully disabled discussion threads for appeals.")
934+
else:
935+
appealguild.discussion_threads = True
936+
await ctx.send("Successfully enabled discussion threads for appeals.")
937+
await appealguild.save([AppealGuild.discussion_threads])
938+
939+
@ensure_db_connection()
940+
@appealset.command(name="voteemojis")
941+
async def toggle_vote_emojis(self, ctx: commands.Context):
942+
"""Toggle whether appeals will have vote emojis added to them"""
943+
appealguild = await AppealGuild.objects().get(AppealGuild.id == ctx.guild.id)
944+
if not appealguild:
945+
return await self.no_appealguild(ctx)
946+
if appealguild.vote_emojis:
947+
appealguild.vote_emojis = False
948+
await ctx.send("Successfully disabled vote emojis for appeals.")
949+
else:
950+
appealguild.vote_emojis = True
951+
await ctx.send("Successfully enabled vote emojis for appeals.")
952+
await appealguild.save([AppealGuild.vote_emojis])
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
2+
from piccolo.columns.column_types import BigInt, Boolean
3+
from piccolo.columns.indexes import IndexMethod
4+
5+
ID = "2025-06-06T20:23:41:085322"
6+
VERSION = "1.13.0"
7+
DESCRIPTION = "discussion thread and vote emoji toggles"
8+
9+
10+
async def forwards():
11+
manager = MigrationManager(
12+
migration_id=ID, app_name="appeals", description=DESCRIPTION
13+
)
14+
15+
manager.add_column(
16+
table_class_name="AppealGuild",
17+
tablename="appeal_guild",
18+
column_name="discussion_threads",
19+
db_column_name="discussion_threads",
20+
column_class_name="Boolean",
21+
column_class=Boolean,
22+
params={
23+
"default": True,
24+
"null": False,
25+
"primary_key": False,
26+
"unique": False,
27+
"index": False,
28+
"index_method": IndexMethod.btree,
29+
"choices": None,
30+
"db_column_name": None,
31+
"secret": False,
32+
},
33+
schema=None,
34+
)
35+
36+
manager.add_column(
37+
table_class_name="AppealGuild",
38+
tablename="appeal_guild",
39+
column_name="vote_emojis",
40+
db_column_name="vote_emojis",
41+
column_class_name="Boolean",
42+
column_class=Boolean,
43+
params={
44+
"default": False,
45+
"null": False,
46+
"primary_key": False,
47+
"unique": False,
48+
"index": False,
49+
"index_method": IndexMethod.btree,
50+
"choices": None,
51+
"db_column_name": None,
52+
"secret": False,
53+
},
54+
schema=None,
55+
)
56+
57+
manager.add_column(
58+
table_class_name="AppealSubmission",
59+
tablename="appeal_submission",
60+
column_name="discussion_thread",
61+
db_column_name="discussion_thread",
62+
column_class_name="BigInt",
63+
column_class=BigInt,
64+
params={
65+
"default": 0,
66+
"null": False,
67+
"primary_key": False,
68+
"unique": False,
69+
"index": False,
70+
"index_method": IndexMethod.btree,
71+
"choices": None,
72+
"db_column_name": None,
73+
"secret": False,
74+
},
75+
schema=None,
76+
)
77+
78+
return manager

appeals/db/tables.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class AppealGuild(Table):
3131
alert_roles = Array(BigInt()) # Roles to alert when a new appeal is submitted
3232
alert_channel = BigInt() # Channel to alert when a new appeal is submitted
3333
appeal_limit = Integer(default=1) # Maximum number of times a user can submit an appeal
34+
discussion_threads = Boolean(default=True) # Whether to create discussion threads for appeals
35+
vote_emojis = Boolean(default=False) # Whether to add basic check and cross emojis to the appeal message
36+
3437
# Appeal button
3538
button_style = Text(default="primary") # can be `primary`, `secondary`, `success`, `danger`
3639
button_label = Text(default="Submit Appeal")
@@ -100,6 +103,9 @@ class AppealSubmission(Table):
100103
message_id = BigInt() # Message ID of the submission message
101104
reason = Text() # Reason for denial
102105

106+
# 0.2.0+
107+
discussion_thread = BigInt() # ID of the discussion thread for this question, if applicable
108+
103109
def created(self, type: t.Literal["t", "T", "d", "D", "f", "F", "R"]) -> str:
104110
return f"<t:{int(self.created_at.timestamp())}:{type}>"
105111

appeals/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ class Appeals(Commands, Listeners, commands.Cog, metaclass=CompositeMetaClass):
2525
"""Straightforward ban appeal system for Discord servers."""
2626

2727
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
28-
__version__ = "0.1.2"
28+
__version__ = "0.2.1"
2929

3030
def __init__(self, bot: Red):
3131
super().__init__()
3232
self.bot: Red = bot
33-
self.db: SQLiteEngine = None
33+
self.db: SQLiteEngine | None = None
3434
self.db_utils: DBUtils = DBUtils()
3535

3636
def format_help_for_context(self, ctx: commands.Context):

0 commit comments

Comments
 (0)