From 9e1729aa290501e5a3da546455fe68e8c13ce212 Mon Sep 17 00:00:00 2001 From: Parker Date: Fri, 2 Aug 2024 21:37:00 -0500 Subject: [PATCH 1/4] Change config validation - make more stuff optional --- .gitignore | 3 +- README.md | 2 + code/bot.py | 59 +++++-- code/cogs/help.py | 3 +- code/cogs/lyrics.py | 78 +++++++++ code/cogs/play.py | 8 + code/utils/config.py | 368 +++++++++++++++++++++---------------------- config.ini.example | 18 --- config.yaml.example | 0 docker-compose.yaml | 16 +- requirements.txt | 5 +- 11 files changed, 329 insertions(+), 231 deletions(-) create mode 100644 code/cogs/lyrics.py delete mode 100644 config.ini.example create mode 100644 config.yaml.example diff --git a/.gitignore b/.gitignore index e62392f..4b18ce9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ config.ini __pycache__ count.db -notes.txt \ No newline at end of file +notes.txt +config.yaml \ No newline at end of file diff --git a/README.md b/README.md index 3eaa371..3011aa3 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ FEEDBACK_CHANNEL_ID | `CHANNEL ID`: Discord channel for feedback messages to be BUG_CHANNEL_ID | `CHANNEL ID`: Discord channel for bug messages to be sent to | **OPTIONAL** SPOTIFY_CLIENT_ID | Client ID from Spotify Developer account | **REQUIRED** SPOTIFY_CLIENT_SECRET | Client Secret from Spotify Developer account | **REQUIRED** +GENIUS_CLIENT_ID | Client ID from Genius API Dashboard | **OPTIONAL** +GENIUS_CLIENT_SECRET | Client Secret from Genius API Dashboard | **OPTIONAL** OPENAI_API_KEY | API Key from OpenAI for autoplay recommendations | **REQUIRED** HOST | Host address for your Lavalink node | **REQUIRED** PORT | Port for your Lavalink node | **REQUIRED** diff --git a/code/bot.py b/code/bot.py index 2864e85..3569e68 100644 --- a/code/bot.py +++ b/code/bot.py @@ -3,6 +3,7 @@ from discord.ext import commands, tasks import os import requests import openai +import lyricsgenius import utils.config as config from utils.command_tree import Tree @@ -19,24 +20,38 @@ class MyBot(commands.Bot): ) async def setup_hook(self): + # Get Spotify, Apple Music, Genius, and OpenAI access tokens/clients get_access_token.start() refresh_media_api_key.start() + login_genius.start() + if config.OPENAI_API_KEY: + bot.openai = openai.OpenAI(api_key=config.OPENAI_API_KEY) + config.LOG.info("Loading cogs...") for ext in os.listdir("./code/cogs"): if ext.endswith(".py"): + # Load the OPTIONAL feedback cog if ext[:-3] == "feedback" and config.FEEDBACK_CHANNEL_ID == None: config.LOG.info("Skipped loading feedback cog - channel ID not provided") continue + # Load the OPTIONAL bug cog if ext[:-3] == "bug" and config.BUG_CHANNEL_ID == None: config.LOG.info("Skipped loading bug cog - channel ID not provided") continue + # Load the OPTIONAL lyrics cog + if ext[:-3] == "lyrics" and config.GENIUS_CLIENT_ID == None: + config.LOG.info("Skipped loading lyrics cog - Genius API credentials not provided") + continue + # Load the OPTIONAL autoplay cog + if ext[:-3] == "autoplay" and config.OPENAI_API_KEY == None: + config.LOG.info("Skipped loading autoplay cog - OpenAI API credentials not provided") + continue + await self.load_extension(f"cogs.{ext[:-3]}") for ext in os.listdir("./code/cogs/owner"): if ext.endswith(".py"): await self.load_extension(f"cogs.owner.{ext[:-3]}") - bot.openai = openai.OpenAI(api_key=config.OPENAI_API_KEY) - async def on_ready(self): config.LOG.info(f"{bot.user} has connected to Discord.") config.LOG.info(f"Startup complete. Sync slash commands by DMing the bot {bot.command_prefix}tree sync (guild id)") @@ -50,15 +65,19 @@ bot.autoplay = [] # guild_id, guild_id, etc. @tasks.loop(minutes=45) async def get_access_token(): - auth_url = "https://accounts.spotify.com/api/token" - data = { - "grant_type": "client_credentials", - "client_id": config.SPOTIFY_CLIENT_ID, - "client_secret": config.SPOTIFY_CLIENT_SECRET, - } - response = requests.post(auth_url, data=data) - access_token = response.json()["access_token"] - bot.spotify_headers = {"Authorization": f"Bearer {access_token}"} + if config.SPOTIFY_CLIENT_ID and config.SPOTIFY_CLIENT_SECRET: + auth_url = "https://accounts.spotify.com/api/token" + data = { + "grant_type": "client_credentials", + "client_id": config.SPOTIFY_CLIENT_ID, + "client_secret": config.SPOTIFY_CLIENT_SECRET, + } + response = requests.post(auth_url, data=data) + if response.status_code == 200: + access_token = response.json()["access_token"] + bot.spotify_headers = {"Authorization": f"Bearer {access_token}"} + else: + bot.spotify_headers = None @tasks.loop(hours=24) @@ -73,6 +92,24 @@ async def refresh_media_api_key(): bot.apple_headers = None +@tasks.loop(hours=1) +async def login_genius(): + if config.GENIUS_CLIENT_ID and config.GENIUS_CLIENT_SECRET: + auth_url = "https://api.genius.com/oauth/token" + data = { + "client_id": config.GENIUS_CLIENT_ID, + "client_secret": config.GENIUS_CLIENT_SECRET, + "grant_type": "client_credentials", + } + response = requests.post(auth_url, data=data) + if response.status_code == 200: + access_token = response.json()["access_token"] + bot.genius = lyricsgenius.Genius(access_token) + bot.genius.verbose = False + else: + bot.genius = None + + if __name__ == "__main__": config.load_config() bot.run(config.TOKEN) diff --git a/code/cogs/help.py b/code/cogs/help.py index 3746482..a15d6d2 100644 --- a/code/cogs/help.py +++ b/code/cogs/help.py @@ -88,7 +88,8 @@ commands_and_descriptions = { class HelpView(discord.ui.View): def __init__(self, timeout=180.0): super().__init__(timeout=timeout) - self.add_item(discord.ui.Button(label="Invite Me", url=BOT_INVITE_LINK, row=1)) + if BOT_INVITE_LINK: + self.add_item(discord.ui.Button(label="Invite Me", url=BOT_INVITE_LINK, row=1)) @discord.ui.button( label="View All Commands", style=discord.ButtonStyle.green, row=1 diff --git a/code/cogs/lyrics.py b/code/cogs/lyrics.py new file mode 100644 index 0000000..711084f --- /dev/null +++ b/code/cogs/lyrics.py @@ -0,0 +1,78 @@ +import discord +import datetime +from discord import app_commands +from discord.ext import commands +from cogs.music import Music + +from utils.config import BOT_COLOR + + +class Lyrics(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command() + @app_commands.check(Music.create_player) + async def lyrics(self, interaction: discord.Interaction): + "Get lyrics for the song that is currently playing" + player = self.bot.lavalink.player_manager.get(interaction.guild.id) + + # If the Genius API client is not setup, send an error message + if self.bot.genius is not None: + embed = discord.Embed( + title="Lyrics Feature Error", + description="The lyrics feature is currently disabled due to errors with the Genius API.", + color=BOT_COLOR, + ) + embed.set_footer( + text=datetime.datetime.now(datetime.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + + # Search for the songs lyrics with Genius + song = self.bot.genius.search_song(player.current.title, player.current.author) + + # If no lyrics are found, send an error message + if song is None: + embed = discord.Embed( + title="Lyrics Not Found", + description="Unfortunately, I wasn't able to find any lyrics for the song that is currently playing.", + color=BOT_COLOR, + ) + embed.set_thumbnail(url=player.current.artwork_url) + embed.set_footer( + text=datetime.datetime.now(datetime.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + + return await interaction.response.send_message(embed=embed, ephemeral=True) + + # Remove unwanted text + lyrics = song.lyrics + lyrics = lyrics.split(" Lyrics", 1)[-1] + lyrics = lyrics.replace("You might also like", "\n") + lyrics = lyrics[:-7] + + embed = discord.Embed( + title=f"Lyrics for {player.current.title} by {player.current.author}", + description=lyrics, + color=BOT_COLOR, + ) + embed.set_thumbnail(url=player.current.artwork_url) + embed.set_footer( + text=datetime.datetime.now(datetime.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Lyrics(bot)) diff --git a/code/cogs/play.py b/code/cogs/play.py index db21cf7..fdd0233 100644 --- a/code/cogs/play.py +++ b/code/cogs/play.py @@ -147,6 +147,14 @@ class Play(commands.Cog): ### elif "open.spotify.com" in query: + if not self.bot.spotify_headers: + embed = discord.Embed( + title="Spotify Error", + description="Spotify support seems to be broken at the moment. Please try again and fill out a bug report with if this continues to happen.", + color=BOT_COLOR, + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + embed = discord.Embed(color=BOT_COLOR) if "open.spotify.com/playlist" in query: diff --git a/code/utils/config.py b/code/utils/config.py index f319792..9fdcd1d 100644 --- a/code/utils/config.py +++ b/code/utils/config.py @@ -1,10 +1,13 @@ -import configparser +import jsonschema import re import os +import yaml import validators +import openai import sys import discord import logging +import requests from colorlog import ColoredFormatter log_level = logging.DEBUG @@ -30,230 +33,225 @@ FEEDBACK_CHANNEL_ID = None BUG_CHANNEL_ID = None SPOTIFY_CLIENT_ID = None SPOTIFY_CLIENT_SECRET = None +GENIUS_CLIENT_ID = None +GENIUS_CLIENT_SECRET = None OPENAI_API_KEY = None LAVALINK_HOST = None LAVALINK_PORT = None LAVALINK_PASSWORD = None -""" -Load the config.ini file and return the contents for validation or -create a new templated config.ini file if it doesn't exist. -""" +schema = { + "type": "object", + "properties": { + "bot_info": { + "type": "object", + "properties": { + "token": {"type": "string"}, + "bot_color": {"type": "string"}, + "bot_invite_link": {"type": "string"}, + "feedback_channel_id": {"type": "integer"}, + "bug_channel_id": {"type": "integer"}, + }, + "required": ["token"], + }, + "spotify": { + "type": "object", + "properties": { + "spotify_client_id": {"type": "string"}, + "spotify_client_secret": {"type": "string"}, + }, + "required": ["spotify_client_id", "spotify_client_secret"], + }, + "genius": { + "type": "object", + "properties": { + "genius_client_id": {"type": "string"}, + "genius_client_secret": {"type": "string"}, + }, + "required": ["genius_client_id", "genius_client_secret"], + }, + "openai": { + "type": "object", + "properties": { + "openai_api_key": {"type": "string"}, + }, + "required": ["openai_api_key"], + }, + "lavalink": { + "type": "object", + "properties": { + "host": {"type": "string"}, + "port": {"type": "integer"}, + "password": {"type": "string"}, + }, + "required": ["host", "port", "password"], + }, + }, + "required": ["bot_info", "lavalink"], +} +# Attempt to load the config file, otherwise create a new template def load_config(): - # Look for variables in the environment - if "TOKEN" in os.environ or "BOT_COLOR" in os.environ or "BOT_INVITE_LINK" in os.environ: - LOG.info("Detected environment variables. Checking for configuration options.") - return validate_env_vars() + if os.path.exists("/.dockerenv"): + file_path = "/config/config.yaml" else: - LOG.info("Detected local environment. Checking for config.ini file.") + file_path = "config.yaml" try: - with open("config.ini", "r") as f: + with open(file_path, "r") as f: file_contents = f.read() validate_config(file_contents) except FileNotFoundError: - config = configparser.ConfigParser() - config["BOT_INFO"] = { - "TOKEN": "", - "BOT_COLOR": "", - "BOT_INVITE_LINK": "", - "FEEDBACK_CHANNEL_ID": "", - "BUG_CHANNEL_ID": "", - } + # Create a new config.yaml file with the template + with open("config.yaml", "w") as f: + f.write( + """ +bot_info: + token: "" + bot_color: "" + bot_invite_link: "" + feedback_channel_id: "" + bug_channel_id: "" - config["SPOTIFY"] = { - "SPOTIFY_CLIENT_ID": "", - "SPOTIFY_CLIENT_SECRET": "", - } +spotify: + spotify_client_id: "" + spotify_client_secret: "" - config["OPENAI"] = { - "OPENAI_API_KEY": "", - } +genius: + genius_client_id: "" + genius_client_secret: "" - config["LAVALINK"] = { - "HOST": "", - "PORT": "", - "PASSWORD": "", - } +openai: + openai_api_key: "" - with open("config.ini", "w") as configfile: - config.write(configfile) +lavalink: + host: "" + port: "" + password: "" + """ + ) sys.exit( LOG.critical( - "Configuration file `config.ini` has been generated. Please fill out all of the necessary information. Refer to the docs for information on what a specific configuration option is." + "Configuration file `config.yaml` has been generated. Please fill out all of the necessary information. Refer to the docs for information on what a specific configuration option is." ) ) -""" -Validate all of the options in the config.ini file. -""" - - +# Thouroughly validate all of the options in the config.yaml file def validate_config(file_contents): - global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD - config = configparser.ConfigParser() - config.read_string(file_contents) + global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, GENIUS_CLIENT_ID, GENIUS_CLIENT_SECRET, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD + config = yaml.safe_load(file_contents) + try: + jsonschema.validate(config, schema) + except jsonschema.ValidationError as e: + sys.exit(LOG.critical(f"Error in config.yaml file: {e.message}")) + + # + # Begin validation for optional BOT_INFO values + # + + # If there is a "bot_invite_link" option, make sure it's a valid URL + if "bot_invite_link" in config["bot_info"]: + if not validators.url(config["bot_info"]["bot_invite_link"]): + LOG.critical( + "Error in config.yaml file: bot_invite_link is not a valid URL" + ) + else: + BOT_INVITE_LINK = config["bot_info"]["bot_invite_link"] + + # Make sure "bot_color" is a valid hex color hex_pattern_one = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" hex_pattern_two = "^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" - errors = 0 - - # Make sure all sections are present - if ["BOT_INFO", "SPOTIFY", "OPENAI", "LAVALINK"] != config.sections(): - sys.exit( + if "bot_color" in config["bot_info"]: + if not bool( + re.match(hex_pattern_one, config["bot_info"]["bot_color"]) + ) and not bool(re.match(hex_pattern_two, config["bot_info"]["bot_color"])): LOG.critical( - "Missing sections in config.ini file. Delete the file and re-run the bot to generate a blank config.ini file." + "Error in config.yaml file: bot_color is not a valid hex color" ) - ) - - if ["token","bot_color","bot_invite_link", "feedback_channel_id","bug_channel_id",] != config.options("BOT_INFO"): - sys.exit( - LOG.critical( - "Missing options in BOT_INFO section of config.ini file. Delete the file and re-run the bot to generate a blank config.ini file." - ) - ) - - if ["spotify_client_id", "spotify_client_secret"] != config.options("SPOTIFY"): - sys.exit( - LOG.critical( - "Missing options in SPOTIFY section of config.ini file. Delete the file and re-run the bot to generate a blank config.ini file." - ) - ) - - if ["openai_api_key"] != config.options("OPENAI"): - sys.exit( - LOG.critical( - "Missing options in OPENAI section of config.ini file. Delete the file and re-run the bot to generate a blank config.ini file." - ) - ) - - if ["host", "port", "password"] != config.options("LAVALINK"): - sys.exit( - LOG.critical( - "Missing options in LAVALINK section of config.ini file. Delete the file and re-run the bot to generate a blank config.ini file." - ) - ) - - # Make sure BOT_COLOR is a valid hex color - if not bool(re.match(hex_pattern_one, config["BOT_INFO"]["BOT_COLOR"])) and not bool(re.match(hex_pattern_two, config["BOT_INFO"]["BOT_COLOR"])): - LOG.error("BOT_COLOR is not a valid hex color.") - errors += 1 - else: - BOT_COLOR = discord.Color(int((config["BOT_INFO"]["BOT_COLOR"]).replace("#", ""), 16)) - - # Make sure BOT_INVITE_LINK is a valid URL - if not validators.url(config["BOT_INFO"]["BOT_INVITE_LINK"]): - LOG.error("BOT_INVITE_LINK is not a valid URL.") - errors += 1 - else: - BOT_INVITE_LINK = config["BOT_INFO"]["BOT_INVITE_LINK"] - - # Make sure FEEDBACK_CHANNEL_ID is either exactly 0 or 19 characters long - if len(config["BOT_INFO"]["FEEDBACK_CHANNEL_ID"]) != 0: - if len(config["BOT_INFO"]["FEEDBACK_CHANNEL_ID"]) != 19: - LOG.error("FEEDBACK_CHANNEL_ID is not a valid Discord channel ID.") - errors += 1 else: - FEEDBACK_CHANNEL_ID = int(config["BOT_INFO"]["FEEDBACK_CHANNEL_ID"]) - - # Make sure BUG_CHANNEL_ID is either exactly 0 or 19 characters long - if len(config["BOT_INFO"]["BUG_CHANNEL_ID"]) != 0: - if len(config["BOT_INFO"]["BUG_CHANNEL_ID"]) != 19: - LOG.error("BUG_CHANNEL_ID is not a valid Discord channel ID.") - errors += 1 - else: - BUG_CHANNEL_ID = int(config["BOT_INFO"]["BUG_CHANNEL_ID"]) - - # Assign the rest of the variables - TOKEN = config["BOT_INFO"]["TOKEN"] - SPOTIFY_CLIENT_ID = config["SPOTIFY"]["SPOTIFY_CLIENT_ID"] - SPOTIFY_CLIENT_SECRET = config["SPOTIFY"]["SPOTIFY_CLIENT_SECRET"] - OPENAI_API_KEY = config["OPENAI"]["OPENAI_API_KEY"] - LAVALINK_HOST = config["LAVALINK"]["HOST"] - LAVALINK_PORT = config["LAVALINK"]["PORT"] - LAVALINK_PASSWORD = config["LAVALINK"]["PASSWORD"] - - if errors > 0: - sys.exit( - LOG.critical( - f"Found {errors} error(s) in the config.ini file. Please fix them and try again." + BOT_COLOR = discord.Color( + int((config["bot_info"]["bot_color"]).replace("#", ""), 16) ) - ) + # Make sure "feedback_channel_id" and "bug_channel_id" are exactly 19 characters long + if "feedback_channel_id" in config["bot_info"]: + if len(str(config["bot_info"]["feedback_channel_id"])) != 0: + if len(str(config["bot_info"]["feedback_channel_id"])) != 19: + LOG.critical( + "Error in config.yaml file: feedback_channel_id is not a valid Discord channel ID" + ) + else: + FEEDBACK_CHANNEL_ID = config["bot_info"]["feedback_channel_id"] -""" -Validate all of the environment variables. -""" + if "bug_channel_id" in config["bot_info"]: + if len(str(config["bot_info"]["bug_channel_id"])) != 0: + if len(str(config["bot_info"]["bug_channel_id"])) != 19: + LOG.critical( + "Error in config.yaml file: bug_channel_id is not a valid Discord channel ID" + ) + else: + BUG_CHANNEL_ID = config["bot_info"]["bug_channel_id"] + # + # If the SPOTIFY section is present, make sure the client ID and secret are valid + # -def validate_env_vars(): - global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD - - hex_pattern_one = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" - hex_pattern_two = "^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" - - errors = 0 - - # Make sure all required variables are present in the environment - required_vars = ["TOKEN", "BOT_COLOR", "BOT_INVITE_LINK", "SPOTIFY_CLIENT_ID", "SPOTIFY_CLIENT_SECRET", "OPENAI_API_KEY", "LAVALINK_HOST", "LAVALINK_PORT", "LAVALINK_PASSWORD"] - - for var in required_vars: - if var not in os.environ: - LOG.error(f"Missing environment variable: {var}") - errors += 1 - - # Make sure BOT_COLOR is a valid hex color - if not bool(re.match(hex_pattern_one, os.environ["BOT_COLOR"])) and not bool(re.match(hex_pattern_two, os.environ["BOT_COLOR"])): - LOG.error("BOT_COLOR is not a valid hex color.") - errors += 1 - else: - BOT_COLOR = discord.Color(int((os.environ["BOT_COLOR"]).replace("#", ""), 16)) - - # Make sure BOT_INVITE_LINK is a valid URL - if not validators.url(os.environ["BOT_INVITE_LINK"]): - LOG.error("BOT_INVITE_LINK is not a valid URL.") - errors += 1 - else: - BOT_INVITE_LINK = os.environ["BOT_INVITE_LINK"] - - # Make sure FEEDBACK_CHANNEL_ID is either None or 19 characters long - try: - if len(os.environ["FEEDBACK_CHANNEL_ID"]) != 19: - LOG.error("FEEDBACK_CHANNEL_ID is not a valid Discord channel ID.") - errors += 1 + if "spotify" in config: + auth_url = "https://accounts.spotify.com/api/token" + data = { + "grant_type": "client_credentials", + "client_id": config["spotify"]["spotify_client_id"], + "client_secret": config["spotify"]["spotify_client_secret"], + } + response = requests.post(auth_url, data=data) + if response.status_code == 200: + SPOTIFY_CLIENT_ID = config["spotify"]["spotify_client_id"] + SPOTIFY_CLIENT_SECRET = config["spotify"]["spotify_client_secret"] else: - FEEDBACK_CHANNEL_ID = int(os.environ["FEEDBACK_CHANNEL_ID"]) - except KeyError: - FEEDBACK_CHANNEL_ID = None - - # Make sure BUG_CHANNEL_ID is either None or 19 characters long - try: - if len(os.environ["BUG_CHANNEL_ID"]) != 19: - LOG.error("BUG_CHANNEL_ID is not a valid Discord channel ID.") - errors += 1 - else: - BUG_CHANNEL_ID = int(os.environ["BUG_CHANNEL_ID"]) - except KeyError: - BUG_CHANNEL_ID = None - - if errors > 0: - sys.exit( LOG.critical( - f"Found {errors} error(s) with environment variables. Please fix them and try again." + "Error in config.yaml file: Spotify client ID or secret is invalid" ) - ) - # Assign the rest of the variables - TOKEN = os.environ["TOKEN"] - SPOTIFY_CLIENT_ID = os.environ["SPOTIFY_CLIENT_ID"] - SPOTIFY_CLIENT_SECRET = os.environ["SPOTIFY_CLIENT_SECRET"] - OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] - LAVALINK_HOST = os.environ["LAVALINK_HOST"] - LAVALINK_PORT = os.environ["LAVALINK_PORT"] - LAVALINK_PASSWORD = os.environ["LAVALINK_PASSWORD"] \ No newline at end of file + # + # If the GENIUS section is present, make sure the client ID and secret are valid + # + + if "genius" in config: + auth_url = "https://api.genius.com/oauth/token" + data = { + "grant_type": "client_credentials", + "client_id": config["genius"]["genius_client_id"], + "client_secret": config["genius"]["genius_client_secret"], + } + response = requests.post(auth_url, data=data) + if response.status_code == 200: + GENIUS_CLIENT_ID = config["genius"]["genius_client_id"] + GENIUS_CLIENT_SECRET = config["genius"]["genius_client_secret"] + else: + LOG.critical( + "Error in config.yaml file: Genius client ID or secret is invalid" + ) + + # + # If the OPENAI section is present, make sure the API key is valid + # + + if "openai" in config: + client = openai.OpenAI(api_key=config["openai"]["openai_api_key"]) + try: + client.models.list() + OPENAI_API_KEY = config["openai"]["openai_api_key"] + except openai.AuthenticationError: + LOG.critical("Error in config.yaml file: OpenAI API key is invalid") + + # Set appropriate values for all non-optional variables + TOKEN = config["bot_info"]["token"] + LAVALINK_HOST = config["lavalink"]["host"] + LAVALINK_PORT = config["lavalink"]["port"] + LAVALINK_PASSWORD = config["lavalink"]["password"] diff --git a/config.ini.example b/config.ini.example deleted file mode 100644 index c2200c5..0000000 --- a/config.ini.example +++ /dev/null @@ -1,18 +0,0 @@ -[BOT_INFO] -token = -bot_color = -bot_invite_link = -feedback_channel_id = -bug_channel_id = - -[SPOTIFY] -spotify_client_id = -spotify_client_secret = - -[OPENAI] -openai_api_key = - -[LAVALINK] -host = -port = -password = diff --git a/config.yaml.example b/config.yaml.example new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yaml b/docker-compose.yaml index 69953c1..8e349e8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,18 +2,6 @@ services: guava: container_name: guava image: ghcr.io/packetparker/guava:latest - environment: - - TOKEN= - - BOT_COLOR= - - BOT_INVITE_LINK= - - FEEDBACK_CHANNEL_ID= - - BUG_CHANNEL_ID= - - SPOTIFY_CLIENT_ID= - - SPOTIFY_CLIENT_SECRET= - - OPENAI_API_KEY= - - LAVALINK_HOST= - - LAVALINK_PORT= - - LAVALINK_PASSWORD= volumes: - - /path/on/system:/data - restart: on-failure \ No newline at end of file + - /path/on/system:/config + - /path/on/system:/data \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 265f095..293be44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,7 @@ lavalink==5.4.0 colorlog==6.8.2 validators==0.28.3 openai==1.35.0 -requests==2.32.3 \ No newline at end of file +requests==2.32.3 +lyricsgenius==3.0.1 +PyYAML==6.0.1 +jsonschema==4.23.0 \ No newline at end of file From 333cb3adc5de9d163685491f08265a4f0e4a3c93 Mon Sep 17 00:00:00 2001 From: Parker Date: Fri, 2 Aug 2024 21:43:51 -0500 Subject: [PATCH 2/4] Fix dumbass mistake lol --- code/cogs/lyrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/cogs/lyrics.py b/code/cogs/lyrics.py index 711084f..b3a918f 100644 --- a/code/cogs/lyrics.py +++ b/code/cogs/lyrics.py @@ -18,7 +18,7 @@ class Lyrics(commands.Cog): player = self.bot.lavalink.player_manager.get(interaction.guild.id) # If the Genius API client is not setup, send an error message - if self.bot.genius is not None: + if not self.bot.genius: embed = discord.Embed( title="Lyrics Feature Error", description="The lyrics feature is currently disabled due to errors with the Genius API.", From 6fdfba09b2cad092e851ee5db0a594d96dbdd47e Mon Sep 17 00:00:00 2001 From: Parker Date: Fri, 2 Aug 2024 21:48:10 -0500 Subject: [PATCH 3/4] Update references for new `/lyrics` command --- code/cogs/help.py | 4 ++++ code/cogs/news.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/code/cogs/help.py b/code/cogs/help.py index a15d6d2..97af668 100644 --- a/code/cogs/help.py +++ b/code/cogs/help.py @@ -70,6 +70,10 @@ commands_and_descriptions = { "description": "Resume the song that is currently paused", "usage": "/resume", }, + "lyrics": { + "description": "Get the lyrics of the song that is currently playing", + "usage": "/lyrics", + }, "news": { "description": "Get recent news and updates about the bot", "usage": "/news", diff --git a/code/cogs/news.py b/code/cogs/news.py index c115559..9013567 100644 --- a/code/cogs/news.py +++ b/code/cogs/news.py @@ -18,6 +18,11 @@ class News(commands.Cog): color=BOT_COLOR, ) + embed.add_field( + name="**Lyrics!**", + value="> You can now get lyrics for the song that is currently playing. Just use the `/lyrics` command! Some songs may not have lyrics available, but the bot will do its best to find them.", + ) + embed.add_field( name="**Apple Music Support!**", value="> After some trial and error, you can now play music through Apple Music links. Just paste the link and the bot will do the rest!", From 3f23ef4edf5a33c3c184c8b92fc5143789cecbd1 Mon Sep 17 00:00:00 2001 From: Parker Date: Fri, 2 Aug 2024 21:52:21 -0500 Subject: [PATCH 4/4] Change config.yaml -> file_path variable --- code/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/utils/config.py b/code/utils/config.py index 9fdcd1d..951676e 100644 --- a/code/utils/config.py +++ b/code/utils/config.py @@ -105,7 +105,7 @@ def load_config(): except FileNotFoundError: # Create a new config.yaml file with the template - with open("config.yaml", "w") as f: + with open(file_path, "w") as f: f.write( """ bot_info: