From 89afed5793221d8d55792c50f8dd30b28db4885d Mon Sep 17 00:00:00 2001 From: Taku <45324516+Taaku18@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:28:36 +0000 Subject: [PATCH 1/7] Update README.md Signed-off-by: Taku <45324516+Taaku18@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 980066ca7e..500978a1f9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@
- +
@@ -24,7 +24,7 @@ - Python 3.8 + Patreon From 359c00df65c8aad23aebf9d9870816f502297c5b Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 17 Jan 2025 00:00:16 +0100 Subject: [PATCH 2/7] Check for local git repository Implements a checking mechanismen for local git repository. --- bot.py | 33 +++++++++++++++++++++++++++++++++ cogs/utility.py | 8 ++++++++ 2 files changed, 41 insertions(+) diff --git a/bot.py b/bot.py index 3c6ebe7911..9cd61bc269 100644 --- a/bot.py +++ b/bot.py @@ -758,6 +758,33 @@ def check_manual_blocked(self, author: discord.Member) -> bool: logger.debug("User blocked, user %s.", author.name) return False + def check_local_git(self) -> bool: + """ + Checks if the bot is installed via git. + """ + valid_local_git = False + git_folder_path = os.path.join(".git") + + # Check if the .git folder exists and is a directory + if os.path.exists(git_folder_path) and os.path.isdir(git_folder_path): + required_files = ["config", "HEAD"] + required_dirs = ["refs", "objects"] + + # Verify required files exist + for file in required_files: + if not os.path.isfile(os.path.join(git_folder_path, file)): + return valid_local_git + + # Verify required directories exist + for directory in required_dirs: + if not os.path.isdir(os.path.join(git_folder_path, directory)): + return valid_local_git + + # If all checks pass, set valid_local_git to True + valid_local_git = True + + return valid_local_git + async def _process_blocked(self, message): _, blocked_emoji = await self.retrieve_emoji() if await self.is_blocked(message.author, channel=message.channel, send_message=True): @@ -1721,6 +1748,12 @@ async def before_autoupdate(self): self.autoupdate.cancel() return + if not self.check_local_git(): + logger.warning("Bot not installed via git.") + logger.warning("Autoupdates disabled.") + self.autoupdate.cancel() + return + @tasks.loop(hours=1, reconnect=False) async def log_expiry(self): log_expire_after = self.config.get("log_expiration") diff --git a/cogs/utility.py b/cogs/utility.py index 31cb065a28..19147b9134 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -2003,6 +2003,14 @@ async def update(self, ctx, *, flag: str = ""): embed.set_author(name=user["username"], icon_url=user["avatar_url"], url=user["url"]) await ctx.send(embed=embed) else: + if self.bot.check_local_git() is False: + embed = discord.Embed( + title="Update Command Unavailable", + description="The bot cannot be updated due to not being installed via a git." + "You need to manually update the bot according to your hosting method.", + color=discord.Color.red(), + ) + return await ctx.send(embed=embed) command = "git pull" proc = await asyncio.create_subprocess_shell( command, From 902082e68b833e97312101fb236bc7563e26f522 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 17 Jan 2025 00:10:45 +0100 Subject: [PATCH 3/7] Fix typo in update command. --- cogs/utility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/utility.py b/cogs/utility.py index 19147b9134..78695bdd57 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -2006,7 +2006,7 @@ async def update(self, ctx, *, flag: str = ""): if self.bot.check_local_git() is False: embed = discord.Embed( title="Update Command Unavailable", - description="The bot cannot be updated due to not being installed via a git." + description="The bot cannot be updated due to not being installed via git." "You need to manually update the bot according to your hosting method.", color=discord.Color.red(), ) From e798fb5941e1418839f7805c8851d97913cb06b1 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 8 Feb 2025 21:28:53 +0100 Subject: [PATCH 4/7] Fix incorrect config help This fixes some incorrect help text of the thread_close_response and thread_self_close_response --- 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 d301763fe4..ad5241c09f 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -521,7 +521,7 @@ "notes": [ "When `recipient_thread_close` is enabled and the recipient closed their own thread, `thread_self_close_response` is used instead of this configuration.", "You may use the `{{closer}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that closed the thread.", - "`{{loglink}}` can be used as a placeholder substitute for the full URL linked to the thread in the log viewer and `{{loglink}}` for the unique key (ie. s3kf91a) of the log.", + "`{{loglink}}` can be used as a placeholder substitute for the full URL linked to the thread in the log viewer and `{{logkey}}` for the unique key (ie. s3kf91a) of the log.", "Discord flavoured markdown is fully supported in `thread_close_response`.", "See also: `thread_close_title`, `thread_close_footer`, `thread_self_close_response`, `thread_creation_response`." ] @@ -535,7 +535,7 @@ "notes": [ "When `recipient_thread_close` is disabled or the thread wasn't closed by the recipient, `thread_close_response` is used instead of this configuration.", "You may use the `{{closer}}` variable for access to the [Member](https://discordpy.readthedocs.io/en/latest/api.html#discord.Member) that closed the thread.", - "`{{loglink}}` can be used as a placeholder substitute for the full URL linked to the thread in the log viewer and `{{loglink}}` for the unique key (ie. s3kf91a) of the log.", + "`{{loglink}}` can be used as a placeholder substitute for the full URL linked to the thread in the log viewer and `{{logkey}}` for the unique key (ie. s3kf91a) of the log.", "Discord flavoured markdown is fully supported in `thread_self_close_response`.", "See also: `thread_close_title`, `thread_close_footer`, `thread_close_response`." ] From 3ebb21e4116e753ae727319cdf37ca6cef5ae5e2 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 27 Feb 2025 18:23:17 +0100 Subject: [PATCH 5/7] recipient_thread_close via Buttons - Updates the recipient_thread_close feature to use a button instead of a reaction. - New config options: recipient_thread_close_button_label, recipient_thread_close_button_style - The close_emoji default value is now None to make it possible changing the button to only have a label. --- bot.py | 23 ++++++++++------------- core/config.py | 9 ++++++--- core/config_help.json | 33 ++++++++++++++++++++++++++++----- core/thread.py | 31 +++++++++++++++++++++++++++---- core/utils.py | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 25 deletions(-) diff --git a/bot.py b/bot.py index 9cd61bc269..6d5fe46ab1 100644 --- a/bot.py +++ b/bot.py @@ -48,7 +48,15 @@ ) from core.thread import ThreadManager from core.time import human_timedelta -from core.utils import extract_block_timestamp, normalize_alias, parse_alias, truncate, tryint, human_join +from core.utils import ( + extract_block_timestamp, + normalize_alias, + parse_alias, + truncate, + tryint, + human_join, + ThreadSelfCloseView, +) logger = getLogger(__name__) @@ -617,6 +625,7 @@ async def on_ready(self): self.post_metadata.start() self.autoupdate.start() self.log_expiry.start() + self.add_view(ThreadSelfCloseView(self)) self._started = True async def convert_emoji(self, name: str) -> str: @@ -1275,19 +1284,7 @@ async def handle_reaction_events(self, payload): return reaction = payload.emoji - close_emoji = await self.convert_emoji(self.config["close_emoji"]) if from_dm: - if ( - payload.event_type == "REACTION_ADD" - and message.embeds - and str(reaction) == str(close_emoji) - and self.config.get("recipient_thread_close") - ): - ts = message.embeds[0].timestamp - if ts == thread.channel.created_at: - # the reacted message is the corresponding thread creation embed - # closing thread - return await thread.close(closer=user) if ( message.author == self.user and message.embeds diff --git a/core/config.py b/core/config.py index 5c6b0dd09d..c33deb21be 100644 --- a/core/config.py +++ b/core/config.py @@ -49,12 +49,14 @@ class ConfigManager: # threads "sent_emoji": "\N{WHITE HEAVY CHECK MARK}", "blocked_emoji": "\N{NO ENTRY SIGN}", - "close_emoji": "\N{LOCK}", + "close_emoji": None, "use_user_id_channel_name": False, "use_timestamp_channel_name": False, "use_nickname_channel_name": False, "use_random_channel_name": False, "recipient_thread_close": False, + "recipient_thread_close_button_label": None, + "recipient_thread_close_button_style": "red", "thread_show_roles": True, "thread_show_account_age": True, "thread_show_join_age": True, @@ -65,7 +67,7 @@ class ConfigManager: "thread_creation_response": "The staff team will get back to you as soon as possible.", "thread_creation_footer": "Your message has been sent", "thread_contact_silently": False, - "thread_self_closable_creation_footer": "Click the lock to close the thread", + "thread_self_closable_creation_footer": "Click the button to close the thread", "thread_creation_contact_title": "New Thread", "thread_creation_self_contact_response": "You have opened a Modmail thread.", "thread_creation_contact_response": "{creator.name} has opened a Modmail thread.", @@ -233,7 +235,8 @@ class ConfigManager: enums = { "dm_disabled": DMDisabled, "status": discord.Status, - "activity_type": discord.ActivityType, + "activity_type": discord.ActivityType + #"recipient_thread_close_button_style": discord.ButtonStyle } force_str = {"command_permissions", "level_permissions"} diff --git a/core/config_help.json b/core/config_help.json index ad5241c09f..144c54e9f1 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -285,8 +285,8 @@ ] }, "close_emoji": { - "default": "🔒", - "description": "This is the emoji the recipient can click to close a thread themselves. The emoji is automatically added to the `thread_creation_response` embed.", + "default": "None", + "description": "This is the emoji for the close button the recipient can click to close a thread themselves. The emoji (attached to the button) is automatically added to the `thread_creation_response` embed.", "examples": [ "`{prefix}config set close_emoji 👍‍`" ], @@ -297,14 +297,37 @@ }, "recipient_thread_close": { "default": "Disabled", - "description": "Setting this configuration will allow recipients to use the `close_emoji` to close the thread themselves.", + "description": "Setting this configuration will allow recipients to close threads by themselves via a button.", "examples": [ "`{prefix}config set recipient_thread_close yes`", "`{prefix}config set recipient_thread_close no`" ], "notes": [ - "The close emoji is dictated by the configuration `close_emoji`.", - "See also: `close_emoji`." + "The button attached to the `thread_creation_response` can have set a custom label, emoji or even both.", + "See also: `close_emoji`, `recipient_thread_close_button_label`, `recipient_thread_close_button_style`." + ] + }, + "recipient_thread_close_button_label": { + "default": "None", + "description": "This configuration changes the label of the button for the `recipient_thread_close` feature.", + "examples": [ + "`{prefix}config set recipient_thread_close_button_label Your label`" + ], + "notes": [ + "The label cannot exceed 80 characters.", + "See also: `recipient_thread_close`, `close_emoji`, `recipient_thread_close_button_style`." + ] + }, + "recipient_thread_close_button_style": { + "default": "red", + "description": "This configuration changes the style of the button for the `recipient_thread_close` feature.", + "examples": [ + "`{prefix}config set recipient_thread_close_button_style green`", + "`{prefix}config set recipient_thread_close_button_style blurple`" + ], + "notes": [ + "The style is limited by discord to the following colors: blurple, green, red, gray.", + "See also: `recipient_thread_close`, `close_emoji`, `recipient_thread_close_button_label`." ] }, "thread_show_roles": { diff --git a/core/thread.py b/core/thread.py index 81dc03f44d..5cd95a68a7 100644 --- a/core/thread.py +++ b/core/thread.py @@ -32,6 +32,7 @@ AcceptButton, DenyButton, ConfirmThreadCreationView, + ThreadSelfCloseView, DummyParam, ) @@ -239,11 +240,33 @@ async def send_recipient_genesis_message(): if creator is None or creator == recipient: msg = await recipient.send(embed=embed) - - if recipient_thread_close: + close_emoji = self.bot.config["close_emoji"] + close_label = self.bot.config["recipient_thread_close_button_label"] + if ( + recipient_thread_close + and self.bot.config["recipient_thread_close_button_style"].lower() + in ["red", "green", "blurple", "gray"] + and (close_emoji is not None and close_label is not None) + ): close_emoji = self.bot.config["close_emoji"] - close_emoji = await self.bot.convert_emoji(close_emoji) - await self.bot.add_reaction(msg, close_emoji) + if close_emoji: + close_emoji = await self.bot.convert_emoji(close_emoji) + + button_style = discord.ButtonStyle( + int( + discord.ButtonStyle[ + self.bot.config["recipient_thread_close_button_style"].lower() + ] + ) + ) + view = ThreadSelfCloseView( + button_style, + close_label, + close_emoji, + self.bot, + ) + await msg.edit(view=view) + # await self.bot.add_reaction(msg, close_emoji) async def send_persistent_notes(): notes = await self.bot.api.find_notes(self.recipient) diff --git a/core/utils.py b/core/utils.py index 9f9f572f5a..2ca68be346 100644 --- a/core/utils.py +++ b/core/utils.py @@ -42,6 +42,7 @@ "AcceptButton", "DenyButton", "ConfirmThreadCreationView", + "ThreadSelfCloseView", "DummyParam", ] @@ -599,6 +600,41 @@ def __init__(self): self.value = None +class ThreadSelfCloseView(discord.ui.View): + def __init__( + self, + style: typing.Optional[discord.ButtonStyle] = None, + label: typing.Optional[str] = None, + emoji: typing.Optional[str] = None, + bot: typing.Any = None, + ): + super().__init__(timeout=None) + self.bot = bot + + self.self_close_button.label = label + self.self_close_button.style = style + self.self_close_button.emoji = emoji + + @discord.ui.button(label="default", style=discord.ButtonStyle.secondary, custom_id="SelfCloseView") + async def self_close_button(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer(ephemeral=False, thinking=True) + thread = await self.bot.threads.find(recipient=interaction.user) + if not thread: + error_embed = discord.Embed( + description="A thread could not be found.", color=self.bot.config["error_color"] + ) + return await interaction.followup.send(embed=error_embed) + + message = self.bot.config["thread_self_close_response"] + embed = discord.Embed( + title="Thread closed", description=message, color=self.bot.config["error_color"] + ) + await thread.close(closer=interaction.user, silent=True) + self.self_close_button.disabled = True + await interaction.message.edit(view=self) + await interaction.followup.send(embed=embed) + + class DummyParam: """ A dummy parameter that can be used for MissingRequiredArgument. From e3af5fa8a06562fd6051934e88cbad30d4c144c3 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 27 Feb 2025 18:28:17 +0100 Subject: [PATCH 6/7] Fix config.py black formatting --- core/config.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/config.py b/core/config.py index c33deb21be..85b6b05fb5 100644 --- a/core/config.py +++ b/core/config.py @@ -232,12 +232,7 @@ class ConfigManager: "registry_plugins_only", } - enums = { - "dm_disabled": DMDisabled, - "status": discord.Status, - "activity_type": discord.ActivityType - #"recipient_thread_close_button_style": discord.ButtonStyle - } + enums = {"dm_disabled": DMDisabled, "status": discord.Status, "activity_type": discord.ActivityType} force_str = {"command_permissions", "level_permissions"} From 58954a43839741e9d8a39b25b9823e3a10bc6fc0 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 27 Feb 2025 18:30:02 +0100 Subject: [PATCH 7/7] Merge pull request #4 from martinbndr/recipient_thread_close_button recipient_thread_close via Buttons