Skip to content

Instantly share code, notes, and snippets.

@imptype
Last active May 2, 2024 19:25
Show Gist options
  • Save imptype/fd24aed88d792899da109354def1e330 to your computer and use it in GitHub Desktop.
Save imptype/fd24aed88d792899da109354def1e330 to your computer and use it in GitHub Desktop.
embed maker command
"""
A quick embed maker command.
Supports Title, URL, Description, Color, Timestamp,
Author (name, icon, url), Footer (icon, name), Image (url), Thumbnail (url)
adding and inserting fields (name, value, inline, index),
removing fields (index),
clearing fields,
editing fields (select -> name, value, inline)
export to json (text file if > 2000 chars),
import to json (modal but limited to 4000 chars),
reset and done buttons.
"""
import io
import copy
import json
import datetime
import discord
from discord.ext import commands
from discord import app_commands
class InputModal(discord.ui.Modal):
def __init__(self, name, *text_inputs):
super().__init__(title = '{} Modal'.format(name), timeout = 300.0)
for text_input in text_inputs:
self.add_item(text_input)
self.done = False # prevents submitting same modal twice
async def on_submit(self, interaction):
if not self.done:
self.interaction = interaction
self.done = True
class EmbedMakerView(discord.ui.View):
def __init__(self, interaction, embed):
super().__init__()
self.interaction = interaction
self.embed = embed
self.embed_dict = copy.deepcopy(embed.to_dict()) # used for default values and reverting changes
self.embed_original = copy.deepcopy(self.embed_dict) # for resetting
self.update_fields(self.embed_original)
async def interaction_check(self, interaction):
if interaction.user == self.interaction.user:
return True
await interaction.response.send_message('This is not your interaction.', ephemeral = True)
return False
async def on_error(self, interaction, error, item):
embed = discord.Embed(
title = 'Edit failed',
description = '```fix\n{} \n```',
color = discord.Color.red()
)
if isinstance(error, discord.HTTPException):
embed.description = embed.description.format(error.text)
await self.interaction.followup.send(embed = embed, ephemeral = True)
elif isinstance(error, (ValueError, TypeError)): # .convert() failed, reuse the modal.interaction
embed.description = embed.description.format(str(error))
await error.interaction.response.send_message(embed = embed, ephemeral = True)
else:
#print('unhandled error:', interaction, error, item, error.__class__.__mro__, sep = '\n')
raise error
async def do(self, interaction, button, *text_inputs, method = None):
name = button.label.lower()
old_values = []
for text_input in text_inputs:
old = self.embed_dict.get(name, None)
if hasattr(text_input, 'key'):
if old:
old = old.get(text_input.key, None)
text_input.default = old
old_values.append(old)
modal = InputModal(button.label, *text_inputs)
await interaction.response.send_modal(modal)
timed_out = await modal.wait()
if timed_out or not modal.done:
return
new_values = []
for text_input in text_inputs:
new = text_input.value.strip()
if new:
if hasattr(text_input, 'convert'):
try:
new = text_input.convert(new)
except Exception as error:
error.interaction = modal.interaction
raise error
new_values.append(new)
else:
new_values.append(None)
if old_values == new_values:
return await modal.interaction.response.defer()
try:
if method:
kwargs = {
text_input.key : new
for text_input, new in zip(text_inputs, new_values)
}
getattr(self.embed, method)(**kwargs) # embed.set_author(name=...,url=...)
else:
setattr(self.embed, name, new_values[0]) # embed.title = ...
await modal.interaction.response.edit_message(embed = self.embed)
except Exception as error:
self.embed = discord.Embed.from_dict(copy.deepcopy(self.embed_dict))
raise error
self.embed_dict = copy.deepcopy(self.embed.to_dict())
def update_fields(self, embed_dict = None):
embed_dict = embed_dict or self.embed_dict
if 'fields' in embed_dict and embed_dict['fields']:
self.remove_field_button.disabled = False
self.clear_fields_button.disabled = False
self.edit_field_select.disabled = False
self.edit_field_select.options = [
discord.SelectOption(label = 'Field {}'.format(i+1), description = field['name'][:100])
for i, field in enumerate(embed_dict['fields'])
]
else:
self.remove_field_button.disabled = True
self.clear_fields_button.disabled = True
self.edit_field_select.disabled = True
@discord.ui.button(label = 'Title', style = discord.ButtonStyle.blurple)
async def title_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'The title of the embed.',
required = False
)
await self.do(interaction, button, text_input)
@discord.ui.button(label = 'URL', style = discord.ButtonStyle.blurple)
async def url_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'The URL of the embed.',
required = False
)
await self.do(interaction, button, text_input)
@discord.ui.button(label = 'Description', style = discord.ButtonStyle.blurple)
async def description_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'The description of the embed.',
style = discord.TextStyle.long,
required = False
)
await self.do(interaction, button, text_input)
@discord.ui.button(label = 'Color', style = discord.ButtonStyle.blurple)
async def color_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'A hex string like "#ffab12" or a number <= 16777215.',
required = False
)
text_input.convert = lambda x: int(x) if x.isnumeric() else int(x.lstrip('#'), base = 16)
await self.do(interaction, button, text_input)
@discord.ui.button(label = 'Timestamp', style = discord.ButtonStyle.blurple)
async def timestamp_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'A unix timestamp or a number like "1659876635".',
required = False
)
def convert(x):
try:
return datetime.datetime.fromtimestamp(int(x))
except:
return datetime.datetime.strptime(x, '%Y-%m-%dT%H:%M:%S%z') # 1970-01-02T10:12:03+00:00
text_input.convert = convert
await self.do(interaction, button, text_input)
@discord.ui.button(label = 'Author', style = discord.ButtonStyle.blurple)
async def author_button(self, interaction, button):
name_input = discord.ui.TextInput(
label = 'Author Name',
placeholder = 'The name of the author.',
required = False
)
name_input.key = 'name'
url_input = discord.ui.TextInput(
label = 'Author URL',
placeholder = 'The URL for the author.',
required = False
)
url_input.key = 'url'
icon_input = discord.ui.TextInput(
label = 'Author Icon URL',
placeholder = 'The URL for the author icon.',
required = False
)
icon_input.key = 'icon_url'
text_inputs = [name_input, url_input, icon_input]
await self.do(interaction, button, *text_inputs, method = 'set_author')
@discord.ui.button(label = 'Thumbnail', style = discord.ButtonStyle.blurple)
async def thumbnail_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'The source URL for the thumbnail.',
required = False
)
text_input.key = 'url'
await self.do(interaction, button, text_input, method = 'set_thumbnail')
@discord.ui.button(label = 'Image', style = discord.ButtonStyle.blurple)
async def image_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = button.label,
placeholder = 'The source URL for the image.',
required = False
)
text_input.key = 'url'
await self.do(interaction, button, text_input, method = 'set_image')
@discord.ui.button(label = 'Footer', style = discord.ButtonStyle.blurple)
async def footer_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = 'Footer Text',
placeholder = 'The footer text.',
required = False
)
text_input.key = 'text'
icon_input = discord.ui.TextInput(
label = 'Footer Icon URL',
placeholder = 'The URL of the footer icon.',
required = False
)
icon_input.key = 'icon_url'
text_inputs = [text_input, icon_input]
await self.do(interaction, button, *text_inputs, method = 'set_footer')
@discord.ui.button(label = 'Add Field', style = discord.ButtonStyle.blurple)
async def add_field_button(self, interaction, button):
name_input = discord.ui.TextInput(
label = 'Field Name',
placeholder = 'The name of the field.'
)
value_input = discord.ui.TextInput(
label = 'Field Value',
placeholder = 'The value of the field.',
style = discord.TextStyle.long
)
inline_input = discord.ui.TextInput(
label = 'Field Inline (Optional)',
placeholder = 'Type "1" for inline, otherwise not inline.',
required = False
)
index_input = discord.ui.TextInput(
label = 'Field Index (Optional)',
placeholder = 'Insert before field(n+1), default at the end.',
required = False
)
text_inputs = [name_input, value_input, inline_input, index_input]
modal = InputModal(button.label, *text_inputs)
await interaction.response.send_modal(modal)
timed_out = await modal.wait()
if timed_out or not modal.done:
return
inline = inline_input.value.strip() == '1'
index = None
if index_input.value.strip().isnumeric():
index = int(index_input.value)
embed = discord.Embed.from_dict(copy.deepcopy(self.embed_dict))
kwargs = {
'name' : name_input.value,
'value' : value_input.value,
'inline' : inline
}
if index or index == 0:
kwargs['index'] = index
embed.insert_field_at(**kwargs)
else:
embed.add_field(**kwargs)
if embed == self.embed:
return await modal.interaction.response.defer()
self.update_fields(embed.to_dict())
try:
await modal.interaction.response.edit_message(embed = embed, view = self)
except Exception as error:
self.update_fields()
raise error
self.embed = embed
self.embed_dict = copy.deepcopy(embed.to_dict())
@discord.ui.select(placeholder = 'Edit Field', options = [discord.SelectOption(label = 'invisible option', description = 'because options cant be empty')])
async def edit_field_select(self, interaction, select):
index = int(select.values[0].lstrip('Field ')) - 1
field = self.embed_dict['fields'][index]
name_input = discord.ui.TextInput(
label = 'Field Name',
placeholder = 'The name of the field.',
default = field['name']
)
value_input = discord.ui.TextInput(
label = 'Field Value',
placeholder = 'The value of the field.',
style = discord.TextStyle.long,
default = field['value']
)
inline_input = discord.ui.TextInput(
label = 'Field Inline (Optional)',
placeholder = 'Type "1" for inline, otherwise not inline.',
required = False,
default = str(int(field['inline']))
)
text_inputs = [name_input, value_input, inline_input]
modal = InputModal(select.placeholder, *text_inputs)
await interaction.response.send_modal(modal)
timed_out = await modal.wait()
if timed_out or not modal.done:
return
inline = inline_input.value.strip() == '1'
kwargs = {
'index' : index,
'name' : name_input.value,
'value' : value_input.value,
'inline' : inline
}
embed = discord.Embed.from_dict(copy.deepcopy(self.embed_dict))
embed.set_field_at(**kwargs)
if embed == self.embed:
return await modal.interaction.response.defer()
self.update_fields(embed.to_dict())
try:
await modal.interaction.response.edit_message(embed = embed, view = self)
except Exception as error:
self.update_fields()
raise error
self.embed = embed
self.embed_dict = copy.deepcopy(embed.to_dict())
@discord.ui.button(label = 'Remove Field', style = discord.ButtonStyle.blurple)
async def remove_field_button(self, interaction, button):
# ?tag no modal selects
text_input = discord.ui.TextInput(
label = 'Field Index (Optional)',
placeholder = 'Remove field(n+1), default removes last field.',
required = False
)
modal = InputModal(button.label, text_input)
await interaction.response.send_modal(modal)
timed_out = await modal.wait()
if timed_out or not modal.done:
return
if text_input.value.strip().isnumeric():
index = int(text_input.value)
else:
index = len(self.embed_dict['fields']) - 1
embed = discord.Embed.from_dict(copy.deepcopy(self.embed_dict))
embed.remove_field(index)
if embed == self.embed:
return await modal.interaction.response.defer()
self.embed = embed
self.embed_dict = copy.deepcopy(embed.to_dict())
self.update_fields()
await modal.interaction.response.edit_message(embed = embed, view = self)
@discord.ui.button(label = 'Clear Fields', style = discord.ButtonStyle.red)
async def clear_fields_button(self, interaction, button):
self.embed.clear_fields()
self.embed_dict = copy.deepcopy(self.embed.to_dict())
self.update_fields()
await interaction.response.edit_message(embed = self.embed, view = self)
@discord.ui.button(label = 'Reset', style = discord.ButtonStyle.red)
async def reset_button(self, interaction, button):
if self.embed_original == self.embed_dict:
return await interaction.response.defer()
self.embed = discord.Embed.from_dict(copy.deepcopy(self.embed_original))
self.embed_dict = copy.deepcopy(self.embed_original)
self.update_fields(self.embed_original)
await interaction.response.edit_message(embed = self.embed, view = self)
@discord.ui.button(label = 'Import JSON', style = discord.ButtonStyle.green)
async def import_button(self, interaction, button):
text_input = discord.ui.TextInput(
label = 'JSON',
placeholder = 'Paste JSON data here.',
style = discord.TextStyle.long
#more than 4000 characters needs files/pastebin
)
modal = InputModal(button.label, text_input)
await interaction.response.send_modal(modal)
timed_out = await modal.wait()
if timed_out:
return
try:
embed_dict = json.loads(text_input.value)
except Exception as error:
error.interaction = modal.interaction
raise error
embed = discord.Embed.from_dict(embed_dict)
if embed == self.embed:
return await modal.interaction.response.defer()
await modal.interaction.response.edit_message(embed = embed)
self.embed = embed
self.embed_dict = copy.deepcopy(embed.to_dict()) # no user keys
@discord.ui.button(label = 'Export JSON', style = discord.ButtonStyle.green)
async def export_button(self, interaction, button):
data = json.dumps(self.embed_dict, indent = 2)
text = '```json\n{}```'
if len(data) > 2000 - len(text) + 2:
await interaction.response.send_message(
file = discord.File(io.StringIO(data), filename = 'embedmaker.json'),
ephemeral = True
)
else:
await interaction.response.send_message(text.format(data), ephemeral = True)
@discord.ui.button(label = 'Stop', style = discord.ButtonStyle.red)
async def stop_button(self, interaction, button):
await interaction.response.edit_message(view = None)
self.stop()
class EmbedMaker(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command()
@app_commands.guild_only()
async def embedmaker(self, interaction):
"""Interactively makes an embed from scratch"""
embed = discord.Embed(
title = 'Embed Maker!',
#description = 'Click the buttons below to edit this embed.'
)
view = EmbedMakerView(interaction, embed)
await interaction.response.send_message(embed = embed, view = view)
timed_out = await view.wait()
if timed_out:
message = await interaction.original_message()
await message.edit(view = None)
async def setup(bot):
await bot.add_cog(EmbedMaker(bot))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment