import configparser import re import os import validators import sys import discord import openai import logging from colorlog import ColoredFormatter log_level = logging.DEBUG log_format = ( " %(log_color)s%(levelname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s" ) logging.root.setLevel(log_level) formatter = ColoredFormatter(log_format) stream = logging.StreamHandler() stream.setLevel(log_level) stream.setFormatter(formatter) LOG = logging.getLogger("pythonConfig") LOG.setLevel(log_level) LOG.addHandler(stream) TOKEN = None BOT_COLOR = None BOT_INVITE_LINK = None FEEDBACK_CHANNEL_ID = None BUG_CHANNEL_ID = None SPOTIFY_CLIENT_ID = None SPOTIFY_CLIENT_SECRET = None APPLE_MUSIC_KEY = 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. """ 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() else: LOG.info("Detected local environment. Checking for config.ini file.") try: with open("config.ini", "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": "", } config["SPOTIFY"] = { "SPOTIFY_CLIENT_ID": "", "SPOTIFY_CLIENT_SECRET": "", } config["APPLE_MUSIC"] = { "APPLE_MUSIC_KEY": "", } config["OPENAI"] = { "OPENAI_API_KEY": "", } config["LAVALINK"] = { "HOST": "", "PORT": "", "PASSWORD": "", } with open("config.ini", "w") as configfile: config.write(configfile) 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." ) ) """ Validate all of the options in the config.ini 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, APPLE_MUSIC_KEY, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD config = configparser.ConfigParser() config.read_string(file_contents) 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", "APPLE_MUSIC", "OPENAI", "LAVALINK"] != config.sections(): sys.exit( LOG.critical( "Missing sections in config.ini file. Delete the file and re-run the bot to generate a blank config.ini file." ) ) 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 ["apple_music_key"] != config.options("APPLE_MUSIC"): sys.exit( LOG.critical( "Missing options in APPLE_MUSIC 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"] APPLE_MUSIC_KEY = config["APPLE_MUSIC"]["APPLE_MUSIC_KEY"] 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." ) ) """ Validate all of the environment variables. """ def validate_env_vars(): global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, APPLE_MUSIC_KEY, 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", "APPLE_MUSIC_KEY", "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 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." ) ) # 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"] APPLE_MUSIC_KEY = os.environ["APPLE_MUSIC_KEY"] 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"]