Skip to content

Commit 444d4aa

Browse files
authored
Fix: closing with timed words/ command in reply. (#3391)
* fix: ignore typing failures Make Modmail keep working when typing is disabled/outage * fix: only surpress failures * chore: sync local edits before push * Fix: closing with timed words/ command in reply. * Fix: typing in changelog command. * Fix: closing with timed words (additional)) * Fix changelog entry for command reply issue Corrected wording in the changelog entry regarding command inclusion in replies. Signed-off-by: lorenzo132 <[email protected]> * Update CHANGELOG for v4.2.0 enhancements Forwarded messages now display correctly in threads. Signed-off-by: lorenzo132 <[email protected]> --------- Signed-off-by: lorenzo132 <[email protected]>
1 parent cc4d17e commit 444d4aa

File tree

5 files changed

+129
-11
lines changed

5 files changed

+129
-11
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ however, insignificant breaking changes do not guarantee a major version bump, s
99
# v4.2.0
1010

1111
Upgraded discord.py to version 2.6.3, added support for CV2.
12+
Forwarded messages now properly show in threads, rather then showing as an empty embed.
1213

1314
### Fixed
1415
- Make Modmail keep working when typing is disabled due to a outage caused by Discord.
@@ -18,6 +19,8 @@ Upgraded discord.py to version 2.6.3, added support for CV2.
1819
- Eliminated duplicate logs and notes.
1920
- Addressed inconsistent use of `logkey` after ticket restoration.
2021
- Fixed issues with identifying the user who sent internal messages.
22+
- Solved an ancient bug where closing with words like `evening` wouldnt work.
23+
- Fixed the command from being included in the reply in rare conditions.
2124

2225
### Added
2326
Commands:

bot.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2035,4 +2035,3 @@ def main():
20352035

20362036
if __name__ == "__main__":
20372037
main()
2038-

cogs/utility.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,7 +1363,18 @@ async def permissions_add(
13631363
key = self.bot.modmail_guild.get_member(value)
13641364
if key is not None:
13651365
logger.info("Granting %s access to Modmail category.", key.name)
1366-
await self.bot.main_category.set_permissions(key, read_messages=True)
1366+
try:
1367+
await self.bot.main_category.set_permissions(key, read_messages=True)
1368+
except discord.Forbidden:
1369+
warn = discord.Embed(
1370+
title="Missing Permissions",
1371+
color=self.bot.error_color,
1372+
description=(
1373+
"I couldn't update the Modmail category permissions. "
1374+
"Please grant me 'Manage Channels' and 'Manage Roles' for this category."
1375+
),
1376+
)
1377+
await ctx.send(embed=warn)
13671378

13681379
embed = discord.Embed(
13691380
title="Success",
@@ -1454,17 +1465,50 @@ async def permissions_remove(
14541465
if level > PermissionLevel.REGULAR:
14551466
if value == -1:
14561467
logger.info("Denying @everyone access to Modmail category.")
1457-
await self.bot.main_category.set_permissions(
1458-
self.bot.modmail_guild.default_role, read_messages=False
1459-
)
1468+
try:
1469+
await self.bot.main_category.set_permissions(
1470+
self.bot.modmail_guild.default_role, read_messages=False
1471+
)
1472+
except discord.Forbidden:
1473+
warn = discord.Embed(
1474+
title="Missing Permissions",
1475+
color=self.bot.error_color,
1476+
description=(
1477+
"I couldn't update the Modmail category permissions. "
1478+
"Please grant me 'Manage Channels' and 'Manage Roles' for this category."
1479+
),
1480+
)
1481+
await ctx.send(embed=warn)
14601482
elif isinstance(user_or_role, discord.Role):
14611483
logger.info("Denying %s access to Modmail category.", user_or_role.name)
1462-
await self.bot.main_category.set_permissions(user_or_role, overwrite=None)
1484+
try:
1485+
await self.bot.main_category.set_permissions(user_or_role, overwrite=None)
1486+
except discord.Forbidden:
1487+
warn = discord.Embed(
1488+
title="Missing Permissions",
1489+
color=self.bot.error_color,
1490+
description=(
1491+
"I couldn't update the Modmail category permissions. "
1492+
"Please grant me 'Manage Channels' and 'Manage Roles' for this category."
1493+
),
1494+
)
1495+
await ctx.send(embed=warn)
14631496
else:
14641497
member = self.bot.modmail_guild.get_member(value)
14651498
if member is not None and member != self.bot.modmail_guild.me:
14661499
logger.info("Denying %s access to Modmail category.", member.name)
1467-
await self.bot.main_category.set_permissions(member, overwrite=None)
1500+
try:
1501+
await self.bot.main_category.set_permissions(member, overwrite=None)
1502+
except discord.Forbidden:
1503+
warn = discord.Embed(
1504+
title="Missing Permissions",
1505+
color=self.bot.error_color,
1506+
description=(
1507+
"I couldn't update the Modmail category permissions. "
1508+
"Please grant me 'Manage Channels' and 'Manage Roles' for this category."
1509+
),
1510+
)
1511+
await ctx.send(embed=warn)
14681512

14691513
embed = discord.Embed(
14701514
title="Success",

core/paginator.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,20 @@ async def run(self) -> typing.Optional[Message]:
156156
if not self.running:
157157
await self.show_page(self.current)
158158

159-
if self.view is not None:
160-
await self.view.wait()
161-
159+
# Don't block command execution while waiting for the View timeout.
160+
# Schedule the wait-and-close sequence in the background so the command
161+
# returns immediately (prevents typing indicator from hanging).
162+
if self.view is not None:
163+
164+
async def _wait_and_close():
165+
try:
166+
await self.view.wait()
167+
finally:
168+
await self.close(delete=False)
169+
170+
# Fire and forget
171+
self.ctx.bot.loop.create_task(_wait_and_close())
172+
else:
162173
await self.close(delete=False)
163174

164175
async def close(

core/time.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,30 @@ def __init__(self, dt: datetime.datetime, now: datetime.datetime = None):
160160
async def ensure_constraints(
161161
self, ctx: Context, uft: UserFriendlyTime, now: datetime.datetime, remaining: str
162162
) -> None:
163+
# Strip stray connector words like "in", "to", or "at" that may
164+
# remain when the natural language parser isolates the time token
165+
# positioned at the end (e.g. "in 10m" leaves "in" before the token).
166+
if isinstance(remaining, str):
167+
cleaned = remaining.strip(" ,.!")
168+
stray_tokens = {
169+
"in",
170+
"to",
171+
"at",
172+
"me",
173+
# also treat vague times of day as stray tokens when they are the only leftover word
174+
"evening",
175+
"night",
176+
"midnight",
177+
"morning",
178+
"afternoon",
179+
"tonight",
180+
"noon",
181+
"today",
182+
"tomorrow",
183+
}
184+
if cleaned.lower() in stray_tokens:
185+
remaining = ""
186+
163187
if self.dt < now:
164188
raise commands.BadArgument("This time is in the past.")
165189

@@ -199,6 +223,26 @@ async def convert(self, ctx: Context, argument: str, *, now=None) -> FriendlyTim
199223
if now is None:
200224
now = ctx.message.created_at
201225

226+
# Heuristic: If the user provides only certain single words that are commonly
227+
# used as salutations or vague times of day, interpret them as a message
228+
# rather than a schedule. This avoids accidental scheduling when the intent
229+
# is a short message (e.g. '?close evening'). Explicit scheduling still works
230+
# via 'in 2h', '2m30s', 'at 8pm', etc.
231+
if argument.strip().lower() in {
232+
"evening",
233+
"night",
234+
"midnight",
235+
"morning",
236+
"afternoon",
237+
"tonight",
238+
"noon",
239+
"today",
240+
"tomorrow",
241+
}:
242+
result = FriendlyTimeResult(now)
243+
await result.ensure_constraints(ctx, self, now, argument)
244+
return result
245+
202246
match = regex.match(argument)
203247
if match is not None and match.group(0):
204248
data = {k: int(v) for k, v in match.groupdict(default=0).items()}
@@ -245,7 +289,10 @@ async def convert(self, ctx: Context, argument: str, *, now=None) -> FriendlyTim
245289
if not status.hasDateOrTime:
246290
raise commands.BadArgument('Invalid time provided, try e.g. "tomorrow" or "3 days".')
247291

248-
if begin not in (0, 1) and end != len(argument):
292+
# If the parsed time token is embedded in the text but only followed by
293+
# trailing punctuation/whitespace, treat it as if it's positioned at the end.
294+
trailing = argument[end:].strip(" ,.!")
295+
if begin not in (0, 1) and trailing != "":
249296
raise commands.BadArgument(
250297
"Time is either in an inappropriate location, which "
251298
"must be either at the end or beginning of your input, "
@@ -260,6 +307,20 @@ async def convert(self, ctx: Context, argument: str, *, now=None) -> FriendlyTim
260307
if status.accuracy == pdt.pdtContext.ACU_HALFDAY:
261308
dt = dt.replace(day=now.day + 1)
262309

310+
# Heuristic: If the matched time string is a vague time-of-day (e.g.,
311+
# 'evening', 'morning', 'afternoon', 'night') and there's additional
312+
# non-punctuation text besides that token, assume the user intended a
313+
# closing message rather than scheduling. This avoids cases like
314+
# '?close Have a good evening!' being treated as a scheduled close.
315+
vague_tod = {"evening", "morning", "afternoon", "night"}
316+
matched_text = dt_string.strip().strip('"').rstrip(" ,.!").lower()
317+
pre_text = argument[:begin].strip(" ,.!")
318+
post_text = argument[end:].strip(" ,.!")
319+
if matched_text in vague_tod and (pre_text or post_text):
320+
result = FriendlyTimeResult(now)
321+
await result.ensure_constraints(ctx, self, now, argument)
322+
return result
323+
263324
result = FriendlyTimeResult(dt.replace(tzinfo=datetime.timezone.utc), now)
264325
remaining = ""
265326

0 commit comments

Comments
 (0)