diff --git a/CHANGELOG.md b/CHANGELOG.md
index 688462f439..4ec54e0f8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
however, insignificant breaking changes do not guarantee a major version bump, see the reasoning [here](https://github.com/modmail-dev/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section.
+# v4.1.2
+
+### Fixed
+- Members not caching correctly for large servers. ([PR #3365](https://github.com/modmail-dev/Modmail/pull/3365))
+
# v4.1.1
### Fixed
diff --git a/README.md b/README.md
index eef9e3f940..2e3d32e344 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
-
+
diff --git a/bot.py b/bot.py
index 3c6ebe7911..3f13ef7ced 100644
--- a/bot.py
+++ b/bot.py
@@ -1,4 +1,4 @@
-__version__ = "4.1.1"
+__version__ = "4.1.2"
import asyncio
@@ -640,6 +640,16 @@ async def get_or_fetch_user(self, id: int) -> discord.User:
"""
return self.get_user(id) or await self.fetch_user(id)
+ @staticmethod
+ async def get_or_fetch_member(guild: discord.Guild, member_id: int) -> typing.Optional[discord.Member]:
+ """
+ Attempt to get a member from cache; on failure fetch from the API.
+
+ Returns:
+ The :obj:`discord.Member` or :obj:`None` to indicate the member could not be found.
+ """
+ return guild.get_member(member_id) or await guild.fetch_member(member_id)
+
async def retrieve_emoji(self) -> typing.Tuple[str, str]:
sent_emoji = self.config["sent_emoji"]
blocked_emoji = self.config["blocked_emoji"]
diff --git a/core/thread.py b/core/thread.py
index 81dc03f44d..00060ab7f5 100644
--- a/core/thread.py
+++ b/core/thread.py
@@ -823,7 +823,13 @@ async def reply(
"""Returns List[user_dm_msg] and thread_channel_msg"""
if not message.content and not message.attachments and not message.stickers:
raise MissingRequiredArgument(DummyParam("msg"))
- if not any(g.get_member(self.id) for g in self.bot.guilds):
+ for guild in self.bot.guilds:
+ try:
+ if await self.bot.get_or_fetch_member(guild, self.id):
+ break
+ except discord.NotFound:
+ pass
+ else:
return await message.channel.send(
embed=discord.Embed(
color=self.bot.error_color,
@@ -995,7 +1001,7 @@ async def send(
attachments.append(attachment)
image_urls = re.findall(
- r"http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
+ r"http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$\-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
message.content,
)
diff --git a/pyproject.toml b/pyproject.toml
index 1a5ed16a3b..7e29a4d4ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ extend-exclude = '''
[tool.poetry]
name = 'Modmail'
-version = '4.1.1'
+version = '4.1.2'
description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way."
license = 'AGPL-3.0-only'
authors = [