Skip to content

Commit 27a16d6

Browse files
onerandomusernamejb3
authored andcommitted
interface updates, typehints, variable names
1 parent 1993071 commit 27a16d6

File tree

1 file changed

+64
-40
lines changed

1 file changed

+64
-40
lines changed

botstrap.py

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import dotenv
1111
from httpx import Client, HTTPStatusError, Response
1212

13-
log = logging.getLogger("botstrap") # note this instance will not have the .trace level
13+
log = logging.getLogger("botstrap") # Note this instance will not have the .trace level
1414

1515
# TODO: Remove once better error handling for constants.py is in place.
1616
if (dotenv.dotenv_values().get("BOT_TOKEN") or os.getenv("BOT_TOKEN")) is None:
@@ -61,7 +61,7 @@ class SilencedDict(dict[str, Any]):
6161
"""A dictionary that silences KeyError exceptions upon subscription to non existent items."""
6262

6363
def __init__(self, name: str):
64-
self.name = name
64+
self.name: str = name
6565
super().__init__()
6666

6767
def __getitem__(self, item: str):
@@ -92,7 +92,7 @@ def __init__(self, *, guild_id: int | str, bot_token: str):
9292
headers={"Authorization": f"Bot {bot_token}"},
9393
event_hooks={"response": [self._raise_for_status]},
9494
)
95-
self.guild_id = guild_id
95+
self.guild_id: int | str = guild_id
9696
self._app_info: dict[str, Any] | None = None
9797
self._guild_info: dict[str, Any] | None = None
9898
self._guild_channels: list[dict[str, Any]] | None = None
@@ -161,17 +161,17 @@ def check_if_in_guild(self) -> bool:
161161

162162
def upgrade_server_to_community_if_necessary(
163163
self,
164-
rules_channel_id_: int | str,
165-
announcements_channel_id_: int | str,
164+
rules_channel_id: int | str,
165+
announcements_channel_id: int | str,
166166
) -> bool:
167167
"""Fetches server info & upgrades to COMMUNITY if necessary."""
168168
payload = self.guild_info
169169

170170
if COMMUNITY_FEATURE not in payload["features"]:
171171
log.info("This server is currently not a community, upgrading.")
172172
payload["features"].append(COMMUNITY_FEATURE)
173-
payload["rules_channel_id"] = rules_channel_id_
174-
payload["public_updates_channel_id"] = announcements_channel_id_
173+
payload["rules_channel_id"] = rules_channel_id
174+
payload["public_updates_channel_id"] = announcements_channel_id
175175
self._guild_info = self.patch(f"/guilds/{self.guild_id}", json=payload).json()
176176
log.info("Server %s has been successfully updated to a community.", self.guild_id)
177177
return True
@@ -215,11 +215,11 @@ def get_all_guild_webhooks(self) -> list[dict[str, Any]]:
215215
response = self.get(f"/guilds/{self.guild_id}/webhooks")
216216
return response.json()
217217

218-
def create_webhook(self, name: str, channel_id_: int) -> str:
218+
def create_webhook(self, name: str, channel_id: int | str) -> str:
219219
"""Creates a new webhook for a particular channel."""
220220
payload = {"name": name}
221221
response = self.post(
222-
f"/channels/{channel_id_}/webhooks",
222+
f"/channels/{channel_id}/webhooks",
223223
json=payload,
224224
headers={"X-Audit-Log-Reason": "Creating webhook as part of PyDis botstrap"},
225225
)
@@ -272,9 +272,9 @@ def __init__(
272272
env_file: Path,
273273
bot_token: str,
274274
):
275-
self.guild_id = guild_id
276-
self.client = DiscordClient(guild_id=guild_id, bot_token=bot_token)
277-
self.env_file = env_file
275+
self.guild_id: int | str = guild_id
276+
self.client: DiscordClient = DiscordClient(guild_id=guild_id, bot_token=bot_token)
277+
self.env_file: Path = env_file
278278

279279
def __enter__(self):
280280
return self
@@ -310,12 +310,13 @@ def check_guild_membership(self) -> None:
310310
def upgrade_guild(self, announcements_channel_id: str, rules_channel_id: str) -> bool:
311311
"""Upgrade the guild to a community if necessary."""
312312
return self.client.upgrade_server_to_community_if_necessary(
313-
rules_channel_id_=rules_channel_id,
314-
announcements_channel_id_=announcements_channel_id,
313+
rules_channel_id=rules_channel_id,
314+
announcements_channel_id=announcements_channel_id,
315315
)
316316

317317
def get_roles(self) -> dict[str, Any]:
318318
"""Get a config map of all of the roles in the guild."""
319+
log.debug("Syncing roles with bot configuration.")
319320
all_roles = self.client.get_all_roles()
320321

321322
data: dict[str, int] = {}
@@ -332,6 +333,7 @@ def get_roles(self) -> dict[str, Any]:
332333

333334
def get_channels(self) -> dict[str, Any]:
334335
"""Get a config map of all of the channels in the guild."""
336+
log.debug("Syncing channels with bot configuration.")
335337
all_channels, _categories = self.client.get_all_channels_and_categories()
336338

337339
data: dict[str, str] = {}
@@ -349,6 +351,7 @@ def get_channels(self) -> dict[str, Any]:
349351

350352
def get_categories(self) -> dict[str, Any]:
351353
"""Get a config map of all of the categories in guild."""
354+
log.debug("Syncing categories with bot configuration.")
352355
_channels, all_categories = self.client.get_all_channels_and_categories()
353356

354357
data: dict[str, str] = {}
@@ -365,27 +368,29 @@ def get_categories(self) -> dict[str, Any]:
365368

366369
def sync_webhooks(self) -> dict[str, Any]:
367370
"""Get webhook config. Will create all webhooks that cannot be found."""
371+
log.debug("Syncing webhooks with bot configuration.")
372+
368373
all_channels, _categories = self.client.get_all_channels_and_categories()
369374

370375
data: dict[str, Any] = {}
371376

372377
existing_webhooks = self.client.get_all_guild_webhooks()
373-
for webhook_name, webhook_model in Webhooks:
378+
for webhook_name, configured_webhook in Webhooks.model_dump().items():
374379
formatted_webhook_name = webhook_name.replace("_", " ").title()
380+
configured_webhook_id = str(configured_webhook["id"])
381+
375382
for existing_hook in existing_webhooks:
376-
if (
377-
# Check the existing ID matches the configured one
378-
existing_hook["id"] == str(webhook_model.id)
379-
or (
380-
# Check if the name and the channel ID match the configured ones
381-
existing_hook["name"] == formatted_webhook_name
382-
and existing_hook["channel_id"] == str(all_channels[webhook_name])
383-
)
383+
existing_hook_id: str = existing_hook["id"]
384+
385+
if existing_hook_id == configured_webhook_id or (
386+
existing_hook["name"] == formatted_webhook_name
387+
# This requires the normalized channel name matches the webhook attribute
388+
and existing_hook["channel_id"] == str(all_channels[webhook_name])
384389
):
385-
webhook_id = existing_hook["id"]
390+
webhook_id = existing_hook_id
386391
break
387392
else:
388-
webhook_channel_id = int(all_channels[webhook_name])
393+
webhook_channel_id = all_channels[webhook_name]
389394
webhook_id = self.client.create_webhook(formatted_webhook_name, webhook_channel_id)
390395

391396
data[webhook_name + "__id"] = webhook_id
@@ -396,12 +401,12 @@ def sync_emojis(self) -> dict[str, Any]:
396401
"""Get emoji config. Will create all emojis that cannot be found."""
397402
existing_emojis = self.client.list_emojis()
398403
log.debug("Syncing emojis with bot configuration.")
399-
data: dict[str, Any] = {}
404+
data: dict[str, str] = {}
400405
for emoji_config_name, emoji_config in _Emojis.model_fields.items():
401-
if not (match := EMOJI_REGEX.match(emoji_config.default)):
406+
if not (match := EMOJI_REGEX.fullmatch(emoji_config.default)):
402407
continue
403408
emoji_name = match.group(1)
404-
emoji_id = match.group(2)
409+
emoji_id: str = match.group(2)
405410

406411
for emoji in existing_emojis:
407412
if emoji["name"] == emoji_name:
@@ -415,30 +420,44 @@ def sync_emojis(self) -> dict[str, Any]:
415420

416421
return data
417422

418-
def write_config_env(self, config: dict[str, dict[str, Any]]) -> None:
423+
def write_config_env(self, config: dict[str, dict[str, Any]]) -> bool:
419424
"""Write the configuration to the specified env_file."""
420-
with self.env_file.open("wb") as file:
421-
for category, category_values in config.items():
425+
with self.env_file.open("r+") as file:
426+
before = file.read()
427+
file.seek(0)
428+
for num, (category, category_values) in enumerate(config.items()):
422429
# In order to support commented sections, we write the following
423-
file.write(f"# {category.capitalize()}\n".encode())
430+
file.write(f"# {category.capitalize()}\n")
424431
# Format the dictionary into .env style
425432
for key, value in category_values.items():
426-
file.write(f"{category}_{key}={value}\n".encode())
427-
file.write(b"\n")
433+
file.write(f"{category}_{key}={value}\n")
434+
if num < len(config) - 1:
435+
file.write("\n")
436+
437+
file.truncate()
438+
file.seek(0)
439+
after = file.read()
440+
441+
return before != after
428442

429-
def run(self) -> None:
443+
def run(self) -> bool:
430444
"""Runs the botstrap process."""
445+
# Track if any changes were made and exit with an error code if so.
446+
changes: bool = False
431447
config: dict[str, dict[str, object]] = {}
432-
self.upgrade_client()
448+
changes |= self.upgrade_client()
433449
self.check_guild_membership()
434450

435451
channels = self.get_channels()
436452

437453
# Ensure the guild is upgraded to a community if necessary.
438454
# This isn't strictly necessary for bot functionality, but
439455
# it prevents weird transients since PyDis is a community server.
440-
self.upgrade_guild(channels[ANNOUNCEMENTS_CHANNEL_NAME], channels[RULES_CHANNEL_NAME])
456+
changes |= self.upgrade_guild(channels[ANNOUNCEMENTS_CHANNEL_NAME], channels[RULES_CHANNEL_NAME])
441457

458+
# Though sync_webhooks and sync_emojis DO make api calls that may modify server state,
459+
# those changes will be reflected in the config written to the .env file.
460+
# Therefore, we don't need to track if any emojis or webhooks are being changed within those settings.
442461
config = {
443462
"categories": self.get_categories(),
444463
"channels": channels,
@@ -447,11 +466,16 @@ def run(self) -> None:
447466
"emojis": self.sync_emojis(),
448467
}
449468

450-
self.write_config_env(config, self.env_file)
469+
changes |= self.write_config_env(config)
470+
return changes
451471

452472

453473
if __name__ == "__main__":
454474
botstrap = BotStrapper(guild_id=GuildConstants.id, env_file=ENV_FILE, bot_token=BotConstants.token)
455475
with botstrap:
456-
botstrap.run()
457-
log.info("Botstrap completed successfully. Configuration has been written to %s", ENV_FILE)
476+
changes_made = botstrap.run()
477+
if changes_made:
478+
log.info("Botstrap completed successfully. Updated configuration has been written to %s", ENV_FILE)
479+
else:
480+
log.info("Botstrap completed successfully. No changes were necessary.")
481+
sys.exit(changes_made)

0 commit comments

Comments
 (0)