Skip to content

Commit f46a668

Browse files
committed
fix: escape mentions on unsnooze
1 parent 0cb5150 commit f46a668

File tree

3 files changed

+66
-25
lines changed

3 files changed

+66
-25
lines changed

core/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ class ConfigManager:
143143
# snooze behavior
144144
"snooze_behavior": "delete", # 'delete' to delete channel, 'move' to move channel to snoozed_category_id
145145
"snoozed_category_id": None, # Category ID to move snoozed channels into when snooze_behavior == 'move'
146+
# attachments persistence for delete-behavior snooze
147+
"snooze_store_attachments": False, # when True, store image attachments as base64 in snooze_data
148+
"snooze_attachment_max_bytes": 4_194_304, # 4 MiB per attachment cap to avoid Mongo 16MB limit
146149
}
147150

148151
private_keys = {
@@ -242,6 +245,8 @@ class ConfigManager:
242245
"use_hoisted_top_role",
243246
"enable_presence_intent",
244247
"registry_plugins_only",
248+
# snooze
249+
"snooze_store_attachments",
245250
}
246251

247252
enums = {

core/config_help.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,5 +1345,29 @@
13451345
"Only used when `snooze_behavior` is 'move'.",
13461346
"If not set or invalid, the channel will remain in its current category or the bot will fall back to deleting on failure."
13471347
]
1348+
},
1349+
"snooze_store_attachments": {
1350+
"default": "No",
1351+
"description": "When enabled and `snooze_behavior` is 'delete', image attachments are stored as base64 within the snooze data so they can be re-uploaded on unsnooze, preserving media even if the original channel was deleted.",
1352+
"examples": [
1353+
"`{prefix}config set snooze_store_attachments yes`",
1354+
"`{prefix}config set snooze_store_attachments no`"
1355+
],
1356+
"notes": [
1357+
"Only applies to delete-behavior snoozes. In move-behavior, attachments remain in the channel history.",
1358+
"To avoid exceeding Mongo's 16MB document size, consider also adjusting `snooze_attachment_max_bytes`."
1359+
]
1360+
},
1361+
"snooze_attachment_max_bytes": {
1362+
"default": "4194304 (4 MiB)",
1363+
"description": "Maximum size per attachment to store as base64 when `snooze_store_attachments` is enabled.",
1364+
"examples": [
1365+
"`{prefix}config set snooze_attachment_max_bytes 2097152` (2 MiB)",
1366+
"`{prefix}config set snooze_attachment_max_bytes 0` (disable size check; not recommended)"
1367+
],
1368+
"notes": [
1369+
"This cap helps prevent hitting MongoDB's 16MB per-document limit when storing large attachments.",
1370+
"Non-image files are not stored as base64 and will be preserved as their original URLs if available."
1371+
]
13481372
}
13491373
}

core/thread.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
2-
import base64
32
import copy
3+
import base64
44
import functools
55
import io
66
import re
@@ -431,6 +431,14 @@ async def restore_from_snooze(self):
431431
else None
432432
),
433433
)
434+
# If there were attachment URLs, include them as a field so mods can access them
435+
if attachments:
436+
try:
437+
embeds[0].add_field(
438+
name="Attachments", value="\n".join(attachments), inline=False
439+
)
440+
except Exception:
441+
pass
434442
await channel.send(embeds=embeds, allowed_mentions=discord.AllowedMentions.none())
435443
else:
436444
# Build a non-empty message; include attachment URLs if no content
@@ -444,7 +452,9 @@ async def restore_from_snooze(self):
444452
await channel.send(formatted, allowed_mentions=discord.AllowedMentions.none())
445453
else:
446454
# Recipient message: include attachment URLs if content is empty
447-
content_to_send = content if content else ("\n".join(attachments) if attachments else None)
455+
content_to_send = (
456+
content if content else ("\n".join(attachments) if attachments else None)
457+
)
448458
await channel.send(
449459
content=content_to_send,
450460
embeds=embeds or None,
@@ -480,9 +490,7 @@ async def restore_from_snooze(self):
480490
notify_channel = self.bot.config.get("unsnooze_notify_channel") or "thread"
481491
notify_text = self.bot.config.get("unsnooze_text") or "This thread has been unsnoozed and restored."
482492
if notify_channel == "thread":
483-
await channel.send(
484-
notify_text, allowed_mentions=discord.AllowedMentions.none()
485-
)
493+
await channel.send(notify_text, allowed_mentions=discord.AllowedMentions.none())
486494
else:
487495
ch = self.bot.get_channel(int(notify_channel))
488496
if ch:
@@ -1720,32 +1728,33 @@ def lottie_to_png(data):
17201728

17211729
if plain:
17221730
if from_mod and not isinstance(destination, discord.TextChannel):
1723-
# Plain to user
1731+
# Plain to user (DM)
17241732
with warnings.catch_warnings():
1725-
# Catch coroutines not awaited warning
17261733
warnings.simplefilter("ignore")
17271734
additional_images = []
17281735

1729-
if embed.footer.text:
1730-
plain_message = f"**{embed.footer.text} "
1731-
else:
1732-
plain_message = "**"
1733-
plain_message += f"{embed.author.name}:** {embed.description}"
1736+
prefix = f"**{embed.footer.text} " if embed.footer and embed.footer.text else "**"
1737+
body = embed.description or ""
1738+
plain_message = f"{prefix}{embed.author.name}:** {body}"
1739+
17341740
files = []
1735-
for i in message.attachments:
1736-
files.append(await i.to_file())
1741+
for att in message.attachments:
1742+
try:
1743+
files.append(await att.to_file())
1744+
except Exception:
1745+
logger.warning("Failed to attach file in plain DM.", exc_info=True)
17371746

1738-
msg = await destination.send(plain_message, files=files)
1747+
msg = await destination.send(plain_message, files=files or None)
17391748
else:
17401749
# Plain to mods
1741-
embed.set_footer(text="[PLAIN] " + embed.footer.text)
1750+
footer_text = embed.footer.text if embed.footer else ""
1751+
embed.set_footer(text=f"[PLAIN] {footer_text}".strip())
17421752
msg = await destination.send(mentions, embed=embed)
17431753

17441754
else:
17451755
try:
17461756
msg = await destination.send(mentions, embed=embed)
17471757
except discord.NotFound:
1748-
# If channel vanished right before send, try to restore and resend once
17491758
if (
17501759
isinstance(destination, discord.TextChannel)
17511760
and (self.snoozed or self.snooze_data)
@@ -1768,16 +1777,19 @@ def lottie_to_png(data):
17681777

17691778
async def get_notifications(self) -> str:
17701779
key = str(self.id)
1771-
1772-
mentions = []
1773-
mentions.extend(self.bot.config["subscriptions"].get(key, []))
1774-
1775-
if key in self.bot.config["notification_squad"]:
1776-
mentions.extend(self.bot.config["notification_squad"][key])
1777-
self.bot.config["notification_squad"].pop(key)
1780+
mentions: typing.List[str] = []
1781+
subs = self.bot.config["subscriptions"].get(key, [])
1782+
mentions.extend(subs)
1783+
one_time = self.bot.config["notification_squad"].get(key, [])
1784+
mentions.extend(one_time)
1785+
1786+
if one_time:
1787+
self.bot.config["notification_squad"].pop(key, None)
17781788
self.bot.loop.create_task(self.bot.config.update())
17791789

1780-
return " ".join(set(mentions))
1790+
if not mentions:
1791+
return ""
1792+
return " ".join(list(dict.fromkeys(mentions)))
17811793

17821794
async def set_title(self, title: str) -> None:
17831795
topic = f"Title: {title}\n"

0 commit comments

Comments
 (0)