|
| 1 | +import base64 |
1 | 2 | import logging |
2 | 3 | import os |
3 | 4 | import re |
4 | 5 | import sys |
5 | 6 | from pathlib import Path |
6 | | -from typing import Any, cast |
| 7 | +from typing import Any, Final, cast |
7 | 8 |
|
8 | 9 | from dotenv import load_dotenv |
9 | 10 | from httpx import Client, HTTPStatusError, Response |
|
15 | 16 | Webhooks, |
16 | 17 | _Categories, # pyright: ignore[reportPrivateUsage] |
17 | 18 | _Channels, # pyright: ignore[reportPrivateUsage] |
| 19 | + _Emojis, # pyright: ignore[reportPrivateUsage] |
18 | 20 | _Roles, # pyright: ignore[reportPrivateUsage] |
19 | 21 | ) |
20 | 22 | from bot.log import get_logger # noqa: E402 |
|
35 | 37 | RULES_CHANNEL_NAME = "rules" |
36 | 38 | GUILD_CATEGORY_TYPE = 4 |
37 | 39 | GUILD_FORUM_TYPE = 15 |
| 40 | +EMOJI_REGEX = re.compile(r"<:(\w+):(\d+)>") |
38 | 41 |
|
39 | 42 | if not BOT_TOKEN: |
40 | 43 | message = ( |
@@ -76,6 +79,8 @@ def __getitem__(self, item: str): |
76 | 79 | class DiscordClient(Client): |
77 | 80 | """An HTTP client to communicate with Discord's APIs.""" |
78 | 81 |
|
| 82 | + CDN_BASE_URL: Final[str] = "https://cdn.discordapp.com" |
| 83 | + |
79 | 84 | def __init__(self, guild_id: int | str): |
80 | 85 | super().__init__( |
81 | 86 | base_url="https://discord.com/api/v10", |
@@ -238,6 +243,37 @@ def create_webhook(self, name: str, channel_id_: int) -> str: |
238 | 243 | new_webhook = response.json() |
239 | 244 | return new_webhook["id"] |
240 | 245 |
|
| 246 | + def list_emojis(self) -> list[dict[str, Any]]: |
| 247 | + """Lists all the emojis for the guild.""" |
| 248 | + response = self.get(f"/guilds/{self.guild_id}/emojis") |
| 249 | + return response.json() |
| 250 | + |
| 251 | + def get_emoji_contents(self, emoji_id: str | int) -> bytes | None: |
| 252 | + """Fetches the image data for an emoji by ID.""" |
| 253 | + # emojis are located at https://cdn.discordapp.com/emojis/821611231663751168.png?size=4096 |
| 254 | + response = self.get(f"{self.CDN_BASE_URL}/emojis/{emoji_id!s}.webp") |
| 255 | + return response.content |
| 256 | + |
| 257 | + def clone_emoji(self, *, name: str, id: str | int) -> str: |
| 258 | + """Creates a new emoji in the guild, cloned from another emoji by ID.""" |
| 259 | + emoji_data = self.get_emoji_contents(id) |
| 260 | + if not emoji_data: |
| 261 | + log.warning(f"Couldn't find emoji with ID {id}.") |
| 262 | + return "" |
| 263 | + |
| 264 | + payload = { |
| 265 | + "name": name, |
| 266 | + "image": f"data:image/png;base64,{base64.b64encode(emoji_data).decode('utf-8')}", |
| 267 | + } |
| 268 | + |
| 269 | + response = self.post( |
| 270 | + f"/guilds/{self.guild_id}/emojis", |
| 271 | + json=payload, |
| 272 | + headers={"X-Audit-Log-Reason": f"Creating {name} emoji as part of PyDis botstrap"}, |
| 273 | + ) |
| 274 | + new_emoji = response.json() |
| 275 | + return new_emoji["id"] |
| 276 | + |
241 | 277 |
|
242 | 278 | with DiscordClient(guild_id=GUILD_ID) as discord_client: |
243 | 279 | if discord_client.upgrade_application_flags_if_necessary(): |
@@ -329,7 +365,24 @@ def create_webhook(self, name: str, channel_id_: int) -> str: |
329 | 365 | config_str += f"webhooks_{webhook_name}__id={webhook_id}\n" |
330 | 366 |
|
331 | 367 | config_str += "\n#Emojis\n" |
332 | | - config_str += "emojis_trashcan=🗑️" |
| 368 | + |
| 369 | + existing_emojis = discord_client.list_emojis() |
| 370 | + log.debug("Syncing emojis with bot configuration.") |
| 371 | + for emoji_config_name, emoji_config in _Emojis.model_fields.items(): |
| 372 | + if not (match := EMOJI_REGEX.match(emoji_config.default)): |
| 373 | + continue |
| 374 | + emoji_name = match.group(1) |
| 375 | + emoji_id = match.group(2) |
| 376 | + |
| 377 | + for emoji in existing_emojis: |
| 378 | + if emoji["name"] == emoji_name: |
| 379 | + emoji_id = emoji["id"] |
| 380 | + break |
| 381 | + else: |
| 382 | + log.info("Creating emoji %s", emoji_name) |
| 383 | + emoji_id = discord_client.clone_emoji(name=emoji_name, id=emoji_id) |
| 384 | + |
| 385 | + config_str += f"emojis_{emoji_config_name}=<:{emoji_name}:{emoji_id}>\n" |
333 | 386 |
|
334 | 387 | with env_file_path.open("wb") as file: |
335 | 388 | file.write(config_str.encode("utf-8")) |
|
0 commit comments