Skip to content

Commit c59e192

Browse files
committed
feat: large images in threadmenu embed
1 parent 4e02991 commit c59e192

File tree

3 files changed

+92
-31
lines changed

3 files changed

+92
-31
lines changed

core/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ class ConfigManager:
160160
"thread_creation_menu_embed_title": None,
161161
"thread_creation_menu_embed_footer": None,
162162
"thread_creation_menu_embed_thumbnail_url": None,
163+
"thread_creation_menu_embed_image_url": None,
164+
"thread_creation_menu_embed_large_image": False,
163165
"thread_creation_menu_embed_footer_icon_url": None,
164166
"thread_creation_menu_embed_color": str(discord.Color.green()),
165167
}
@@ -276,6 +278,7 @@ class ConfigManager:
276278
"thread_creation_menu_anonymous_menu",
277279
"thread_creation_menu_selection_log",
278280
"thread_creation_menu_precreate_channel",
281+
"thread_creation_menu_embed_large_image",
279282
}
280283

281284
enums = {

core/config_help.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,32 @@
15571557
"Consider file size/CDN reliability so it loads quickly for users."
15581558
]
15591559
},
1560+
"thread_creation_menu_embed_image_url": {
1561+
"default": "Empty (no image)",
1562+
"description": "Optional large hero image displayed in the body of the menu embed. If set, this image is shown prominently and takes precedence over the thumbnail.",
1563+
"examples": [
1564+
"`{prefix}config set thread_creation_menu_embed_image_url https://example.com/banner.png`",
1565+
"`{prefix}config delete thread_creation_menu_embed_image_url` (clear)"
1566+
],
1567+
"notes": [
1568+
"Use a direct image URL (PNG/JPEG/GIF).",
1569+
"When both a thumbnail and image URL are set, the image URL is used as the large embed image while the thumbnail may still be shown in the top-right.",
1570+
"See also: `thread_creation_menu_embed_thumbnail_url`, `thread_creation_menu_embed_large_image`."
1571+
]
1572+
},
1573+
"thread_creation_menu_embed_large_image": {
1574+
"default": "No",
1575+
"description": "Promotes the thumbnail to a large hero image when no `thread_creation_menu_embed_image_url` is set. Useful if you want a big image without specifying a separate URL.",
1576+
"examples": [
1577+
"`{prefix}config set thread_creation_menu_embed_large_image yes`",
1578+
"`{prefix}config set thread_creation_menu_embed_large_image no`"
1579+
],
1580+
"notes": [
1581+
"Only applies when `thread_creation_menu_embed_image_url` is not set.",
1582+
"If both are provided, the explicit image URL takes precedence.",
1583+
"See also: `thread_creation_menu_embed_thumbnail_url`, `thread_creation_menu_embed_image_url`."
1584+
]
1585+
},
15601586
"thread_creation_menu_embed_color": {
15611587
"default": "Green (hex for Discord Color.green)",
15621588
"description": "Color for the menu embed's side strip. Accepts hex (e.g. #5865F2) or one of the supported color names.",

core/thread.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ async def wait_until_ready(self) -> None:
9595
try:
9696
await task
9797
except asyncio.TimeoutError:
98-
pass
99-
100-
self.wait_tasks.remove(task)
98+
logger.warning("Waiting for thread setup timed out.")
99+
finally:
100+
if task in self.wait_tasks:
101+
self.wait_tasks.remove(task)
101102

102103
@property
103104
def id(self) -> int:
@@ -120,10 +121,19 @@ def ready(self) -> bool:
120121
return self._ready_event.is_set()
121122

122123
@ready.setter
123-
def ready(self, flag: bool):
124+
def ready(self, flag: bool) -> None:
125+
"""Set the ready state and dispatch thread_create when transitioning to ready.
126+
127+
Some legacy code paths set thread.ready = True/False. This setter preserves that API by
128+
updating the internal event and emitting the creation event when entering the ready state.
129+
"""
124130
if flag:
125-
self._ready_event.set()
126-
self.bot.dispatch("thread_create", self)
131+
if not self._ready_event.is_set():
132+
self._ready_event.set()
133+
try:
134+
self.bot.dispatch("thread_create", self)
135+
except Exception:
136+
pass
127137
else:
128138
self._ready_event.clear()
129139

@@ -1212,23 +1222,12 @@ async def _disable_dm_creation_menu(self) -> None:
12121222
return
12131223
except Exception:
12141224
return
1215-
# Remove components to make the menu unavailable. Keep the embed/text intact.
12161225
try:
12171226
closed_text = "This thread has been closed. Send a new message to start a new thread." # Grammar-friendly guidance
1218-
if msg.embeds:
1219-
emb = msg.embeds[0]
1220-
# Only overwrite if not already showing a closed message
1221-
if not (emb.description and "closed" in emb.description.lower()):
1222-
emb.description = closed_text
1223-
await msg.edit(embed=emb, view=None)
1224-
else:
1225-
# Overwrite content similarly
1226-
if not (msg.content and "closed" in msg.content.lower()):
1227-
await msg.edit(content=closed_text, view=None)
1228-
else:
1229-
await msg.edit(view=None)
1227+
closed_embed = discord.Embed(description=closed_text)
1228+
await msg.edit(content=None, embed=closed_embed, view=None)
12301229
except Exception:
1231-
# Fallback: at least remove interaction
1230+
# Fallback: at least remove interaction so menu cannot be used
12321231
try:
12331232
await msg.edit(view=None)
12341233
except Exception:
@@ -2809,23 +2808,37 @@ async def on_timeout(self):
28092808
embed_title = self.bot.config.get("thread_creation_menu_embed_title")
28102809
embed_footer = self.bot.config.get("thread_creation_menu_embed_footer")
28112810
embed_thumb = self.bot.config.get("thread_creation_menu_embed_thumbnail_url")
2811+
embed_image = self.bot.config.get("thread_creation_menu_embed_image_url")
28122812
embed_footer_icon = self.bot.config.get("thread_creation_menu_embed_footer_icon_url")
28132813
embed_color_raw = self.bot.config.get("thread_creation_menu_embed_color")
28142814
except Exception:
28152815
embed_title = None
28162816
embed_footer = None
28172817
embed_thumb = None
2818+
embed_image = None
28182819
embed_footer_icon = None
28192820
embed_color_raw = None
28202821
embed_color = embed_color_raw or self.bot.mod_color
28212822
embed = discord.Embed(title=embed_title, description=embed_text, color=embed_color)
28222823
if embed_footer:
2823-
embed.set_footer(text=embed_footer, icon_url=embed_footer_icon or discord.Embed.Empty)
2824-
if embed_thumb:
2824+
try:
2825+
if embed_footer_icon:
2826+
embed.set_footer(text=embed_footer, icon_url=embed_footer_icon)
2827+
else:
2828+
embed.set_footer(text=embed_footer)
2829+
except Exception as e:
2830+
logger.debug("Footer build failed (ignored): %s", e)
2831+
# Option A: prefer dedicated large image when provided
2832+
if embed_image:
2833+
try:
2834+
embed.set_image(url=embed_image)
2835+
except Exception as e:
2836+
logger.debug("Image set failed (ignored): %s", e)
2837+
elif embed_thumb:
28252838
try:
28262839
embed.set_thumbnail(url=embed_thumb)
2827-
except Exception:
2828-
pass
2840+
except Exception as e:
2841+
logger.debug("Thumbnail set failed (ignored): %s", e)
28292842
menu_view = _ThreadCreationMenuView(thread)
28302843
menu_msg = await recipient.send(embed=embed, view=menu_view)
28312844
# mark thread as pending menu selection
@@ -2841,9 +2854,10 @@ async def on_timeout(self):
28412854
thread._dm_menu_channel_id = menu_msg.channel.id
28422855
except Exception:
28432856
pass
2844-
except Exception:
2857+
except Exception as e:
28452858
logger.warning(
2846-
"Failed to send thread-creation menu DM, falling back to immediate thread creation."
2859+
"Failed to send thread-creation menu DM, falling back to immediate thread creation.",
2860+
exc_info=True,
28472861
)
28482862
self.bot.loop.create_task(
28492863
thread.setup(creator=creator, category=category, initial_message=message)
@@ -3005,23 +3019,41 @@ async def on_timeout(self):
30053019
embed_title = self.bot.config.get("thread_creation_menu_embed_title")
30063020
embed_footer = self.bot.config.get("thread_creation_menu_embed_footer")
30073021
embed_thumb = self.bot.config.get("thread_creation_menu_embed_thumbnail_url")
3022+
embed_image = self.bot.config.get("thread_creation_menu_embed_image_url")
3023+
embed_large = bool(self.bot.config.get("thread_creation_menu_embed_large_image"))
30083024
embed_footer_icon = self.bot.config.get("thread_creation_menu_embed_footer_icon_url")
30093025
embed_color_raw = self.bot.config.get("thread_creation_menu_embed_color")
30103026
except Exception:
30113027
embed_title = None
30123028
embed_footer = None
30133029
embed_thumb = None
3030+
embed_image = None
3031+
embed_large = False
30143032
embed_footer_icon = None
30153033
embed_color_raw = None
30163034
embed_color = embed_color_raw or self.bot.mod_color
30173035
embed = discord.Embed(title=embed_title, description=embed_text, color=embed_color)
30183036
if embed_footer:
3019-
embed.set_footer(text=embed_footer, icon_url=embed_footer_icon or discord.Embed.Empty)
3020-
if embed_thumb:
30213037
try:
3022-
embed.set_thumbnail(url=embed_thumb)
3023-
except Exception:
3024-
pass
3038+
if embed_footer_icon:
3039+
embed.set_footer(text=embed_footer, icon_url=embed_footer_icon)
3040+
else:
3041+
embed.set_footer(text=embed_footer)
3042+
except Exception as e:
3043+
logger.debug("Footer build failed (ignored precreate): %s", e)
3044+
if embed_image:
3045+
try:
3046+
embed.set_image(url=embed_image)
3047+
except Exception as e:
3048+
logger.debug("Image set failed (ignored precreate): %s", e)
3049+
elif embed_thumb:
3050+
try:
3051+
if embed_large:
3052+
embed.set_image(url=embed_thumb)
3053+
else:
3054+
embed.set_thumbnail(url=embed_thumb)
3055+
except Exception as e:
3056+
logger.debug("Thumbnail/image set failed (ignored precreate): %s", e)
30253057
menu_view = _PrecreateMenuView(thread)
30263058
# Send menu DM AFTER channel creation initiation (channel will be created below)
30273059
menu_msg = await recipient.send(embed=embed, view=menu_view)

0 commit comments

Comments
 (0)