Skip to content

Commit 3f393e0

Browse files
committed
New regex to properly match/parse channel topics.
1 parent fc156f9 commit 3f393e0

File tree

3 files changed

+91
-34
lines changed

3 files changed

+91
-34
lines changed

cogs/modmail.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1848,7 +1848,7 @@ async def repair(self, ctx):
18481848
and message.embeds[0].color.value == self.bot.main_color
18491849
and message.embeds[0].footer.text
18501850
):
1851-
user_id = match_user_id(message.embeds[0].footer.text)
1851+
user_id = match_user_id(message.embeds[0].footer.text, any_string=True)
18521852
other_recipients = match_other_recipients(ctx.channel.topic)
18531853
for n, uid in enumerate(other_recipients):
18541854
other_recipients[n] = self.bot.get_user(uid) or await self.bot.fetch_user(uid)

core/thread.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
from core.utils import (
1818
is_image_url,
1919
days,
20+
parse_channel_topic,
2021
match_title,
2122
match_user_id,
22-
match_other_recipients,
2323
truncate,
2424
get_top_hoisted_role,
2525
create_thread_channel,
@@ -119,17 +119,16 @@ def cancelled(self, flag: bool):
119119

120120
@classmethod
121121
async def from_channel(cls, manager: "ThreadManager", channel: discord.TextChannel) -> "Thread":
122-
recipient_id = match_user_id(
123-
channel.topic
124-
) # there is a chance it grabs from another recipient's main thread
122+
# there is a chance it grabs from another recipient's main thread
123+
_, recipient_id, other_ids = parse_channel_topic(channel.topic)
125124

126125
if recipient_id in manager.cache:
127126
thread = manager.cache[recipient_id]
128127
else:
129128
recipient = manager.bot.get_user(recipient_id) or await manager.bot.fetch_user(recipient_id)
130129

131130
other_recipients = []
132-
for uid in match_other_recipients(channel.topic):
131+
for uid in other_ids:
133132
try:
134133
other_recipient = manager.bot.get_user(uid) or await manager.bot.fetch_user(uid)
135134
except discord.NotFound:
@@ -1162,23 +1161,31 @@ async def _update_users_genesis(self):
11621161
await genesis_message.edit(embed=embed)
11631162

11641163
async def add_users(self, users: typing.List[typing.Union[discord.Member, discord.User]]) -> None:
1165-
title = match_title(self.channel.topic)
1166-
user_id = match_user_id(self.channel.topic)
1164+
title, user_id, _ = parse_channel_topic(self.channel.topic)
1165+
if title is not None:
1166+
title = f"Title: {title}\n"
1167+
else:
1168+
title = ""
1169+
11671170
self._other_recipients += users
11681171

11691172
ids = ",".join(str(i.id) for i in self._other_recipients)
1170-
await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}")
1173+
await self.channel.edit(topic=f"{title}User ID: {user_id}\nOther Recipients: {ids}")
11711174

11721175
await self._update_users_genesis()
11731176

11741177
async def remove_users(self, users: typing.List[typing.Union[discord.Member, discord.User]]) -> None:
1175-
title = match_title(self.channel.topic)
1176-
user_id = match_user_id(self.channel.topic)
1178+
title, user_id, _ = parse_channel_topic(self.channel.topic)
1179+
if title is not None:
1180+
title = f"Title: {title}\n"
1181+
else:
1182+
title = ""
1183+
11771184
for u in users:
11781185
self._other_recipients.remove(u)
11791186

11801187
ids = ",".join(str(i.id) for i in self._other_recipients)
1181-
await self.channel.edit(topic=f"Title: {title}\nUser ID: {user_id}\nOther Recipients: {ids}")
1188+
await self.channel.edit(topic=f"{title}User ID: {user_id}\nOther Recipients: {ids}")
11821189

11831190
await self._update_users_genesis()
11841191

@@ -1240,16 +1247,24 @@ async def find(
12401247
await thread.close(closer=self.bot.user, silent=True, delete_channel=False)
12411248
thread = None
12421249
else:
1250+
1251+
def check(topic):
1252+
_, user_id, other_ids = parse_channel_topic(topic)
1253+
return recipient_id == user_id or recipient_id in other_ids
1254+
12431255
channel = discord.utils.find(
1244-
lambda x: str(recipient_id) in x.topic if x.topic else False,
1256+
lambda x: (check(x.topic)) if x.topic else False,
12451257
self.bot.modmail_guild.text_channels,
12461258
)
12471259

12481260
if channel:
12491261
thread = await Thread.from_channel(self, channel)
12501262
if thread.recipient:
1251-
# only save if data is valid
1252-
self.cache[recipient_id] = thread
1263+
# only save if data is valid.
1264+
# also the recipient_id here could belong to other recipient,
1265+
# it would be wrong if we set it as the dict key,
1266+
# so we use the thread id instead
1267+
self.cache[thread.id] = thread
12531268
thread.ready = True
12541269

12551270
if thread and recipient_id not in [x.id for x in thread.recipients]:
@@ -1265,10 +1280,11 @@ async def _find_from_channel(self, channel):
12651280
searching channel history for genesis embed and
12661281
extracts user_id from that.
12671282
"""
1268-
user_id = -1
12691283

1270-
if channel.topic:
1271-
user_id = match_user_id(channel.topic)
1284+
if not channel.topic:
1285+
return None
1286+
1287+
_, user_id, other_ids = parse_channel_topic(channel.topic)
12721288

12731289
if user_id == -1:
12741290
return None
@@ -1282,7 +1298,7 @@ async def _find_from_channel(self, channel):
12821298
recipient = None
12831299

12841300
other_recipients = []
1285-
for uid in match_other_recipients(channel.topic):
1301+
for uid in other_ids:
12861302
try:
12871303
other_recipient = self.bot.get_user(uid) or await self.bot.fetch_user(uid)
12881304
except discord.NotFound:

core/utils.py

+56-15
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
"human_join",
2121
"days",
2222
"cleanup_code",
23+
"parse_channel_topic",
2324
"match_title",
2425
"match_user_id",
2526
"match_other_recipients",
27+
"create_thread_channel",
2628
"create_not_found_embed",
2729
"parse_alias",
2830
"normalize_alias",
@@ -218,9 +220,45 @@ def cleanup_code(content: str) -> str:
218220
return content.strip("` \n")
219221

220222

221-
TOPIC_OTHER_RECIPIENTS_REGEX = re.compile(r"Other Recipients:\s*((?:\d{17,21},*)+)", flags=re.IGNORECASE)
222-
TOPIC_TITLE_REGEX = re.compile(r"\bTitle: (.*)\n(?:User ID: )\b", flags=re.IGNORECASE | re.DOTALL)
223-
TOPIC_UID_REGEX = re.compile(r"\bUser ID:\s*(\d{17,21})\b", flags=re.IGNORECASE)
223+
TOPIC_REGEX = re.compile(
224+
r"\b(Title: (?P<title>.*)\n)?"
225+
r"\bUser ID:\s*(?P<user_id>\d{17,21})\b"
226+
r"(\nOther Recipients:\s*(?P<other_ids>(\d{17,21},?)+))?",
227+
flags=re.IGNORECASE | re.DOTALL,
228+
)
229+
UID_REGEX = re.compile(r"\bUser ID:\s*(\d{17,21})\b", flags=re.IGNORECASE)
230+
231+
232+
def parse_channel_topic(text: str) -> typing.Tuple[typing.Optional[str], int, typing.List[int]]:
233+
"""
234+
A helper to parse channel topics and respectivefully returns all the required values
235+
at once.
236+
237+
Parameters
238+
----------
239+
text : str
240+
The text of channel topic.
241+
242+
Returns
243+
-------
244+
Tuple[Optional[str], int, List[int]]
245+
A tuple of title, user ID, and other recipients IDs.
246+
"""
247+
title, user_id, other_ids = None, -1, []
248+
match = TOPIC_REGEX.search(text)
249+
if match is not None:
250+
groupdict = match.groupdict()
251+
title = groupdict["title"]
252+
253+
# user ID string is the required one in regex, so if match is found
254+
# the value of this won't be None
255+
user_id = int(groupdict["user_id"])
256+
257+
oth_ids = groupdict["other_ids"]
258+
if oth_ids:
259+
other_ids = list(map(int, oth_ids.split(",")))
260+
261+
return title, user_id, other_ids
224262

225263

226264
def match_title(text: str) -> str:
@@ -237,29 +275,35 @@ def match_title(text: str) -> str:
237275
Optional[str]
238276
The title if found.
239277
"""
240-
match = TOPIC_TITLE_REGEX.search(text)
241-
if match is not None:
242-
return match.group(1)
278+
return parse_channel_topic(text)[0]
243279

244280

245-
def match_user_id(text: str) -> int:
281+
def match_user_id(text: str, any_string: bool = False) -> int:
246282
"""
247283
Matches a user ID in the format of "User ID: 12345".
248284
249285
Parameters
250286
----------
251287
text : str
252288
The text of the user ID.
289+
any_string: bool
290+
Whether to search any string that matches the UID_REGEX, e.g. not from channel topic.
291+
Defaults to False.
253292
254293
Returns
255294
-------
256295
int
257296
The user ID if found. Otherwise, -1.
258297
"""
259-
match = TOPIC_UID_REGEX.search(text)
260-
if match is not None:
261-
return int(match.group(1))
262-
return -1
298+
user_id = -1
299+
if any_string:
300+
match = UID_REGEX.search(text)
301+
if match is not None:
302+
user_id = int(match.group(1))
303+
else:
304+
user_id = parse_channel_topic(text)[1]
305+
306+
return user_id
263307

264308

265309
def match_other_recipients(text: str) -> typing.List[int]:
@@ -276,10 +320,7 @@ def match_other_recipients(text: str) -> typing.List[int]:
276320
List[int]
277321
The list of other recipients IDs.
278322
"""
279-
match = TOPIC_OTHER_RECIPIENTS_REGEX.search(text)
280-
if match is not None:
281-
return list(map(int, match.group(1).split(",")))
282-
return []
323+
return parse_channel_topic(text)[2]
283324

284325

285326
def create_not_found_embed(word, possibilities, name, n=2, cutoff=0.6) -> discord.Embed:

0 commit comments

Comments
 (0)