#!/usr/bin/env python from argparse import ArgumentParser, Namespace from json import load from pathlib import Path from logging import error, info, debug, INFO, DEBUG, FileHandler from sys import exit from os import listdir from os.path import splitext from discord import Client, Intents # Set default paths for config and emoji dir, and supported file formats DEFAULT_CONFIG_PATH = "config.json" DEFAULT_EMOJI_DIR = "emojis/" SUPPORTED_FORMATS = [".jpeg", ".jpg", ".png", ".gif"] class EmojiUploadClient(Client): emoji_dir: Path = Path("") guild_id: str = "" async def on_ready(self): """Key function to run upon startup.""" info(f"Logged on as {self.user}") # Attempt to upload emoji after startup. await self.upload_emoji() async def upload_emoji(self): """Uses emoji_dir and guild_id to upload all emoji in the directory to the specified guild. """ info(f'Attempting to upload emoji at "{self.emoji_dir}"...') # Check guild_id is numeric. if not self.guild_id.isnumeric(): error(f"Not a proper guild_id: {self.guild_id}") exit(1) # Obtain guild object. guild = self.get_guild(int(self.guild_id)) # Grab list of emoji files. emoji_files = listdir(self.emoji_dir) # Iterate through emojis and attempt to upload them. for emoji in emoji_files: current_emoji_filepath = f"{self.emoji_dir}/{emoji}" (file_name, file_extension) = splitext(emoji) # Validate that the image is a supported format. if file_extension not in SUPPORTED_FORMATS: debug(f'Skipping: "{current_emoji_filepath}"') continue # Try to upload the custom emoji. debug(f"Attempting to upload: {current_emoji_filepath}") with open(current_emoji_filepath, "rb") as img: await guild.create_custom_emoji(name=file_name, image=img.read()) # Disconnect the client. info("All emojis attempted, disconnecting.") await self.close() def parse_config(config_file: Path) -> tuple: try: with open(config_file) as config: parse_config = load(config) return (parse_config["api_key"], parse_config["guild_id"]) except Exception: error(f"There was an error parsing the config file at: {config_file}") exit(1) return def parse_opts(opts: Namespace) -> tuple: """Parse out opts for config and emoji paths. Args: opts (Namespace): Opts object to be parsed. Returns: (config_path, emoji_dir): Tuple of opts paths parsed. """ try: info( f""" config_file: {opts.config_file} emoji_dir: {opts.emoji_dir} """ ) config_file = Path(opts.config_file) emoji_dir = Path(opts.emoji_dir) except Exception: error(f"{Exception}\nCould not successfully parse arguments.") exit(1) if not config_file.exists(): error(f'No config file found at "{config_file}".') exit(1) if not emoji_dir.exists(): error(f'"{emoji_dir}" is not a valid directory.') exit(1) if opts.verbose: log_level = DEBUG else: log_level = INFO return (config_file, emoji_dir, log_level) def make_args() -> ArgumentParser: """Create an ArgumentParser object. Returns: ArgumentParser: ArgumentParser object """ parser = ArgumentParser() parser.add_argument( "-c", "--config-file", default=DEFAULT_CONFIG_PATH, help=f"Path to config json, defaults to: {DEFAULT_CONFIG_PATH}", ) parser.add_argument( "-e", "--emoji-dir", default=DEFAULT_EMOJI_DIR, help=f"Path to emoji directory, defaults to: {DEFAULT_EMOJI_DIR}", ) parser.add_argument( "-v", "--verbose", action="store_true", help="Sets the logging level to DEBUG.", ) return parser def main(argv: list[str] | None = None, print_stdout: bool | None = True): args = make_args() opts = args.parse_args(argv) # Parse out the config filepath, emoji directory, and verbose flag. config_file, emoji_dir, log_level = parse_opts(opts) # Parse out the api key and the guild id. (api_key, guild_id) = parse_config(config_file) # Create Discord API Client object and set properties. client = EmojiUploadClient(intents=Intents.default()) client.emoji_dir = emoji_dir client.guild_id = guild_id # Set up logging for Discord API Client handler = FileHandler(filename="emoji_uploader.log", encoding="utf-8", mode="w") # Run the Discord API Client. client.run(api_key, log_handler=handler, root_logger=True, log_level=log_level) exit(0) if __name__ == "__main__": main()