From c5c3e7c25b8bedd879542a2140581f3f1a654e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Sat, 5 Jul 2025 11:54:20 +0200 Subject: [PATCH 1/7] feat: thread_min_characters config. Added - Config option `thread_min_characters` to require a minimum number of characters in the initial message to create a thread. - Configurable error message options: `thread_min_characters_title`, `thread_min_characters_response`, and `thread_min_characters_footer`. --- cogs/modmail.py | 1 + core/config.py | 5 ++++ core/config_help.json | 61 ++++++++++++++++++++++++++++++++++++------- core/thread.py | 21 +++++++++++++++ 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 13b9f94d24..f91ef8d36a 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1637,6 +1637,7 @@ async def contact( creator=creator, category=category, manual_trigger=manual_trigger, + # The minimum character check is enforced in ThreadManager.create ) if thread.cancelled: diff --git a/core/config.py b/core/config.py index 5c6b0dd09d..87cef553c4 100644 --- a/core/config.py +++ b/core/config.py @@ -129,6 +129,11 @@ class ConfigManager: # regex "use_regex_autotrigger": False, "use_hoisted_top_role": True, + # Minimum characters for thread creation + "thread_min_characters": 0, + "thread_min_characters_title": "Message too short", + "thread_min_characters_response": "Your message is too short to create a thread. Please provide more details.", + "thread_min_characters_footer": "Minimum {min_characters} characters required.", } private_keys = { diff --git a/core/config_help.json b/core/config_help.json index d301763fe4..c12d03d883 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -12,7 +12,7 @@ }, "main_category_id": { "default": "`Modmail` (created with `{prefix}setup`)", - "description": "This is the category where all new threads will be created.\n\nTo change the Modmail category, you will need to find the [category’s ID](https://support.discordapp.com/hc/en-us/articles/206346498).", + "description": "This is the category where all new threads will be created.\n\nTo change the Modmail category, you will need to find the [category's ID](https://support.discordapp.com/hc/en-us/articles/206346498).", "examples": [ "`{prefix}config set main_category_id 9234932582312` (`9234932582312` is the category ID)" ], @@ -24,7 +24,7 @@ }, "fallback_category_id": { "default": "`Fallback Modmail` (created when the main category is full)", - "description": "This is the category that will hold the threads when the main category is full.\n\nTo change the Fallback category, you will need to find the [category’s ID](https://support.discordapp.com/hc/en-us/articles/206346498).", + "description": "This is the category that will hold the threads when the main category is full.\n\nTo change the Fallback category, you will need to find the [category's ID](https://support.discordapp.com/hc/en-us/articles/206346498).", "examples": [ "`{prefix}config set fallback_category_id 9234932582312` (`9234932582312` is the category ID)" ], @@ -88,7 +88,7 @@ }, "user_typing": { "default": "Enabled", - "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see “{bot.user.display_name} is typing…” in the thread channel.", + "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see "{bot.user.display_name} is typing..." in the thread channel.", "examples": [ "`{prefix}config set user_typing yes`", "`{prefix}config set user_typing no`" @@ -151,7 +151,7 @@ }, "mod_typing": { "default": "Disabled", - "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see \"{bot.user.display_name} is typing…\" in their DM channel.", + "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see "{bot.user.display_name} is typing..." in their DM channel.", "examples": [ "`{prefix}config set mod_typing yes`", "`{prefix}config set mod_typing no`" @@ -219,7 +219,7 @@ }, "log_channel_id": { "default": "`#bot-logs` (created with `{prefix}setup`)", - "description": "This is the channel where all log messages will be sent (ie. thread close message, update message, etc.).\n\nTo change the log channel, you will need to find the [channel’s ID](https://support.discordapp.com/hc/en-us/articles/206346498). The channel doesn’t necessary have to be under the `main_category`.", + "description": "This is the channel where all log messages will be sent (ie. thread close message, update message, etc.).\n\nTo change the log channel, you will need to find the [channel's ID](https://support.discordapp.com/hc/en-us/articles/206346498). The channel doesn't necessary have to be under the `main_category`.", "examples": [ "`{prefix}config set log_channel_id 9234932582312` (9234932582312 is the channel ID)" ], @@ -704,7 +704,7 @@ }, "mod_tag": { "default": "The moderator's highest role", - "description": "This is the name tag in the “footer” section of the embeds sent by moderators in the recipient DM and thread channel.", + "description": "This is the name tag in the "footer" section of the embeds sent by moderators in the recipient DM and thread channel.", "examples": [ "`{prefix}config set mod_tag Moderator`" ], @@ -715,7 +715,7 @@ }, "anon_username": { "default": "Fallback on `mod_tag`", - "description": "This is the name in the “author” section of the embeds sent by anonymous moderators in the recipient DM.", + "description": "This is the name in the "author" section of the embeds sent by anonymous moderators in the recipient DM.", "examples": [ "`{prefix}config set anon_username Incognito Mod`" ], @@ -737,7 +737,7 @@ }, "anon_tag": { "default": "\"Response\"", - "description": "This is the name tag in the “footer” section of the embeds sent by anonymous moderators in the recipient DM.", + "description": "This is the name tag in the "footer" section of the embeds sent by anonymous moderators in the recipient DM.", "examples": [ "`{prefix}config set anon_tag Support Agent`" ], @@ -976,7 +976,7 @@ ], "notes": [ "The private_ variant is used when sending to the new user.", - "See also: `private_removed_from_group_title`, `private_removed_from_group_title`" + "See also: `private_removed_from_group_title`, `private_removed_from_group_title` ] }, "public_removed_from_group_response": { @@ -1222,5 +1222,48 @@ "If this configuration is enabled, only roles that are hoisted (displayed seperately in member list) will be used. If a user has no hoisted roles, it will return 'None'.", "If you would like to display the top role of a user regardless of if it's hoisted or not, disable `use_hoisted_top_role`." ] + }, + "thread_min_characters": { + "default": "0", + "description": "The minimum number of characters required in the initial message to create a thread. Set to 0 to disable.", + "examples": [ + "`{prefix}config set thread_min_characters 20`" + ], + "notes": [ + "If a user tries to create a thread with a message shorter than this, an error will be shown.", + "See also: `thread_min_characters_title`, `thread_min_characters_response`, `thread_min_characters_footer`." + ] + }, + "thread_min_characters_title": { + "default": "Message too short", + "description": "The title of the error embed when a user tries to create a thread with too few characters.", + "examples": [ + "`{prefix}config set thread_min_characters_title Too short!`" + ], + "notes": [ + "See also: `thread_min_characters`, `thread_min_characters_response`, `thread_min_characters_footer`." + ] + }, + "thread_min_characters_response": { + "default": "Your message is too short to create a thread. Please provide more details.", + "description": "The description of the error embed when a user tries to create a thread with too few characters.", + "examples": [ + "`{prefix}config set thread_min_characters_response Please write a longer message.`" + ], + "notes": [ + "You can use `{min_characters}` as a placeholder for the minimum required characters.", + "See also: `thread_min_characters`, `thread_min_characters_title`, `thread_min_characters_footer`." + ] + }, + "thread_min_characters_footer": { + "default": "Minimum {min_characters} characters required.", + "description": "The footer of the error embed when a user tries to create a thread with too few characters.", + "examples": [ + "`{prefix}config set thread_min_characters_footer At least {min_characters} characters needed.`" + ], + "notes": [ + "You can use `{min_characters}` as a placeholder for the minimum required characters.", + "See also: `thread_min_characters`, `thread_min_characters_title`, `thread_min_characters_response`." + ] } } diff --git a/core/thread.py b/core/thread.py index f5ccfedffd..6ecba3af8e 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1406,6 +1406,27 @@ async def create( ) -> Thread: """Creates a Modmail thread""" + # Minimum character check + min_chars = self.bot.config.get("thread_min_characters") + if min_chars is None: + min_chars = 0 + try: + min_chars = int(min_chars) + except Exception: + min_chars = 0 + if min_chars > 0 and message is not None and message.content is not None: + if len(message.content.strip()) < min_chars: + embed = discord.Embed( + title=self.bot.config.get("thread_min_characters_title", "Message too short"), + description=self.bot.config.get("thread_min_characters_response", "Your message is too short to create a thread. Please provide more details.").replace("{min_characters}", str(min_chars)), + color=self.bot.error_color, + ) + embed.set_footer(text=self.bot.config.get("thread_min_characters_footer", "Minimum {min_characters} characters required.").replace("{min_characters}", str(min_chars))) + await message.channel.send(embed=embed) + thread = Thread(self, recipient) + thread.cancelled = True + return thread + # checks for existing thread in cache thread = self.cache.get(recipient.id) if thread: From 746e1159ddcbda0d34b7b12d44b52fb903cd5485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Sat, 5 Jul 2025 12:04:54 +0200 Subject: [PATCH 2/7] fix: syntax --- core/config_help.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/config_help.json b/core/config_help.json index c12d03d883..de7950b158 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -88,7 +88,7 @@ }, "user_typing": { "default": "Enabled", - "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see "{bot.user.display_name} is typing..." in the thread channel.", + "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see \"{bot.user.display_name} is typing...\" in the thread channel.", "examples": [ "`{prefix}config set user_typing yes`", "`{prefix}config set user_typing no`" @@ -151,7 +151,7 @@ }, "mod_typing": { "default": "Disabled", - "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see "{bot.user.display_name} is typing..." in their DM channel.", + "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see \"{bot.user.display_name} is typing...\" in their DM channel.", "examples": [ "`{prefix}config set mod_typing yes`", "`{prefix}config set mod_typing no`" @@ -904,7 +904,7 @@ ], "notes": [ "The private_ variant is used when sending to the new user.", - "See also: `private_added_to_group_title`, `private_added_to_group_title`" + "See also: `private_added_to_group_title`, `private_added_to_group_title` ] }, "public_added_to_group_response": { From c0ed9d24ebdbf57b9ae9f995579944f0dc3c51f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Sat, 5 Jul 2025 12:06:14 +0200 Subject: [PATCH 3/7] Update config_help.json --- core/config_help.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/config_help.json b/core/config_help.json index de7950b158..b6b1367621 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -88,7 +88,7 @@ }, "user_typing": { "default": "Enabled", - "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see \"{bot.user.display_name} is typing...\" in the thread channel.", + "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see {bot.user.display_name} is typing... in the thread channel.", "examples": [ "`{prefix}config set user_typing yes`", "`{prefix}config set user_typing no`" @@ -151,7 +151,7 @@ }, "mod_typing": { "default": "Disabled", - "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see \"{bot.user.display_name} is typing...\" in their DM channel.", + "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see {bot.user.display_name} is typing... in their DM channel.", "examples": [ "`{prefix}config set mod_typing yes`", "`{prefix}config set mod_typing no`" From efc37fd8586bbb8cd603cd9694864dc3f4175328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Sat, 5 Jul 2025 12:10:28 +0200 Subject: [PATCH 4/7] fix: syntax --- core/config_help.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/config_help.json b/core/config_help.json index b6b1367621..6547c85ebd 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -12,7 +12,7 @@ }, "main_category_id": { "default": "`Modmail` (created with `{prefix}setup`)", - "description": "This is the category where all new threads will be created.\n\nTo change the Modmail category, you will need to find the [category's ID](https://support.discordapp.com/hc/en-us/articles/206346498).", + "description": "This is the category where all new threads will be created.\n\nTo change the Modmail category, you will need to find the [category’s ID](https://support.discordapp.com/hc/en-us/articles/206346498).", "examples": [ "`{prefix}config set main_category_id 9234932582312` (`9234932582312` is the category ID)" ], @@ -24,7 +24,7 @@ }, "fallback_category_id": { "default": "`Fallback Modmail` (created when the main category is full)", - "description": "This is the category that will hold the threads when the main category is full.\n\nTo change the Fallback category, you will need to find the [category's ID](https://support.discordapp.com/hc/en-us/articles/206346498).", + "description": "This is the category that will hold the threads when the main category is full.\n\nTo change the Fallback category, you will need to find the [category’s ID](https://support.discordapp.com/hc/en-us/articles/206346498).", "examples": [ "`{prefix}config set fallback_category_id 9234932582312` (`9234932582312` is the category ID)" ], @@ -88,7 +88,7 @@ }, "user_typing": { "default": "Enabled", - "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see {bot.user.display_name} is typing... in the thread channel.", + "description": "When this is set to `yes`, whenever the recipient user starts to type in their DM channel, the moderator will see “{bot.user.display_name} is typing…” in the thread channel.", "examples": [ "`{prefix}config set user_typing yes`", "`{prefix}config set user_typing no`" @@ -151,7 +151,7 @@ }, "mod_typing": { "default": "Disabled", - "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see {bot.user.display_name} is typing... in their DM channel.", + "description": "When this is set to `yes`, whenever a moderator starts to type in the thread channel, the recipient user will see \"{bot.user.display_name} is typing…\" in their DM channel.", "examples": [ "`{prefix}config set mod_typing yes`", "`{prefix}config set mod_typing no`" @@ -219,7 +219,7 @@ }, "log_channel_id": { "default": "`#bot-logs` (created with `{prefix}setup`)", - "description": "This is the channel where all log messages will be sent (ie. thread close message, update message, etc.).\n\nTo change the log channel, you will need to find the [channel's ID](https://support.discordapp.com/hc/en-us/articles/206346498). The channel doesn't necessary have to be under the `main_category`.", + "description": "This is the channel where all log messages will be sent (ie. thread close message, update message, etc.).\n\nTo change the log channel, you will need to find the [channel’s ID](https://support.discordapp.com/hc/en-us/articles/206346498). The channel doesn’t necessary have to be under the `main_category`.", "examples": [ "`{prefix}config set log_channel_id 9234932582312` (9234932582312 is the channel ID)" ], @@ -704,7 +704,7 @@ }, "mod_tag": { "default": "The moderator's highest role", - "description": "This is the name tag in the "footer" section of the embeds sent by moderators in the recipient DM and thread channel.", + "description": "This is the name tag in the “footer” section of the embeds sent by moderators in the recipient DM and thread channel.", "examples": [ "`{prefix}config set mod_tag Moderator`" ], @@ -715,7 +715,7 @@ }, "anon_username": { "default": "Fallback on `mod_tag`", - "description": "This is the name in the "author" section of the embeds sent by anonymous moderators in the recipient DM.", + "description": "This is the name in the “author” section of the embeds sent by anonymous moderators in the recipient DM.", "examples": [ "`{prefix}config set anon_username Incognito Mod`" ], @@ -737,7 +737,7 @@ }, "anon_tag": { "default": "\"Response\"", - "description": "This is the name tag in the "footer" section of the embeds sent by anonymous moderators in the recipient DM.", + "description": "This is the name tag in the “footer” section of the embeds sent by anonymous moderators in the recipient DM.", "examples": [ "`{prefix}config set anon_tag Support Agent`" ], @@ -904,7 +904,7 @@ ], "notes": [ "The private_ variant is used when sending to the new user.", - "See also: `private_added_to_group_title`, `private_added_to_group_title` + "See also: `private_added_to_group_title`, `private_added_to_group_title`" ] }, "public_added_to_group_response": { @@ -976,7 +976,7 @@ ], "notes": [ "The private_ variant is used when sending to the new user.", - "See also: `private_removed_from_group_title`, `private_removed_from_group_title` + "See also: `private_removed_from_group_title`, `private_removed_from_group_title`" ] }, "public_removed_from_group_response": { @@ -1266,4 +1266,4 @@ "See also: `thread_min_characters`, `thread_min_characters_title`, `thread_min_characters_response`." ] } -} +} \ No newline at end of file From ab0071baf86d3276b79e71176cd137421b8c5999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Sat, 5 Jul 2025 12:14:53 +0200 Subject: [PATCH 5/7] fix: embed error? --- core/thread.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/thread.py b/core/thread.py index 6ecba3af8e..cfd404ce65 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1417,11 +1417,11 @@ async def create( if min_chars > 0 and message is not None and message.content is not None: if len(message.content.strip()) < min_chars: embed = discord.Embed( - title=self.bot.config.get("thread_min_characters_title", "Message too short"), - description=self.bot.config.get("thread_min_characters_response", "Your message is too short to create a thread. Please provide more details.").replace("{min_characters}", str(min_chars)), + title=self.bot.config["thread_min_characters_title"], + description=self.bot.config["thread_min_characters_response"].replace("{min_characters}", str(min_chars)), color=self.bot.error_color, ) - embed.set_footer(text=self.bot.config.get("thread_min_characters_footer", "Minimum {min_characters} characters required.").replace("{min_characters}", str(min_chars))) + embed.set_footer(text=self.bot.config["thread_min_characters_footer"].replace("{min_characters}", str(min_chars))) await message.channel.send(embed=embed) thread = Thread(self, recipient) thread.cancelled = True From fa65c4727c17de3115eb12ebf98dfa26be38a85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Mon, 14 Jul 2025 22:04:22 +0200 Subject: [PATCH 6/7] change: Exception ~> ValueError Changed: - Only catch ValueError (instead of all exceptions) when parsing thread_min_characters config as an integer. --- core/thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread.py b/core/thread.py index cfd404ce65..4cbd3b1114 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1412,7 +1412,7 @@ async def create( min_chars = 0 try: min_chars = int(min_chars) - except Exception: + except ValueError: min_chars = 0 if min_chars > 0 and message is not None and message.content is not None: if len(message.content.strip()) < min_chars: From 4907de4025a9e2069a33a771c7d3988104088148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clorenzo132=E2=80=9D?= Date: Thu, 17 Jul 2025 18:23:27 +0200 Subject: [PATCH 7/7] fix: Black formatting. --- core/_color_data.py | 1 - core/thread.py | 10 ++++++++-- core/time.py | 1 + core/utils.py | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/_color_data.py b/core/_color_data.py index 0ac42d5c1f..13ec45620e 100644 --- a/core/_color_data.py +++ b/core/_color_data.py @@ -3,7 +3,6 @@ Slightly modified to conform with usage. """ - BASE_COLORS = { "b": "0000ff", "g": "007f00", diff --git a/core/thread.py b/core/thread.py index 4cbd3b1114..ec2070a08e 100644 --- a/core/thread.py +++ b/core/thread.py @@ -1418,10 +1418,16 @@ async def create( if len(message.content.strip()) < min_chars: embed = discord.Embed( title=self.bot.config["thread_min_characters_title"], - description=self.bot.config["thread_min_characters_response"].replace("{min_characters}", str(min_chars)), + description=self.bot.config["thread_min_characters_response"].replace( + "{min_characters}", str(min_chars) + ), color=self.bot.error_color, ) - embed.set_footer(text=self.bot.config["thread_min_characters_footer"].replace("{min_characters}", str(min_chars))) + embed.set_footer( + text=self.bot.config["thread_min_characters_footer"].replace( + "{min_characters}", str(min_chars) + ) + ) await message.channel.send(embed=embed) thread = Thread(self, recipient) thread.cancelled = True diff --git a/core/time.py b/core/time.py index c56c7264e2..71f4ca3c8a 100644 --- a/core/time.py +++ b/core/time.py @@ -3,6 +3,7 @@ Source: https://github.com/Rapptz/RoboDanny/blob/rewrite/cogs/utils/time.py """ + from __future__ import annotations import datetime diff --git a/core/utils.py b/core/utils.py index 149240dc08..89d0676eb0 100644 --- a/core/utils.py +++ b/core/utils.py @@ -370,7 +370,7 @@ def create_not_found_embed(word, possibilities, name, n=2, cutoff=0.6) -> discor def parse_alias(alias, *, split=True): def encode_alias(m): - return "\x1AU" + base64.b64encode(m.group(1).encode()).decode() + "\x1AU" + return "\x1aU" + base64.b64encode(m.group(1).encode()).decode() + "\x1aU" def decode_alias(m): return base64.b64decode(m.group(1).encode()).decode() @@ -392,7 +392,7 @@ def decode_alias(m): iterate = [alias] for a in iterate: - a = re.sub("\x1AU(.+?)\x1AU", decode_alias, a) + a = re.sub("\x1aU(.+?)\x1aU", decode_alias, a) if a[0] == a[-1] == '"': a = a[1:-1] aliases.append(a)