commit af0d6cd2c0a034c83b7ffba2f48d00bc70904eaa Author: Ada Werefox Date: Wed Apr 9 17:13:29 2025 -0500 Initial commit, already feature complete. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db20b24 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Ignore python cache +**/*.pyc +**/__pycache__/* + +# Ignore python virtual environment +.venv/ + +# Ignore log files +**/*.log + +# Ignore config file +**/config.json \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/README.md b/README.md new file mode 100644 index 0000000..df9ad80 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Discord emoji uploader + +I just wanna put my emojis on my Discord server. + +## Setup + +### (Optional) Set up a virtual environment + +``` +python -m venv .venv +source .venv/bin/activate +``` + +### Install the required packages + +``` +pip install -r requirements.txt +``` + +### Edit the config + +Rename `sample-config.json` to `config.json`. + +``` +mv sample-config.json config.json +``` + +Then, edit in your API Key and Guild ID. + +### (Optional) Add emoji files + +Any images in the `emojis/` directory will be uploaded by default. + +## Usage + +Assuming your `config.json` is in the default place at the root of the +project directory, and the emoji images you want to upload are in `emoji/`, +you do not need to add any arguments. You can, however, run +`upload-emoji.py -h` for a usage printout. + +You can specify a config file with `-c`. + +``` +upload-emoji.py -c some/path/to/config.json +``` + +You can also specify a directory for emojis. + +``` +upload-emoji.py -e some/path/to/emoji/images/ +``` + +If you want more verbose logs, you can use the `-v` flag. + +``` +upload-emoji.py -v +``` diff --git a/emojis/.gitkeep b/emojis/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..74c7d27 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +aiohappyeyeballs==2.6.1 +aiohttp==3.11.16 +aiosignal==1.3.2 +attrs==25.3.0 +discord==2.3.2 +discord.py==2.5.2 +frozenlist==1.5.0 +idna==3.10 +multidict==6.2.0 +propcache==0.3.1 +yarl==1.19.0 diff --git a/sample-config.json b/sample-config.json new file mode 100644 index 0000000..c0b5895 --- /dev/null +++ b/sample-config.json @@ -0,0 +1,4 @@ +{ + "api_key": "(Your big api_key from https://discord.com/developers)", + "guild_id": "0000000000000000000" +} \ No newline at end of file diff --git a/upload-emoji.py b/upload-emoji.py new file mode 100755 index 0000000..1f6ea83 --- /dev/null +++ b/upload-emoji.py @@ -0,0 +1,171 @@ +#!/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()