Initial commit, already feature complete.

This commit is contained in:
Ada Werefox 2025-04-09 17:13:29 -05:00
commit af0d6cd2c0
7 changed files with 256 additions and 0 deletions

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# Ignore python cache
**/*.pyc
**/__pycache__/*
# Ignore python virtual environment
.venv/
# Ignore log files
**/*.log
# Ignore config file
**/config.json

1
.python-version Normal file
View file

@ -0,0 +1 @@
3.11

57
README.md Normal file
View file

@ -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
```

0
emojis/.gitkeep Normal file
View file

11
requirements.txt Normal file
View file

@ -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

4
sample-config.json Normal file
View file

@ -0,0 +1,4 @@
{
"api_key": "(Your big api_key from https://discord.com/developers)",
"guild_id": "0000000000000000000"
}

171
upload-emoji.py Executable file
View file

@ -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()