diff --git a/cogs/utils.py b/cogs/utils.py index 067ae9d8..4e022658 100644 --- a/cogs/utils.py +++ b/cogs/utils.py @@ -10,8 +10,8 @@ import time import datetime from api import auth -from framework.isobot import currency, embedengine, commands as cmds -from framework.isobot.db import levelling +from framework.isobot import currency, commands as cmds +from framework.isobot.db import levelling, embeds as _embeds from discord import option, ApplicationContext from discord.commands import SlashCommandGroup from discord.ext import commands @@ -22,7 +22,7 @@ currency = currency.CurrencyAPI("database/currency.json", "logs/currency.log") levelling = levelling.Levelling() _commands = cmds.Commands() -# openai.api_key = os.getenv("chatgpt_API_KEY") +_embeds = _embeds.Embeds() openai.api_key = auth.ext_token('chatgpt') chatgpt_conversation = dict() _presence = Presence() @@ -70,32 +70,6 @@ async def repo(self, ctx: ApplicationContext): ) await ctx.respond(embed=localembed) - @commands.slash_command( - name="embedbuilder", - description="Builds a custom embed however you want." - ) - @option(name="title", description="The title of the embed", type=str) - @option(name="description", description="The body of the embed", type=str) - @option(name="image_url", description="The main image you want to show for the embed (URL ONLY)", type=str, default=None) - @option(name="thumbnail_url", description="The thumbnail image you want to show for the embed (URL ONLY)", type=str, default=None) - @option(name="color", description="The embed's accent color (Use -1 for random color)", type=int, default=None) - @option(name="footer_text", description="The text at the footer of the embed", type=str, default=None) - @option(name="footer_icon_url", description="The icon you want to show in the embed's footer (URL ONLY)", type=str, default=None) - async def embedbuilder(self, ctx: ApplicationContext, title: str, description: str, image_url: str = None, thumbnail_url: str = None, color: int = None, footer_text: str = None, footer_icon_url: str = None): - """Builds a custom embed however you want.""" - await ctx.respond("Embed Built!", ephemeral=True) - await ctx.channel.send( - embed=embedengine.embed( - title, - description, - image=image_url, - thumbnail=thumbnail_url, - color=color, - footer_text=footer_text, - footer_img=footer_icon_url - ) - ) - @commands.slash_command( name='whoami', description='Shows information on a user.' @@ -368,6 +342,205 @@ async def serverinfo(self, ctx: ApplicationContext): ) await ctx.respond(embed=localembed) + # Server Embed System Manager Commands + embed_system = SlashCommandGroup("embeds", "Commands used to add, edit and remove custom bot embeds for server-wide use.") + + @embed_system.command( + name="create", + description="Create a new custom embed for the server." + ) + @commands.guild_only() + @option(name="embed_name", description="What do you want your embed's name to be?") + @option(name="title", description="Set a title for your embed", type=str, default=None) + @option(name="description", description="Set a description for your embed", type=str, default=None) + @option(name="color", description="Format: 0x{replace with hex code} (-1 = random color)", type=int, default=None) + @option(name="timestamp_enabled", description="Choose whether to display timestamps on the embed or not", type=bool, default=False) + @option(name="title_url", description="Attach a url to your embed title (https:// only)", type=str, default=None) + @option(name="image_url", description="Add an image to your embed (https:// only)", type=str, default=None) + @option(name="thumbnail", description="Add a thumbnail image to your embed (https:// only)", type=str, default=None) + async def embeds_create(self, ctx: ApplicationContext, embed_name: str, title: str = None, description: str = None, color: int = None, timestamp_enabled: bool = False, title_url: str = None, image_url: str = None, thumbnail: str = None): + """Create a new custom embed for the server.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + _embeds.generate_embed( + server_id=ctx.guild.id, + embed_name=embed_name, + title=title, + description=description, + color=color, + timestamp_enabled=timestamp_enabled, + title_url=title_url, + image_url=image_url, + thumbnail=thumbnail + ) + new_embed = _embeds.build_embed(ctx.guild.id, embed_name) + await ctx.respond( + f"## :white_check_mark: Embed Successfully Created.\nThe name of this custom embed is `{embed_name}`. You can use this embed name to reference this custom embed in other commands.\n\nHere's a preview of your new embed:", + embed=new_embed + ) + + @embed_system.command( + name="delete", + description="Delete a custom embed from the server." + ) + @commands.guild_only() + @option(name="embed_name", description="The specific embed you want to delete.", type=str) + async def embeds_delete(self, ctx: ApplicationContext, embed_name: str): + """Delete a custom embed from the server.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + result_code = _embeds.delete_embed(ctx.guild.id, embed_name=embed_name) + if result_code == 1: + localembed = discord.Embed( + title=":x: Failed to delete embed", + description=f"This server does not have an embed with the name `{embed_name}`.", + color=discord.Color.red() + ) + return await ctx.respond(embed=localembed, ephemeral=True) + else: + localembed = discord.Embed( + title=":white_check_mark: Custom embed successfully deleted", + description=f"The custom server embed `{embed_name}` has been deleted.\n\nMake sure to remove any references of this embed from any other serverconfig features in the bot.", + color=discord.Color.green() + ) + return await ctx.respond(embed=localembed) + + @embed_system.command( + name="list", + description="View a list of all the custom embeds in this server." + ) + @commands.guild_only() + async def embeds_list(self, ctx: ApplicationContext): + """View a list of all the custom embeds in this server.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + embeds_list = _embeds.get_embeds_list(ctx.guild.id) + num = 0 + parsed_output = str() + for embed in embeds_list.keys(): + num += 1 + parsed_output += f"{num}. `{embed}`\n" + if parsed_output == "": + parsed_output = "*Nothing to see here...*" + localembed = discord.Embed( + title=f"Custom Server Embeds for **{ctx.guild.name}**", + description=parsed_output + ) + localembed.set_footer(text="Run \"/embeds view \" to get a preview of an embed.") + await ctx.respond(embed=localembed) + + @embed_system.command( + name="view", + description="See a preview of an existing custom embed in the server." + ) + @commands.guild_only() + @option(name="embed_name", description="The server embed that you want to view.", type=str) + async def embeds_view(self, ctx: ApplicationContext, embed_name: str): + """See a preview of an existing custom embed in the server.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + embed_preview = _embeds.build_embed(ctx.guild.id, embed_name=embed_name) + await ctx.respond( + f"Here's a preview of the server embed `{embed_name}`:", + embed=embed_preview + ) + + @embed_system.command( + name="add_embed_field", + description="Appends a new field to an existing server embed." + ) + @commands.guild_only() + @option(name="embed_name", description="The server embed to which you want to add the field.", type=str) + @option(name="name", description="The name of the field.", type=str) + @option(name="value", description="The value of the field.", type=str) + @option(name="inline", description="Whether you want the field to be in-line or not.", type=bool, default=False) + async def embeds_add_embed_field(self, ctx: ApplicationContext, embed_name: str, name: str, value: str, inline: bool = False): + """Appends a new field to an existing server embed.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + result_code = _embeds.add_embed_field(ctx.guild.id, embed_name=embed_name, name=name, value=value, inline=inline) + if result_code == 1: + localembed = discord.Embed( + title=":x: Failed to add new field to embed", + description=f"This server does not have an embed with the name `{embed_name}`.", + color=discord.Color.red() + ) + return await ctx.respond(embed=localembed, ephemeral=True) + else: + localembed = _embeds.build_embed(ctx.guild.id, embed_name=embed_name) + await ctx.respond(f"## :white_check_mark: New field successfully added to server embed `{embed_name}`\nHere's a preview of your modified embed:", embed=localembed) + + @embed_system.command( + name="add_embed_author", + description="Add or replace the author section of an existing server embed." + ) + @commands.guild_only() + @option(name="embed_name", description="The server embed to which you want to add the author", type=str) + @option(name="author_name", description="The name of the author.", type=str) + @option(name="url", description="Attach a url to the author name. (https:// only)", type=str, default=None) + @option(name="icon_url", description="Add an icon next to the author (https:// only)", type=str, default=None) + async def embeds_add_embed_author(self, ctx: ApplicationContext, embed_name: str, author_name: str, url: str = None, icon_url: str = None): + """Add or replace the author section of an existing server embed.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + result_code = _embeds.add_embed_author(ctx.guild.id, embed_name=embed_name, name=author_name, url=url, icon_url=icon_url) + if result_code == 1: + localembed = discord.Embed( + title=":x: Failed to add author to embed", + description=f"This server does not have an embed with the name `{embed_name}`.", + color=discord.Color.red() + ) + return await ctx.respond(embed=localembed, ephemeral=True) + else: + localembed = _embeds.build_embed(ctx.guild.id, embed_name=embed_name) + await ctx.respond(f"## :white_check_mark: Author section successfully added to server embed `{embed_name}\nHere's a preview of your modified embed:`", embed=localembed) + + @embed_system.command( + name="add_embed_footer", + description="Add or replace the footer of an existing server embed." + ) + @commands.guild_only() + @option(name="embed_name", description="The server embed to which you want to add the author", type=str) + @option(name="text", description="The text you want to display in the footer.", type=str) + @option(name="icon_url", description="Add an icon in the footer of the embed. (https:// only)", type=str, default=None) + async def embeds_add_embed_author(self, ctx: ApplicationContext, embed_name: str, text: str, icon_url: str = None): + """Add or replace the footer of an existing server embed.""" + if not ctx.author.guild_permissions.manage_messages: + return await ctx.respond( + "You can't use this command! You need the `Manage Messages` permission in this server to run this command.", + ephemeral=True + ) + result_code = _embeds.add_embed_footer(ctx.guild.id, embed_name=embed_name, text=text, icon_url=icon_url) + if result_code == 1: + localembed = discord.Embed( + title=":x: Failed to add footer to embed", + description=f"This server does not have an embed with the name `{embed_name}`.", + color=discord.Color.red() + ) + return await ctx.respond(embed=localembed, ephemeral=True) + else: + localembed = _embeds.build_embed(ctx.guild.id, embed_name=embed_name) + await ctx.respond(f"## :white_check_mark: Footer section successfully added to server embed `{embed_name}\nHere's a preview of your modified embed:`", embed=localembed) + + # Isobot Command Registry Management System commandmanager = SlashCommandGroup("commandmanager", "Manage isobot's command registry.") @commandmanager.command( diff --git a/framework/isobot/db/embeds.py b/framework/isobot/db/embeds.py new file mode 100644 index 00000000..7979a81a --- /dev/null +++ b/framework/isobot/db/embeds.py @@ -0,0 +1,196 @@ +# Imports +import json +import discord +import datetime +import framework.types +from typing_extensions import Union +from framework.isobot.colors import Colors as colors + +# Functions +class Embeds(): + """Initializes the Embed database system.""" + def __init__(self): + print(f"[framework/db/Embeds] {colors.green}Embeds db library initialized.{colors.end}") + + def load(self) -> dict: + """Fetches and returns the latest data from the embeds database.""" + with open("database/embeds.json", 'r', encoding="utf8") as f: db = json.load(f) + return db + + def save(self, data: dict) -> int: + """Dumps all cached data to your local machine.""" + with open("database/embeds.json", 'w+', encoding="utf8") as f: json.dump(data, f) + return 0 + + def generate_server_key(self, server_id: Union[str, int]) -> int: + """Creates a new data key for the specified server in the embeds database.\n\nReturns `0` if successful, returns `1` if the server already exists in the database.""" + embeds = self.load() + if str(server_id) not in embeds.keys(): + embeds[str(server_id)] = {} + self.save(embeds) + return 0 + return 1 + + def get_embeds_list(self, server_id: Union[str, int]) -> dict: + """Fetches a `dict` of all the added embeds in the specified server.\n\nReturns an empty `dict` if none are set up.""" + embeds = self.load() + return embeds[str(server_id)] + + def generate_embed( + self, + server_id: Union[str, int], + embed_name: str, + *, + title: str = None, + description: str = None, + color: int = None, + timestamp_enabled: bool = False, + title_url: str = None, + image_url: str = None, + thumbnail: str = None + + ) -> int: + """Generates an embed for the specified server using the given embed data.\n\nReturns `0` if successful, returns `1` if an embed with the same embed name already exists, returns `2` if the specified embed color is not a valid hex code.""" + embeds = self.load() + + self.generate_server_key(server_id) + + if embed_name not in embeds[str(server_id)].keys(): + # Check whether color code is a valid value or not + #if color not in [-1, 0] or not framework.types.is_hex_color_code(color): + # return 2 + + embeds[str(server_id)][embed_name] = { + "title": title, + "description": description, + "color": color, # -1 = random color, None = no color, hex code = custom color + "timestamp_enabled": timestamp_enabled, + "title_url": title_url, + "image_url": image_url, + "thumbnail": thumbnail, + "fields": [], + "author": {}, + "footer": {} + } + self.save(embeds) + return 0 + else: return 1 + + def build_embed(self, server_id: Union[str, int], embed_name: str) -> discord.Embed: + """Builds a Discord embed from the specified server's embed data, and returns it as `discord.Embed`.\n\nReturns `1` if the embeds does not exist.""" + embeds = self.load() + if embed_name in embeds[str(server_id)].keys(): + embed_data = embeds[str(server_id)][embed_name] + + title = embed_data["title"] + description = embed_data["description"] + color = embed_data["color"] + title_url = embed_data["title_url"] + image_url = embed_data["image_url"] + thumbnail = embed_data["thumbnail"] + if embed_data["title"] == None: title = discord.Embed.Empty + if embed_data["description"] == None: description = discord.Embed.Empty + if embed_data["color"] == None: color = discord.Embed.Empty + if embed_data["title_url"] == None: title_url = discord.Embed.Empty + if embed_data["image_url"] == None: image_url = discord.Embed.Empty + if embed_data["thumbnail"] == None: thumbnail = discord.Embed.Empty + + embed = discord.Embed( + title=title, + description=description, + color = discord.Color.random() if embed_data["color"] == -1 else color, + url=title_url, + timestamp = datetime.datetime.utcnow() if embed_data["timestamp_enabled"] else discord.Embed.Empty + ) + embed.set_image(url=image_url) + embed.set_thumbnail(url=thumbnail) + + for field in embed_data["fields"]: + embed.add_field( + name=field["name"], + value=field["value"], + inline=field["inline"] + ) + + if embed_data["author"] != {}: + author_url = embed_data["author"]["url"] + author_icon_url = embed_data["author"]["icon_url"] + if embed_data["author"]["url"] == None: author_url = discord.Embed.Empty + if embed_data["author"]["icon_url"] == None: author_icon_url = discord.Embed.Empty + embed.set_author(name=embed_data["author"]["name"], url=author_url, icon_url=author_icon_url) + + if embed_data["footer"] != {}: + if embed_data["footer"]["icon_url"] == None: footer_icon_url = discord.Embed.Empty + embed.set_footer(text=embed_data["footer"]["text"], icon_url=footer_icon_url) + + return embed # Returning the embed data as output + else: return 1 + + def delete_embed(self, server_id: Union[str, int], embed_name: str) -> int: + """Deletes an existing embed from the specified server's embeds list.\n\nReturns `0` if successful, returns `1` if the embed does not exist.""" + embeds = self.load() + if embed_name in embeds[str(server_id)].keys(): + del embeds[str(server_id)][embed_name] + self.save(embeds) + return 0 + else: return 1 + + def add_embed_field( + self, + server_id: Union[str, int], + embed_name: str, + name: str, + value: str, + inline: bool = False + ) -> int: + """Adds a new field to an already existing embed.\n\nReturns `0` if successful, returns `1` if the embed does not exist.""" + embeds = self.load() + if embed_name in embeds[str(server_id)].keys(): + embeds[str(server_id)][embed_name]["fields"].append( + { + "name": name, + "value": value, + "inline": inline + } + ) + self.save(embeds) + return 0 + else: return 1 + + def add_embed_footer( + self, + server_id: Union[str, int], + embed_name: str, + text: str, + icon_url: str = None + ) -> int: + """Adds a footer to an already existing embed.\n\nReturns `0` if successful, returns `1` if the embed does not exist.""" + embeds = self.load() + if embed_name in embeds[str(server_id)].keys(): + embeds[str(server_id)][embed_name]["footer"] = { + "text": text, + "icon_url": icon_url + } + self.save(embeds) + return 0 + else: return 1 + + def add_embed_author( + self, + server_id: Union[str, int], + embed_name: str, + name: str, + url: str = None, + icon_url: str = None + ): + """Adds the author field to an already existing embed.\n\nReturns `0` if successful, returns `1` if the embed does not exist.""" + embeds = self.load() + if embed_name in embeds[str(server_id)].keys(): + embeds[str(server_id)][embed_name]["author"] = { + "name": name, + "url": url, + "icon_url": icon_url + } + self.save(embeds) + return 0 + else: return 1 diff --git a/framework/isobot/embedengine.py b/framework/isobot/embedengine.py deleted file mode 100644 index 9ddf9368..00000000 --- a/framework/isobot/embedengine.py +++ /dev/null @@ -1,25 +0,0 @@ -"""The library used for designing and outputting Discord embeds.""" - -import discord - -def embed(title: str, desc: str, *, image: str = None, thumbnail: str = None, color:int=None, footer_text: str = None, footer_img: str = None): - """Designs a Discord embed. - ----------------------------------------------------------- - The color argument is completely optional. - - If a color is set, it will return the embed in that color only. - - If the color is set as "rand", then it will return the embed with a random color. - - If a color is not set, it will appear as Discord's blurple embed color. - """ - if color == -1: color = discord.Color.random() - elif color == None: color = discord.Color.blurple() - local_embed = discord.Embed( - title=title, - description=desc, - colour=color - ) - if image is not None: local_embed.set_image(url=image) - if thumbnail is not None: local_embed.set_thumbnail(url=thumbnail) - if footer_text is not None and footer_img is not None: local_embed.set_footer(text=footer_text, icon_url=footer_img) - elif footer_text is not None: local_embed.set_footer(text=footer_text) - elif footer_img is not None: local_embed.set_footer(icon_url=footer_img) - return local_embed diff --git a/framework/types.py b/framework/types.py new file mode 100644 index 00000000..987629fb --- /dev/null +++ b/framework/types.py @@ -0,0 +1,5 @@ +"""An NKA Framework Module to check and verify various datatypes used across systems.""" + +def is_hex_color_code(num) -> bool: + """Checks whether an integer is a valid hexadecimal code.\n\nReturns `True` if valid, returns `False` if invalid.""" + return isinstance(num, int) and 0x000000 <= num <= 0xFFFFFF diff --git a/main.py b/main.py index 7c4421ba..9202b0d3 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ from random import randint from framework.isobot import currency, colors, settings, commands as _commands from framework.isobot.shop import ShopData -from framework.isobot.db import levelling, items, userdata, automod, weather, warnings, presence as _presence, serverconfig +from framework.isobot.db import levelling, items, userdata, automod, weather, warnings, presence as _presence, serverconfig, embeds from discord import ApplicationContext, option from discord.ext import commands from cogs.isocoin import create_isocoin_key @@ -53,6 +53,7 @@ def initial_setup(): "presence", "user_data", "weather", + "embeds", "isobank/accounts", "isobank/auth" ] @@ -122,6 +123,7 @@ def initial_setup(): automod = automod.Automod() _presence = _presence.Presence() weather = weather.Weather() +embeds = embeds.Embeds() _commands = _commands.Commands() shop_data = ShopData("config/shop.json") @@ -245,6 +247,7 @@ async def on_message(ctx): try: automod.generate(ctx.guild.id) serverconfig.generate(ctx.guild.id) + embeds.generate_server_key(ctx.guild.id) warningsdb.generate(ctx.guild.id, ctx.author.id) except AttributeError: pass