Skip to content

Commit aa16b9f

Browse files
committed
Appeals - delete message threads when deleting submissions; update profile generation to support square rendering
1 parent cf05dc2 commit aa16b9f

File tree

3 files changed

+149
-63
lines changed

3 files changed

+149
-63
lines changed

appeals/commands/admin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ async def approve_appeal(self, ctx: commands.Context, submission_id: int, *, rea
267267
else:
268268
try:
269269
message = await pending_channel.fetch_message(submission.message_id)
270+
if message.thread:
271+
await message.thread.delete()
270272
await message.delete()
271273
except discord.NotFound:
272274
await ctx.send(f"Submission message not found in {pending_channel.mention}")
@@ -363,6 +365,8 @@ async def deny_appeal(self, ctx: commands.Context, submission_id: int, *, reason
363365
else:
364366
try:
365367
message = await pending_channel.fetch_message(submission.message_id)
368+
if message.thread:
369+
await message.thread.delete()
366370
await message.delete()
367371
except discord.NotFound:
368372
await ctx.send(f"Submission message not found in {pending_channel.mention}")

appeals/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ 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.1"
28+
__version__ = "0.1.2"
2929

3030
def __init__(self, bot: Red):
3131
super().__init__()

levelup/generator/styles/default.py

Lines changed: 144 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,7 @@
1-
"""
2-
Generate a full profile image with customizable parameters.
3-
If the avatar is animated and not the background, the avatar will be rendered as a gif.
4-
If the background is animated and not the avatar, the background will be rendered as a gif.
5-
If both are animated, the avatar will be rendered as a gif and the background will be rendered as a static image.
6-
To optimize performance, the profile will be generated in 3 layers, the background, the avatar, and the stats.
7-
The stats layer will be generated as a separate image and then pasted onto the background.
8-
9-
Args:
10-
background (t.Optional[bytes], optional): The background image as bytes. Defaults to None.
11-
avatar (t.Optional[bytes], optional): The avatar image as bytes. Defaults to None.
12-
username (t.Optional[str], optional): The username. Defaults to "Spartan117".
13-
status (t.Optional[str], optional): The status. Defaults to "online".
14-
level (t.Optional[int], optional): The level. Defaults to 1.
15-
messages (t.Optional[int], optional): The number of messages. Defaults to 0.
16-
voicetime (t.Optional[int], optional): The voicetime. Defaults to 3600.
17-
stars (t.Optional[int], optional): The number of stars. Defaults to 0.
18-
prestige (t.Optional[int], optional): The prestige level. Defaults to 0.
19-
prestige_emoji (t.Optional[bytes], optional): The prestige emoji as bytes. Defaults to None.
20-
balance (t.Optional[int], optional): The balance. Defaults to 0.
21-
currency_name (t.Optional[str], optional): The name of the currency. Defaults to "Credits".
22-
previous_xp (t.Optional[int], optional): The previous XP. Defaults to 0.
23-
current_xp (t.Optional[int], optional): The current XP. Defaults to 0.
24-
next_xp (t.Optional[int], optional): The next XP. Defaults to 0.
25-
position (t.Optional[int], optional): The position. Defaults to 0.
26-
role_icon (t.Optional[bytes, str], optional): The role icon as bytes or url. Defaults to None.
27-
blur (t.Optional[bool], optional): Whether to blur the box behind the stats. Defaults to False.
28-
user_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the user. Defaults to None.
29-
base_color (t.Optional[t.Tuple[int, int, int]], optional): The base color. Defaults to None.
30-
stat_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the stats. Defaults to None.
31-
level_bar_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the level bar. Defaults to None.
32-
font_path (t.Optional[t.Union[str, Path], optional): The path to the font file. Defaults to None.
33-
render_gif (t.Optional[bool], optional): Whether to render as gif if profile or background is one. Defaults to False.
34-
debug (t.Optional[bool], optional): Whether to raise any errors rather than suppressing. Defaults to False.
35-
36-
Returns:
37-
t.Tuple[bytes, bool]: The generated full profile image as bytes, and whether the image is animated.
38-
"""
39-
1+
import importlib.util
402
import logging
413
import math
4+
import sys
425
import typing as t
436
from io import BytesIO
447
from pathlib import Path
@@ -48,11 +11,65 @@
4811
from redbot.core.utils.chat_formatting import humanize_number
4912

5013
try:
14+
# Loaded from cog
5115
from .. import imgtools
5216
from ..pilmojisrc.core import Pilmoji
5317
except ImportError:
54-
import imgtools
55-
from pilmojisrc.core import Pilmoji
18+
# Running in vscode "Run Python File in Terminal"
19+
# Add parent directory to sys.path to enable imports
20+
parent_dir = Path(__file__).parent.parent
21+
sys.path.insert(0, str(parent_dir))
22+
23+
# Import imgtools directly
24+
imgtools_path = parent_dir / "imgtools.py"
25+
if imgtools_path.exists():
26+
spec = importlib.util.spec_from_file_location("imgtools", imgtools_path)
27+
imgtools = importlib.util.module_from_spec(spec)
28+
sys.modules["imgtools"] = imgtools
29+
spec.loader.exec_module(imgtools)
30+
else:
31+
raise ImportError(f"Could not find imgtools at {imgtools_path}")
32+
33+
# Set up pilmojisrc as a package
34+
pilmoji_dir = parent_dir / "pilmojisrc"
35+
if not pilmoji_dir.exists():
36+
raise ImportError(f"Could not find pilmojisrc directory at {pilmoji_dir}")
37+
38+
# Create and register the pilmojisrc package
39+
pilmojisrc_init = pilmoji_dir / "__init__.py"
40+
if pilmojisrc_init.exists():
41+
spec = importlib.util.spec_from_file_location("pilmojisrc", pilmojisrc_init)
42+
pilmojisrc = importlib.util.module_from_spec(spec)
43+
sys.modules["pilmojisrc"] = pilmojisrc
44+
spec.loader.exec_module(pilmojisrc)
45+
else:
46+
# Create an empty module if __init__.py doesn't exist
47+
pilmojisrc = type(sys)("pilmojisrc")
48+
sys.modules["pilmojisrc"] = pilmojisrc
49+
50+
# Import helpers module first (since core depends on it)
51+
helpers_path = pilmoji_dir / "helpers.py"
52+
if helpers_path.exists():
53+
spec = importlib.util.spec_from_file_location("pilmojisrc.helpers", helpers_path)
54+
helpers = importlib.util.module_from_spec(spec)
55+
pilmojisrc.helpers = helpers
56+
sys.modules["pilmojisrc.helpers"] = helpers
57+
spec.loader.exec_module(helpers)
58+
else:
59+
raise ImportError(f"Could not find helpers module at {helpers_path}")
60+
61+
# Now import core module
62+
core_path = pilmoji_dir / "core.py"
63+
if core_path.exists():
64+
spec = importlib.util.spec_from_file_location("pilmojisrc.core", core_path)
65+
core = importlib.util.module_from_spec(spec)
66+
pilmojisrc.core = core
67+
sys.modules["pilmojisrc.core"] = core
68+
spec.loader.exec_module(core)
69+
Pilmoji = core.Pilmoji
70+
else:
71+
raise ImportError(f"Could not find core module at {core_path}")
72+
5673

5774
log = logging.getLogger("red.vrt.levelup.generator.styles.default")
5875
_ = Translator("LevelUp", __file__)
@@ -85,8 +102,50 @@ def generate_default_profile(
85102
render_gif: bool = False,
86103
debug: bool = False,
87104
reraise: bool = False,
105+
square: bool = False,
88106
**kwargs,
89107
) -> t.Tuple[bytes, bool]:
108+
"""
109+
Generate a full profile image with customizable parameters.
110+
If the avatar is animated and not the background, the avatar will be rendered as a gif.
111+
If the background is animated and not the avatar, the background will be rendered as a gif.
112+
If both are animated, the avatar will be rendered as a gif and the background will be rendered as a static image.
113+
To optimize performance, the profile will be generated in 3 layers, the background, the avatar, and the stats.
114+
The stats layer will be generated as a separate image and then pasted onto the background.
115+
116+
Args:
117+
background (t.Optional[bytes], optional): The background image as bytes. Defaults to None.
118+
avatar (t.Optional[bytes], optional): The avatar image as bytes. Defaults to None.
119+
username (t.Optional[str], optional): The username. Defaults to "Spartan117".
120+
status (t.Optional[str], optional): The status. Defaults to "online".
121+
level (t.Optional[int], optional): The level. Defaults to 1.
122+
messages (t.Optional[int], optional): The number of messages. Defaults to 0.
123+
voicetime (t.Optional[int], optional): The voicetime. Defaults to 3600.
124+
stars (t.Optional[int], optional): The number of stars. Defaults to 0.
125+
prestige (t.Optional[int], optional): The prestige level. Defaults to 0.
126+
prestige_emoji (t.Optional[bytes], optional): The prestige emoji as bytes. Defaults to None.
127+
balance (t.Optional[int], optional): The balance. Defaults to 0.
128+
currency_name (t.Optional[str], optional): The name of the currency. Defaults to "Credits".
129+
previous_xp (t.Optional[int], optional): The previous XP. Defaults to 0.
130+
current_xp (t.Optional[int], optional): The current XP. Defaults to 0.
131+
next_xp (t.Optional[int], optional): The next XP. Defaults to 0.
132+
position (t.Optional[int], optional): The position. Defaults to 0.
133+
role_icon (t.Optional[bytes, str], optional): The role icon as bytes or url. Defaults to None.
134+
blur (t.Optional[bool], optional): Whether to blur the box behind the stats. Defaults to False.
135+
user_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the user. Defaults to None.
136+
base_color (t.Optional[t.Tuple[int, int, int]], optional): The base color. Defaults to None.
137+
stat_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the stats. Defaults to None.
138+
level_bar_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the level bar. Defaults to None.
139+
font_path (t.Optional[t.Union[str, Path], optional): The path to the font file. Defaults to None.
140+
render_gif (t.Optional[bool], optional): Whether to render as gif if profile or background is one. Defaults to False.
141+
debug (t.Optional[bool], optional): Whether to raise any errors rather than suppressing. Defaults to False.
142+
reraise (t.Optional[bool], optional): Whether to raise any errors rather than suppressing. Defaults to False.
143+
square (t.Optional[bool], optional): Whether to render the profile as a square. Defaults to False.
144+
**kwargs: Additional keyword arguments.
145+
146+
Returns:
147+
t.Tuple[bytes, bool]: The generated full profile image as bytes, and whether the image is animated.
148+
"""
90149
user_color = user_color or base_color
91150
stat_color = stat_color or base_color
92151
level_bar_color = level_bar_color or base_color
@@ -130,30 +189,53 @@ def generate_default_profile(
130189
bg_animated = getattr(card, "is_animated", False)
131190
log.debug(f"PFP animated: {pfp_animated}, BG animated: {bg_animated}")
132191

133-
# Ensure the card is the correct size and aspect ratio
134-
desired_card_size = (1050, 450)
135-
# aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size)
136192
# Setup
137193
default_fill = (0, 0, 0) # Default fill color for text
138194
stroke_width = 2 # Width of the stroke around text
139-
name_y = 35 # Upper bound of username placement
140-
stats_y = 160 # Upper bound of stats texts
141-
blur_edge = 450 # Left bound of blur edge
142-
bar_width = 550 # Length of level bar
143-
bar_height = 40 # Height of level bar
144-
bar_start = 475 # Left bound of level bar
145-
bar_top = 380 # Top bound of level bar
146-
stat_bottom = bar_top - 10 # Bottom bound of all stats
147-
stat_start = bar_start + 10 # Left bound of all stats
148-
stat_split = stat_start + 210 # Split between left and right stats
149-
stat_end = 990 # Right bound of all stats
150-
stat_offset = 45 # Offset between stats
151-
circle_x = 60 # Left bound of profile circle
152-
circle_y = 60 # Top bound of profile circle
153-
star_text_x = 910 # Left bound of star text
154-
star_text_y = 35 # Top bound of star text
155-
star_icon_x = 850 # Left bound of star icon
156-
star_icon_y = 35 # Top bound of star icon
195+
196+
if square:
197+
desired_card_size = (450, 450)
198+
# aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size)
199+
name_y = 35 # Upper bound of username placement
200+
stats_y = 160 # Upper bound of stats texts
201+
blur_edge = 450 # Left bound of blur edge
202+
bar_width = 550 # Length of level bar
203+
bar_height = 40 # Height of level bar
204+
bar_start = 475 # Left bound of level bar
205+
bar_top = 380 # Top bound of level bar
206+
stat_bottom = bar_top - 10 # Bottom bound of all stats
207+
stat_start = bar_start + 10 # Left bound of all stats
208+
stat_split = stat_start + 210 # Split between left and right stats
209+
stat_end = 990 # Right bound of all stats
210+
stat_offset = 45 # Offset between stats
211+
circle_x = 60 # Left bound of profile circle
212+
circle_y = 60 # Top bound of profile circle
213+
star_text_x = 910 # Left bound of star text
214+
star_text_y = 35 # Top bound of star text
215+
star_icon_x = 850 # Left bound of star icon
216+
star_icon_y = 35 # Top bound of star icon
217+
else:
218+
# Ensure the card is the correct size and aspect ratio
219+
desired_card_size = (1050, 450)
220+
# aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size)
221+
name_y = 35 # Upper bound of username placement
222+
stats_y = 160 # Upper bound of stats texts
223+
blur_edge = 450 # Left bound of blur edge
224+
bar_width = 550 # Length of level bar
225+
bar_height = 40 # Height of level bar
226+
bar_start = 475 # Left bound of level bar
227+
bar_top = 380 # Top bound of level bar
228+
stat_bottom = bar_top - 10 # Bottom bound of all stats
229+
stat_start = bar_start + 10 # Left bound of all stats
230+
stat_split = stat_start + 210 # Split between left and right stats
231+
stat_end = 990 # Right bound of all stats
232+
stat_offset = 45 # Offset between stats
233+
circle_x = 60 # Left bound of profile circle
234+
circle_y = 60 # Top bound of profile circle
235+
star_text_x = 910 # Left bound of star text
236+
star_text_y = 35 # Top bound of star text
237+
star_icon_x = 850 # Left bound of star icon
238+
star_icon_y = 35 # Top bound of star icon
157239

158240
# Establish layer for all text and accents
159241
stats = Image.new("RGBA", desired_card_size, (0, 0, 0, 0))

0 commit comments

Comments
 (0)