Heimdallr/heimdallr/commands/bot_messages.py

216 lines
7.0 KiB
Python

from copy import deepcopy
import json
from json.decoder import JSONDecodeError
import logging
from naff import (
Client,
Extension,
slash_command,
InteractionContext,
Modal,
ParagraphText,
ModalContext,
context_menu,
Permissions,
CommandTypes,
Message,
)
from database import BotMessages as BotMessagesModel
# Template modal for creating/editing bot messages.
message_creation_modal = Modal(
custom_id="bot-message-create",
title="Create a message as the bot",
components=[
ParagraphText(
custom_id="embeds",
label="Embeds",
placeholder="Embeds as JSON",
required=False,
),
ParagraphText(
custom_id="content",
label="Text",
placeholder="Lorem ipsum dolor sit amet and so on",
required=False,
),
]
)
class BotMessages(Extension):
def __init__(self, client: Client) -> None:
self.client = client
# Create a new bot message.
@slash_command(
name="bot-message-create",
description="Create a message as the bot.",
dm_permission=False,
default_member_permissions=Permissions.MANAGE_GUILD,
)
async def bot_message_create_command(self, ctx: InteractionContext):
# Respond with the template modal. No values have been set in the modal, as it is
# a new message.
await ctx.send_modal(message_creation_modal)
# Wait for the user to submit the modal, and ensure that we are receiving the
# correct modal.
modal_ctx: ModalContext = await self.client.wait_for_modal(message_creation_modal, author=ctx.author)
if modal_ctx.custom_id != "bot-message-create":
return
# Retrieve the values from the modal.
# Ensure that at least one of the fields is filled in, as a message cannot be
# empty.
embeds_string: str = modal_ctx.responses["embeds"]
content_string: str = modal_ctx.responses["content"]
if (
(embeds_string is None or embeds_string == "")
and (content_string is None or content_string == "")
):
await modal_ctx.send(
"You must provide either an embed or text.",
ephemeral=True,
)
return
# Attempt to parse the embed(s) JSON into a Python object.
# Try loading it as a single or multiple embeds depending on if the object
# returned is a list or a dictionary.
# If the JSON is invalid, return an error.
embed: dict | None = None
embeds: list | None = None
try:
if embeds_string:
embeds_temp = json.loads(embeds_string)
if isinstance(embeds_temp, list):
embeds = embeds_temp
elif isinstance(embeds_temp, dict):
embed = embeds_temp
except JSONDecodeError:
await modal_ctx.send(
"The embeds were not valid JSON.",
ephemeral=True,
)
return
# Send the bot message in the channel.
msg = await ctx.channel.send(
content=content_string if content_string else None,
embed=embed,
embeds=embeds,
)
# Add an entry of the message in the database.
BotMessagesModel.create(
guild_id=msg.guild.id,
channel_id=msg.channel.id,
message_id=msg.id,
)
# Send a confirmation message, as Discord requires us to respond to the interaction.
await modal_ctx.send(
"Message created!",
ephemeral=True,
)
# A context menu to allow moderators to edit a bot message.
@context_menu(
name="Edit bot message",
context_type=CommandTypes.MESSAGE,
dm_permission=False,
default_member_permissions=Permissions.MANAGE_GUILD,
)
async def edit_bot_message_context_menu(self, ctx: InteractionContext):
message: Message = ctx.target
# Ensure that the target message is from the bot.
# If it is not, return an error.
if message.author.id != self.client.user.id:
await ctx.send(
"This is not a bot message.",
ephemeral=True,
)
return
# Retrieve the bot message from the database, if any.
bot_message: BotMessagesModel | None = BotMessagesModel.get_or_none(
BotMessagesModel.guild_id == message.channel.guild.id,
BotMessagesModel.channel_id == message.channel.id,
BotMessagesModel.message_id == message.id,
)
# If there is no bot message, return an error.
if bot_message is None:
await ctx.send(
"This is not an editable bot message.",
ephemeral=True,
)
return
# Create a copy of the template modal, and insert the contents of the bot message.
modal = deepcopy(message_creation_modal)
modal.title = "Edit bot message"
modal.custom_id = "bot-message-edit"
modal.components[0].value = json.dumps(
[e.to_dict() for e in message.embeds] if message.embeds else "",
indent=4,
)
modal.components[1].value = message.content
# Send the modal to the user
await ctx.send_modal(modal)
# Wait for the user to submit the modal, and ensure that we are receiving the
# correct modal.
modal_ctx: ModalContext = await self.client.wait_for_modal(modal, author=ctx.author)
if modal_ctx.custom_id != "bot-message-edit":
return
embeds_string: str = modal_ctx.responses["embeds"]
content_string: str = modal_ctx.responses["content"]
if (
(embeds_string is None or embeds_string == "")
and (content_string is None or content_string == "")
):
await modal_ctx.send(
"You must provide either an embed or text.",
ephemeral=True,
)
return
embed: dict | None = None
embeds: list | None = None
try:
if embeds_string:
embeds_temp = json.loads(embeds_string)
if isinstance(embeds_temp, list):
embeds = embeds_temp
elif isinstance(embeds_temp, dict):
embed = embeds_temp
except JSONDecodeError:
await modal_ctx.send(
"The embeds were not valid JSON.",
ephemeral=True,
)
return
await message.edit(
content=content_string if content_string else None,
embed=embed,
embeds=embeds,
)
await modal_ctx.send(
"Message edited!",
ephemeral=True,
)
def setup(client: Client):
BotMessagesModel.create_table()
BotMessages(client)
logging.info("BotMessages extension loaded")