Skip to content

Commit 20b31f8

Browse files
Jerrie-Ariesfourjr
authored andcommitted
Resolve linked messages issues, improve group functionality (bugfixes), resolves #3041
1 parent 3407e05 commit 20b31f8

File tree

4 files changed

+179
-83
lines changed

4 files changed

+179
-83
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ v3.10 adds group conversations while resolving othre bugs and QOL changes. It is
3030
- `close_on_leave_reason` now works properly when `close_on_leave` is enabled. ([GH #3075](https://github.com/kyb3r/modmail/issues/3075))
3131
- Invalid arguments are now properly catched and a proper error message is sent.
3232
- Update database after resetting/purging all plugins. ([GH #3011](https://github.com/kyb3r/modmail/pull/3011))
33+
- `thread_auto_close` timer now only resets on non-note and replies from mods. ([GH #3030](https://github.com/kyb3r/modmail/issues/3030))
3334

3435
## Internal
3536

Diff for: bot.py

+5-12
Original file line numberDiff line numberDiff line change
@@ -1265,7 +1265,7 @@ async def handle_reaction_events(self, payload):
12651265
if not thread.recipient.dm_channel:
12661266
await thread.recipient.create_dm()
12671267
try:
1268-
linked_message = await thread.find_linked_message_from_dm(message, either_direction=True)
1268+
linked_messages = await thread.find_linked_message_from_dm(message, either_direction=True)
12691269
except ValueError as e:
12701270
logger.warning("Failed to find linked message for reactions: %s", e)
12711271
return
@@ -1274,19 +1274,19 @@ async def handle_reaction_events(self, payload):
12741274
if not thread:
12751275
return
12761276
try:
1277-
_, *linked_message = await thread.find_linked_messages(message.id, either_direction=True)
1277+
_, *linked_messages = await thread.find_linked_messages(message.id, either_direction=True)
12781278
except ValueError as e:
12791279
logger.warning("Failed to find linked message for reactions: %s", e)
12801280
return
12811281

1282-
if self.config["transfer_reactions"] and linked_message is not [None]:
1282+
if self.config["transfer_reactions"] and linked_messages is not [None]:
12831283
if payload.event_type == "REACTION_ADD":
1284-
for msg in linked_message:
1284+
for msg in linked_messages:
12851285
await self.add_reaction(msg, reaction)
12861286
await self.add_reaction(message, reaction)
12871287
else:
12881288
try:
1289-
for msg in linked_message:
1289+
for msg in linked_messages:
12901290
await msg.remove_reaction(reaction, self.user)
12911291
await message.remove_reaction(reaction, self.user)
12921292
except (discord.HTTPException, discord.InvalidArgument) as e:
@@ -1432,13 +1432,6 @@ async def on_message_delete(self, message):
14321432
if not thread:
14331433
return
14341434

1435-
audit_logs = self.modmail_guild.audit_logs(limit=10, action=discord.AuditLogAction.message_delete)
1436-
1437-
entry = await audit_logs.find(lambda a: a.target == self.user)
1438-
1439-
if entry is None:
1440-
return
1441-
14421435
try:
14431436
await thread.delete_message(message, note=False)
14441437
embed = discord.Embed(description="Successfully deleted message.", color=self.main_color)

Diff for: core/thread.py

+99-66
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
match_user_id,
2222
match_other_recipients,
2323
truncate,
24-
format_channel_name,
2524
get_top_hoisted_role,
25+
create_thread_channel,
26+
get_joint_id,
2627
)
2728

2829
logger = getLogger(__name__)
@@ -36,7 +37,7 @@ def __init__(
3637
manager: "ThreadManager",
3738
recipient: typing.Union[discord.Member, discord.User, int],
3839
channel: typing.Union[discord.DMChannel, discord.TextChannel] = None,
39-
other_recipients: typing.List[typing.Union[discord.Member, discord.User]] = [],
40+
other_recipients: typing.List[typing.Union[discord.Member, discord.User]] = None,
4041
):
4142
self.manager = manager
4243
self.bot = manager.bot
@@ -48,7 +49,7 @@ def __init__(
4849
raise CommandError("Recipient cannot be a bot.")
4950
self._id = recipient.id
5051
self._recipient = recipient
51-
self._other_recipients = other_recipients
52+
self._other_recipients = other_recipients or []
5253
self._channel = channel
5354
self.genesis_message = None
5455
self._ready_event = asyncio.Event()
@@ -127,9 +128,13 @@ async def from_channel(cls, manager: "ThreadManager", channel: discord.TextChann
127128
else:
128129
recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user(recipient_id)
129130

130-
other_recipients = match_other_recipients(channel.topic)
131-
for n, uid in enumerate(other_recipients):
132-
other_recipients[n] = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid)
131+
other_recipients = []
132+
for uid in match_other_recipients(channel.topic):
133+
try:
134+
other_recipient = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid)
135+
except discord.NotFound:
136+
continue
137+
other_recipients.append(other_recipient)
133138

134139
thread = cls(manager, recipient or recipient_id, channel, other_recipients)
135140

@@ -149,33 +154,19 @@ async def setup(self, *, creator=None, category=None, initial_message=None):
149154
overwrites = None
150155

151156
try:
152-
channel = await self.bot.modmail_guild.create_text_channel(
153-
name=format_channel_name(self.bot, recipient),
154-
category=category,
155-
overwrites=overwrites,
156-
reason="Creating a thread channel.",
157-
)
158-
except discord.HTTPException as e:
159-
# try again but null-discrim (name could be banned)
160-
try:
161-
channel = await self.bot.modmail_guild.create_text_channel(
162-
name=format_channel_name(self.bot, recipient, force_null=True),
163-
category=category,
164-
overwrites=overwrites,
165-
reason="Creating a thread channel.",
166-
)
167-
except discord.HTTPException as e: # Failed to create due to missing perms.
168-
logger.critical("An error occurred while creating a thread.", exc_info=True)
169-
self.manager.cache.pop(self.id)
157+
channel = await create_thread_channel(self.bot, recipient, category, overwrites)
158+
except discord.HTTPException as e: # Failed to create due to missing perms.
159+
logger.critical("An error occurred while creating a thread.", exc_info=True)
160+
self.manager.cache.pop(self.id)
170161

171-
embed = discord.Embed(color=self.bot.error_color)
172-
embed.title = "Error while trying to create a thread."
173-
embed.description = str(e)
174-
embed.add_field(name="Recipient", value=recipient.mention)
162+
embed = discord.Embed(color=self.bot.error_color)
163+
embed.title = "Error while trying to create a thread."
164+
embed.description = str(e)
165+
embed.add_field(name="Recipient", value=recipient.mention)
175166

176-
if self.bot.log_channel is not None:
177-
await self.bot.log_channel.send(embed=embed)
178-
return
167+
if self.bot.log_channel is not None:
168+
await self.bot.log_channel.send(embed=embed)
169+
return
179170

180171
self._channel = channel
181172

@@ -209,7 +200,7 @@ async def send_genesis_message():
209200
logger.error("Failed unexpectedly:", exc_info=True)
210201

211202
async def send_recipient_genesis_message():
212-
# Once thread is ready, tell the recipient.
203+
# Once thread is ready, tell the recipient (don't send if using contact on others)
213204
thread_creation_response = self.bot.config["thread_creation_response"]
214205

215206
embed = discord.Embed(
@@ -585,7 +576,7 @@ async def find_linked_messages(
585576
either_direction: bool = False,
586577
message1: discord.Message = None,
587578
note: bool = True,
588-
) -> typing.Tuple[discord.Message, typing.Optional[discord.Message]]:
579+
) -> typing.Tuple[discord.Message, typing.List[typing.Optional[discord.Message]]]:
589580
if message1 is not None:
590581
if not message1.embeds or not message1.embeds[0].author.url or message1.author != self.bot.user:
591582
raise ValueError("Malformed thread message.")
@@ -698,49 +689,84 @@ async def delete_message(
698689
if tasks:
699690
await asyncio.gather(*tasks)
700691

701-
async def find_linked_message_from_dm(self, message, either_direction=False):
702-
if either_direction and message.embeds and message.embeds[0].author.url:
703-
compare_url = message.embeds[0].author.url
704-
compare_id = compare_url.split("#")[-1]
705-
else:
706-
compare_url = None
707-
compare_id = None
692+
async def find_linked_message_from_dm(
693+
self, message, either_direction=False
694+
) -> typing.List[discord.Message]:
695+
696+
joint_id = None
697+
if either_direction:
698+
joint_id = get_joint_id(message)
699+
# could be None too, if that's the case we'll reassign this variable from
700+
# thread message we fetch in the next step
708701

702+
linked_messages = []
709703
if self.channel is not None:
710-
async for linked_message in self.channel.history():
711-
if not linked_message.embeds:
704+
async for msg in self.channel.history():
705+
if not msg.embeds:
712706
continue
713-
url = linked_message.embeds[0].author.url
714-
if not url:
715-
continue
716-
if url == compare_url:
717-
return linked_message
718707

719-
msg_id = url.split("#")[-1]
720-
if not msg_id.isdigit():
708+
msg_joint_id = get_joint_id(msg)
709+
if msg_joint_id is None:
721710
continue
722-
msg_id = int(msg_id)
723-
if int(msg_id) == message.id:
724-
return linked_message
725711

726-
if compare_id is not None and compare_id.isdigit():
727-
if int(msg_id) == int(compare_id):
728-
return linked_message
712+
if msg_joint_id == message.id:
713+
linked_messages.append(msg)
714+
break
729715

716+
if joint_id is not None and msg_joint_id == joint_id:
717+
linked_messages.append(msg)
718+
break
719+
else:
720+
raise ValueError("Thread channel message not found.")
721+
else:
730722
raise ValueError("Thread channel message not found.")
731723

724+
if joint_id is None:
725+
joint_id = get_joint_id(linked_messages[0])
726+
if joint_id is None:
727+
# still None, supress this and return the thread message
728+
logger.error("Malformed thread message.")
729+
return linked_messages
730+
731+
for user in self.recipients:
732+
if user.dm_channel == message.channel:
733+
continue
734+
async for other_msg in user.history():
735+
if either_direction:
736+
if other_msg.id == joint_id:
737+
linked_messages.append(other_msg)
738+
break
739+
740+
if not other_msg.embeds:
741+
continue
742+
743+
other_joint_id = get_joint_id(other_msg)
744+
if other_joint_id is not None and other_joint_id == joint_id:
745+
linked_messages.append(other_msg)
746+
break
747+
else:
748+
logger.error("Linked message from recipient %s not found.", user)
749+
750+
return linked_messages
751+
732752
async def edit_dm_message(self, message: discord.Message, content: str) -> None:
733753
try:
734-
linked_message = await self.find_linked_message_from_dm(message)
754+
linked_messages = await self.find_linked_message_from_dm(message)
735755
except ValueError:
736756
logger.warning("Failed to edit message.", exc_info=True)
737757
raise
738-
embed = linked_message.embeds[0]
739-
embed.add_field(name="**Edited, former message:**", value=embed.description)
740-
embed.description = content
741-
await asyncio.gather(self.bot.api.edit_message(message.id, content), linked_message.edit(embed=embed))
742758

743-
async def note(self, message: discord.Message, persistent=False, thread_creation=False) -> None:
759+
for msg in linked_messages:
760+
embed = msg.embeds[0]
761+
if isinstance(msg.channel, discord.TextChannel):
762+
# just for thread channel, we put the old message in embed field
763+
embed.add_field(name="**Edited, former message:**", value=embed.description)
764+
embed.description = content
765+
await asyncio.gather(self.bot.api.edit_message(message.id, content), msg.edit(embed=embed))
766+
767+
async def note(
768+
self, message: discord.Message, persistent=False, thread_creation=False
769+
) -> discord.Message:
744770
if not message.content and not message.attachments:
745771
raise MissingRequiredArgument(SimpleNamespace(name="msg"))
746772

@@ -758,7 +784,9 @@ async def note(self, message: discord.Message, persistent=False, thread_creation
758784

759785
return msg
760786

761-
async def reply(self, message: discord.Message, anonymous: bool = False, plain: bool = False) -> None:
787+
async def reply(
788+
self, message: discord.Message, anonymous: bool = False, plain: bool = False
789+
) -> typing.Tuple[discord.Message, discord.Message]:
762790
if not message.content and not message.attachments:
763791
raise MissingRequiredArgument(SimpleNamespace(name="msg"))
764792
if not any(g.get_member(self.id) for g in self.bot.guilds):
@@ -854,7 +882,8 @@ async def send(
854882
thread_creation: bool = False,
855883
) -> None:
856884

857-
self.bot.loop.create_task(self._restart_close_timer()) # Start or restart thread auto close
885+
if not note and from_mod:
886+
self.bot.loop.create_task(self._restart_close_timer()) # Start or restart thread auto close
858887

859888
if self.close_task is not None:
860889
# cancel closing if a thread message is sent.
@@ -1208,9 +1237,13 @@ async def _find_from_channel(self, channel):
12081237
except discord.NotFound:
12091238
recipient = None
12101239

1211-
other_recipients = match_other_recipients(channel.topic)
1212-
for n, uid in enumerate(other_recipients):
1213-
other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid)
1240+
other_recipients = []
1241+
for uid in match_other_recipients(channel.topic):
1242+
try:
1243+
other_recipient = self.bot.get_user(uid) or await self.bot.fetch_user(uid)
1244+
except discord.NotFound:
1245+
continue
1246+
other_recipients.append(other_recipient)
12141247

12151248
if recipient is None:
12161249
thread = Thread(self, user_id, channel, other_recipients)

0 commit comments

Comments
 (0)