Skip to content

recipient_thread_close via Buttons #3363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<br>

<a href="#">
<img src="https://img.shields.io/badge/Latest%20Version-v4.1.0-7289da?style=for-the-badge&logo=data:image/gif;base64,R0lGODlhGAAYAPcAAAAAADQ+Yj5MdThCaEFOekNQfWt5e1lqfEdVhVpriVx1iVtsnFZpll1xlF1znFx5mGNtjGR7hWNzjGd5iWJunGNslmN0lGl1lGN5lWt7lWR0nGp1nWR5nGt8nHJ9l3J9jFxto1xtqlZqqF5zpF15pV10rVx5q1Z2ql1us150s114sV50uVd1smJupGNurGN0pGp1pGR6o2t7o2F0rml0rGR5rGt8q3J9pGNus2Fvu2Bzs2p2smR5s2t7tGF0u2N6vGp7u2l1uXJ+uGJ1wWV7w2l+xWl+yl5zwHWDiWyCmHODnHiGmHSLkW2CpGuEqnSCpHqMo3ODrHWKrXyMq3iHp3yTqGyEtWqFunKDs3uLtHSGvXSMvHuMu3eItnyTs3yTu3iUuW+RsV+ApmuCxG2Dy22LxnSMxHuMxHGGzXSLzXqLzXWFxXyTw3uSy3WSx26E0W+N2m+L1XKG03SL1XmL1HOL2HmL23WF2XiS1m+QyHOK4XmL4XaH4nGM5ImXm4WOlJSjnoOOq4OOpYSUrImXqJOcqYONt4OUtIybtIOUu4ycvImYt5OcuIyiqZekqYyiupOjvJurvJimtp+yvqKruae2ua23trLCucPHvIOMxoOUxIuaw4eYyJKbyISW1YiW0o2kyJWlxJqpxZWkzJury5aoxp6xxZ2zyZio1Jyy2Yuk0aOsyKOyxKOzzKy8zKq2yLO7xqOr1KSz1Ku706W026y73Ki21bK716u75LS95KSu4Zyx6bPDzLjGx63B2LPD1LTE3LvL3brI17/S2bbKzrTE47rF47zM5L3M67bH577R5L7R67zK8rjM8MLM3MPK1sTT3crU3MjW2dLZ2srUy8PN5MTO68nP68jL48TS5MnT5cbR68jS7M3b7Mva5dTb6MTO8srO8szF8MXR8snS8c3a8cnY+NXb9tDL7dnm6dzk8tro99Pq9uTp6+r17fr77eTr/efr+Oz09OXz/Ov0/e37/uj39fX19fX89fz+9vP1/fX9/f7//vr2/ODl4sK75yH5BAEAAAAALAAAAAAYABgAAAj/AAEAmFOnoMGDCAvGmRNHYMKHEOdIhEgRIcGKGDMeTJVO00WNBj194/frFik5IOvgqdaP371N26xpefORYq57/HI+s7Vtmy0eKCmmapcz575FPXt+SfHmociiRae1SroNWIoVTQ2uxAk15yJg1awl7ZSCaUE82uRxhTpt1TNXq0Ip2pSobIqCc0iVoqsI0ipXr1bhywfVXxYdiA3Sourz2dqc8lbNQJz4bDWqix73E7ZjhgrKOjYmvfWLbZcZqCdTPjjHGLdtibi2U5Q6NWiEqqq64ifvVe3at1nX2tTuWQ/QyIMfxHOGi+rkyeMklPMDuvWaBqlbj469IPUU2ykPCOw+53t4AAEBADs=">
<img src="https://img.shields.io/badge/Latest%20Version-v4.1.1-7289da?style=for-the-badge&logo=data:image/gif;base64,R0lGODlhGAAYAPcAAAAAADQ+Yj5MdThCaEFOekNQfWt5e1lqfEdVhVpriVx1iVtsnFZpll1xlF1znFx5mGNtjGR7hWNzjGd5iWJunGNslmN0lGl1lGN5lWt7lWR0nGp1nWR5nGt8nHJ9l3J9jFxto1xtqlZqqF5zpF15pV10rVx5q1Z2ql1us150s114sV50uVd1smJupGNurGN0pGp1pGR6o2t7o2F0rml0rGR5rGt8q3J9pGNus2Fvu2Bzs2p2smR5s2t7tGF0u2N6vGp7u2l1uXJ+uGJ1wWV7w2l+xWl+yl5zwHWDiWyCmHODnHiGmHSLkW2CpGuEqnSCpHqMo3ODrHWKrXyMq3iHp3yTqGyEtWqFunKDs3uLtHSGvXSMvHuMu3eItnyTs3yTu3iUuW+RsV+ApmuCxG2Dy22LxnSMxHuMxHGGzXSLzXqLzXWFxXyTw3uSy3WSx26E0W+N2m+L1XKG03SL1XmL1HOL2HmL23WF2XiS1m+QyHOK4XmL4XaH4nGM5ImXm4WOlJSjnoOOq4OOpYSUrImXqJOcqYONt4OUtIybtIOUu4ycvImYt5OcuIyiqZekqYyiupOjvJurvJimtp+yvqKruae2ua23trLCucPHvIOMxoOUxIuaw4eYyJKbyISW1YiW0o2kyJWlxJqpxZWkzJury5aoxp6xxZ2zyZio1Jyy2Yuk0aOsyKOyxKOzzKy8zKq2yLO7xqOr1KSz1Ku706W026y73Ki21bK716u75LS95KSu4Zyx6bPDzLjGx63B2LPD1LTE3LvL3brI17/S2bbKzrTE47rF47zM5L3M67bH577R5L7R67zK8rjM8MLM3MPK1sTT3crU3MjW2dLZ2srUy8PN5MTO68nP68jL48TS5MnT5cbR68jS7M3b7Mva5dTb6MTO8srO8szF8MXR8snS8c3a8cnY+NXb9tDL7dnm6dzk8tro99Pq9uTp6+r17fr77eTr/efr+Oz09OXz/Ov0/e37/uj39fX19fX89fz+9vP1/fX9/f7//vr2/ODl4sK75yH5BAEAAAAALAAAAAAYABgAAAj/AAEAmFOnoMGDCAvGmRNHYMKHEOdIhEgRIcGKGDMeTJVO00WNBj194/frFik5IOvgqdaP371N26xpefORYq57/HI+s7Vtmy0eKCmmapcz575FPXt+SfHmociiRae1SroNWIoVTQ2uxAk15yJg1awl7ZSCaUE82uRxhTpt1TNXq0Ip2pSobIqCc0iVoqsI0ipXr1bhywfVXxYdiA3Sourz2dqc8lbNQJz4bDWqix73E7ZjhgrKOjYmvfWLbZcZqCdTPjjHGLdtibi2U5Q6NWiEqqq64ifvVe3at1nX2tTuWQ/QyIMfxHOGi+rkyeMklPMDuvWaBqlbj469IPUU2ykPCOw+53t4AAEBADs=">
</a>

<br>
Expand All @@ -24,7 +24,7 @@
</a>

<a href="https://patreon.com/kyber">
<img src="https://img.shields.io/badge/patreon-donate-orange.svg?style=for-the-badge&logo=Patreon" alt="Python 3.8">
<img src="https://img.shields.io/badge/patreon-donate-orange.svg?style=for-the-badge&logo=Patreon" alt="Patreon">
</a>

<a href="https://www.python.org/downloads/">
Expand Down
56 changes: 43 additions & 13 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -758,6 +767,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):
Expand Down Expand Up @@ -1248,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
Expand Down Expand Up @@ -1721,6 +1745,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")
Expand Down
8 changes: 8 additions & 0 deletions cogs/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 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,
Expand Down
12 changes: 5 additions & 7 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.",
Expand Down Expand Up @@ -230,11 +232,7 @@ class ConfigManager:
"registry_plugins_only",
}

enums = {
"dm_disabled": DMDisabled,
"status": discord.Status,
"activity_type": discord.ActivityType,
}
enums = {"dm_disabled": DMDisabled, "status": discord.Status, "activity_type": discord.ActivityType}

force_str = {"command_permissions", "level_permissions"}

Expand Down
37 changes: 30 additions & 7 deletions core/config_help.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 👍‍`"
],
Expand All @@ -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": {
Expand Down Expand Up @@ -521,7 +544,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`."
]
Expand All @@ -535,7 +558,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`."
]
Expand Down
31 changes: 27 additions & 4 deletions core/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
AcceptButton,
DenyButton,
ConfirmThreadCreationView,
ThreadSelfCloseView,
DummyParam,
)

Expand Down Expand Up @@ -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)
Expand Down
36 changes: 36 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"AcceptButton",
"DenyButton",
"ConfirmThreadCreationView",
"ThreadSelfCloseView",
"DummyParam",
]

Expand Down Expand Up @@ -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.
Expand Down
Loading