Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ The required scopes are:
And the required permissions:
- Embed Links
- Manage Messages
- Manage Webhooks
- Read Message History
- Send Messages
- Send Messages in Threads
Expand Down
80 changes: 75 additions & 5 deletions cogs/link_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,17 @@ async def fix_embeds(
or (isinstance(channel, discore.Thread) and (channel.locked or channel.archived))):
return

async with Typing(channel):
async def render_and_send() -> tuple[list[tuple[str, list[WebsiteLink]]], dict[discore.Message, list[WebsiteLink]]]:
rendered_links = [link for link in links if await link.render()]
if not rendered_links:
return
not_sent, messages = await send_fixed_links(rendered_links, guild, original_message)
return [], {}
return await send_fixed_links(rendered_links, guild, original_message)

if guild.reply_as_original_author_replica:
not_sent, messages = await render_and_send()
else:
async with Typing(channel):
not_sent, messages = await render_and_send()

to_delete = []
if messages:
Expand Down Expand Up @@ -200,13 +206,17 @@ async def send_fixed_links(
links_failed: list[tuple[str, list[WebsiteLink]]] = []

grouped = group_items(rendered_links, 2000)
use_original_author_replica = guild.reply_as_original_author_replica
webhook = await get_or_create_webhook(original_message.channel) if use_original_author_replica else None

for i, (message_content, links_in_group) in enumerate(grouped):
if i == 0 and guild.reply_to_message:
if webhook is not None:
coro = webhook_send(webhook, original_message, message_content, guild.reply_silently)
elif i == 0 and guild.reply_to_message:
coro = discore.fallback_reply(original_message, message_content, silent=guild.reply_silently)
else:
coro = original_message.channel.send(message_content, silent=guild.reply_silently)

sent, msg = await safe_send_coro(coro, invalid_form_body='Embed size exceeds maximum size', forbidden=True)
if sent and msg:
messages_sent[msg] = links_in_group
Expand All @@ -216,6 +226,66 @@ async def send_fixed_links(
return links_failed, messages_sent


async def get_or_create_webhook(channel: GuildMessageableChannel) -> discore.Webhook | None:
"""
Get or create the webhook used to send messages as the original author.

:param channel: the channel to send the fixed links to
:return: the webhook to use, if available
"""

webhook_channel = channel.parent if isinstance(channel, discore.Thread) else channel
if webhook_channel is None:
return None

if not hasattr(webhook_channel, 'webhooks') or not hasattr(webhook_channel, 'create_webhook'):
return None

if not webhook_channel.permissions_for(channel.guild.me).manage_webhooks:
return None

success, webhooks = await safe_send_coro(webhook_channel.webhooks(), forbidden=True)
if not success:
return None
bot = discore.Bot.get()
webhook = next((
w for w in webhooks
if getattr(w.user, 'id', None) == bot.user.id
Comment thread
Kyrela marked this conversation as resolved.
), None)
if webhook is not None:
return webhook
success, webhook = await safe_send_coro(webhook_channel.create_webhook(name=bot.user.display_name), forbidden=True)
return webhook if success else None


async def webhook_send(
webhook: discore.Webhook,
original_message: discore.Message,
content: str,
silent: bool
) -> discore.Message:
"""
Send a fixed link using the original author's display name and avatar.

:param webhook: the webhook to use
:param original_message: the message associated with the context to reply to
:param content: the content to send
:param silent: whether to send the message silently
:return: the message created by the webhook
"""

kwargs = {
'content': content,
'username': original_message.author.display_name,
'avatar_url': original_message.author.display_avatar.url,
'silent': silent,
'wait': True
}
if isinstance(original_message.channel, discore.Thread):
kwargs['thread'] = original_message.channel
return await webhook.send(**kwargs)


async def wait_for_embed(message: discore.Message) -> bool:
"""
Wait for the message to have embeds.
Expand Down
Comment thread
Kyrela marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""AddOriginalAuthorReplicaReplies Migration."""

from masoniteorm.migrations import Migration


class AddOriginalAuthorReplicaReplies(Migration):
def up(self):
"""
Run the migrations.
"""
with self.schema.table("guilds") as table:
table.boolean("reply_as_original_author_replica").after("reply_silently").default(False)

def down(self):
"""
Revert the migrations.
"""
with self.schema.table("guilds") as table:
table.drop_column("reply_as_original_author_replica")
1 change: 1 addition & 0 deletions database/models/Guild.py
Comment thread
Kyrela marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class Guild(DiscordRepresentation):
'roles_use_any_rule': bool,
'reply_to_message': bool,
'reply_silently': bool,
'reply_as_original_author_replica': bool,
'webhooks': bool,
'original_message': OriginalMessage,
'twitter_view': FxEmbedView,
Expand Down
12 changes: 11 additions & 1 deletion locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ settings:
read_message_history:
"true": "🟢 `Read message history` permission"
"false": "🔴 Missing `read message history` permission"
manage_webhooks:
"true": "🟢 `Manage webhooks` permission"
"false": "🔴 Missing `manage webhooks` permission"
filters:
button:
toggle:
Expand Down Expand Up @@ -148,7 +151,7 @@ settings:
reply_method:
name: "Reply method"
description: "Change the behavior on the reply"
content: "**Change what to do on the reply**\n- %{state}\n- %{silent}%{perms}"
content: "**Change what to do on the reply**\n- %{state}\n- %{silent}\n- %{replica}%{perms}"
reply:
button:
"true": "Replying"
Expand All @@ -163,6 +166,13 @@ settings:
state:
"true": "🔕 Send silently"
"false": "🔔 Send with a notification"
original_author_replica:
button:
"true": "Send as original author replica"
"false": "Send as %{bot}"
state:
"true": "🪪 Send as original author replica"
"false": "🤖 Send as %{bot}"
webhooks:
name: "Webhooks"
description: "Enable/Disable for webhooks"
Expand Down
12 changes: 11 additions & 1 deletion locales/ko.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ settings:
read_message_history:
"true": "🟢 `메시지 기록 보기` 권한"
"false": "🔴 `메시지 기록 보기` 권한 없음"
manage_webhooks:
"true": "🟢 `웹후크 관리하기` 권한"
"false": "🔴 `웹후크 관리하기` 권한 없음"
filters:
button:
toggle:
Expand Down Expand Up @@ -145,7 +148,7 @@ settings:
reply_method:
name: "답장 방법"
description: "답장할 때의 동작 변경하기"
content: "**답장할 때 수행할 작업 변경하기**\n- %{state}\n- %{silent}%{perms}"
content: "**답장할 때 수행할 작업 변경하기**\n- %{state}\n- %{silent}\n- %{replica}%{perms}"
reply:
button:
"true": "답장"
Expand All @@ -160,6 +163,13 @@ settings:
state:
"true": "🔕 조용히 보내기"
"false": "🔔 알림과 함께 보내기"
original_author_replica:
button:
"true": "원본 작성자 복제본으로 보내기"
"false": "%{bot}(으)로 보내기"
state:
"true": "🪪 원본 작성자 복제본으로 보내기"
"false": "🤖 %{bot}(으)로 보내기"
webhooks:
name: "웹후크"
description: "웹후크 활성화/비활성화하기"
Expand Down
42 changes: 38 additions & 4 deletions src/settings.py
Comment thread
Kyrela marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ async def embed(self) -> discore.Embed:
perms.append('manage_messages')
if self.ctx.guild.reply_to_message:
perms.append('read_message_history')
if self.ctx.guild.reply_as_original_author_replica:
perms.append('manage_webhooks')
embed.add_field(
name=t('settings.troubleshooting.permissions', channel=self.ctx.channel.mention),
value=format_perms(perms, self.ctx.channel.discord_object, include_label=False, include_valid=True),
Expand Down Expand Up @@ -1123,7 +1125,8 @@ class ReplyMethodSetting(BaseSetting):

def __init__(self, interaction: discore.Interaction, view: SettingsView, ctx: DataElements):
super().__init__(interaction, view, ctx)
self.reply_to_message = bool(ctx.guild.reply_to_message)
self.reply_as_original_author_replica = bool(ctx.guild.reply_as_original_author_replica)
self.reply_to_message = bool(ctx.guild.reply_to_message and not self.reply_as_original_author_replica)
self.reply_silently = bool(ctx.guild.reply_silently)

@property
Expand All @@ -1137,21 +1140,31 @@ async def embed(self) -> discore.Embed:
perms.append('send_messages_in_threads')
if self.reply_to_message:
perms.append('read_message_history')
if self.reply_as_original_author_replica:
perms.append('manage_webhooks')
embed = discore.Embed(
title=f"{self.emoji} {t(self.name)}",
description=t(
'settings.reply_method.content',
state=t(f'settings.reply_method.reply.state.{l(self.reply_to_message)}', emoji=self.emoji),
silent=t(f'settings.reply_method.silent.state.{l(self.reply_silently)}'),
replica=t(
f'settings.reply_method.original_author_replica.state.{l(self.reply_as_original_author_replica)}',
bot=self.bot.user.display_name),
perms=format_perms(perms, self.ctx.channel.discord_object))
)
discore.set_embed_footer(self.bot, embed)
return embed

@property
async def option(self) -> discore.SelectOption:
has_missing_perms = (
(self.reply_to_message and is_missing_perm(['read_message_history'], self.ctx.channel.discord_object))
or (self.reply_as_original_author_replica
and is_missing_perm(['manage_webhooks'], self.ctx.channel.discord_object))
)
return discore.SelectOption(
label=('⚠️ ' if self.reply_to_message and is_missing_perm(['read_message_history'], self.ctx.channel.discord_object) else '')
label=('⚠️ ' if has_missing_perms else '')
+ t(self.name),
value=self.id,
description=t(self.description),
Expand All @@ -1163,7 +1176,8 @@ async def items(self) -> List[discore.ui.Item]:
reply_to_message_button = discore.ui.Button(
style=discore.ButtonStyle.primary if self.reply_to_message else discore.ButtonStyle.secondary,
label=t(f'settings.reply_method.reply.button.{l(self.reply_to_message)}'),
custom_id=self.id
custom_id=self.id,
disabled=self.reply_as_original_author_replica
)
edit_callback(reply_to_message_button, self.view, self.toggle_reply_to_message)
reply_silently_button = discore.ui.Button(
Expand All @@ -1172,9 +1186,19 @@ async def items(self) -> List[discore.ui.Item]:
custom_id='reply_silently'
)
edit_callback(reply_silently_button, self.view, self.toggle_reply_silently)
return [reply_to_message_button, reply_silently_button]
original_author_replica_button = discore.ui.Button(
style=discore.ButtonStyle.primary if self.reply_as_original_author_replica else discore.ButtonStyle.secondary,
label=t(
f'settings.reply_method.original_author_replica.button.{l(self.reply_as_original_author_replica)}',
bot=self.bot.user.display_name),
custom_id='reply_as_original_author_replica'
)
edit_callback(original_author_replica_button, self.view, self.toggle_reply_as_original_author_replica)
return [reply_to_message_button, reply_silently_button, original_author_replica_button]

async def toggle_reply_to_message(self, view: SettingsView, interaction: discore.Interaction, _) -> None:
if self.reply_as_original_author_replica:
return
self.reply_to_message = not self.reply_to_message
self.ctx.guild.update({'reply_to_message': self.reply_to_message})
await view.refresh(interaction)
Expand All @@ -1184,6 +1208,16 @@ async def toggle_reply_silently(self, view: SettingsView, interaction: discore.I
self.ctx.guild.update({'reply_silently': self.reply_silently})
await view.refresh(interaction)

async def toggle_reply_as_original_author_replica(self, view: SettingsView, interaction: discore.Interaction, _) -> None:
self.reply_as_original_author_replica = not self.reply_as_original_author_replica
if self.reply_as_original_author_replica:
self.reply_to_message = False
self.ctx.guild.update({
'reply_as_original_author_replica': self.reply_as_original_author_replica,
'reply_to_message': self.reply_to_message
})
await view.refresh(interaction)


class WebhooksSetting(BaseSetting):
"""Represents the webhooks setting (respond to webhooks or not)"""
Expand Down
Loading