Skip to content

Commit 5ecb581

Browse files
refactor: improve code readability and structure in admin commands
- Reformatted the `applytemplate_prefix` and `applytemplate` methods for better readability by adjusting line breaks and indentation. - Grouped import statements for clarity and consistency. - Enhanced error handling and logging for template application processes. - Updated the `DeleteExtraObjectsView` and `ConfirmDeleteObjectsView` classes for improved readability and maintainability. These changes aim to streamline the codebase and enhance the overall developer experience.
1 parent 37c179f commit 5ecb581

File tree

3 files changed

+133
-37
lines changed

3 files changed

+133
-37
lines changed

src/gitcord/cogs/admin.py

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
import zipfile
1414
import os
1515
import shutil
16-
import requests
17-
from urllib.parse import urlparse, parse_qs
16+
from urllib.parse import urlparse
1817

1918
from .base_cog import BaseCog
20-
from ..utils.helpers import create_embed, clean_webpage_text, parse_category_config_from_str, parse_channel_config_from_str
21-
from ..cogs.channels import Channels
22-
from discord.ui import View, Button
19+
from ..utils.helpers import (
20+
create_embed,
21+
clean_webpage_text,
22+
parse_category_config_from_str,
23+
parse_channel_config_from_str,
24+
)
2325
from ..views.base_views import DeleteExtraObjectsView
2426

2527

@@ -196,13 +198,19 @@ async def synccommands_prefix(self, ctx: commands.Context) -> None:
196198

197199
@commands.command(name="applytemplate")
198200
@commands.has_permissions(administrator=True)
199-
async def applytemplate_prefix(self, ctx: commands.Context, url: str, folder: str = None, branch: str = "main") -> None:
200-
self.logger.info(f"[applytemplate_prefix] User: {ctx.author}, URL: {url}, folder: {folder}, branch: {branch}")
201+
async def applytemplate_prefix(
202+
self, ctx: commands.Context, url: str, folder: str = None, branch: str = "main"
203+
) -> None:
204+
self.logger.info(
205+
f"[applytemplate_prefix] User: {ctx.author}, URL: {url}, folder: {folder}, branch: {branch}"
206+
)
201207
await ctx.send("🔄 Downloading and extracting template from GitHub...")
202208
try:
203209
temp_dir = self._download_and_extract_github(url, folder, branch)
204210
self.logger.info(f"[applytemplate_prefix] Extracted to: {temp_dir}")
205-
result_msgs = await self._apply_template_from_dir(ctx.guild, temp_dir, ctx=ctx)
211+
result_msgs = await self._apply_template_from_dir(
212+
ctx.guild, temp_dir, ctx=ctx
213+
)
206214
for msg in result_msgs:
207215
self.logger.info(f"[applytemplate_prefix] {msg}")
208216
await ctx.send("\n".join(result_msgs))
@@ -212,16 +220,32 @@ async def applytemplate_prefix(self, ctx: commands.Context, url: str, folder: st
212220
self.logger.error(f"[applytemplate_prefix] Error: {e}", exc_info=True)
213221
await self.send_error(ctx, "❌ Template Error", str(e))
214222

215-
@app_commands.command(name="applytemplate", description="Apply a Gitcord template from a GitHub URL")
216-
@app_commands.describe(url="GitHub repo/folder URL", folder="Subfolder to use (optional)", branch="Branch/tag/commit (default: main)")
223+
@app_commands.command(
224+
name="applytemplate", description="Apply a Gitcord template from a GitHub URL"
225+
)
226+
@app_commands.describe(
227+
url="GitHub repo/folder URL",
228+
folder="Subfolder to use (optional)",
229+
branch="Branch/tag/commit (default: main)",
230+
)
217231
@app_commands.checks.has_permissions(administrator=True)
218-
async def applytemplate(self, interaction: discord.Interaction, url: str, folder: str = None, branch: str = "main") -> None:
219-
self.logger.info(f"[applytemplate_slash] User: {interaction.user}, URL: {url}, folder: {folder}, branch: {branch}")
232+
async def applytemplate(
233+
self,
234+
interaction: discord.Interaction,
235+
url: str,
236+
folder: str = None,
237+
branch: str = "main",
238+
) -> None:
239+
self.logger.info(
240+
f"[applytemplate_slash] User: {interaction.user}, URL: {url}, folder: {folder}, branch: {branch}"
241+
)
220242
await interaction.response.defer()
221243
try:
222244
temp_dir = self._download_and_extract_github(url, folder, branch)
223245
self.logger.info(f"[applytemplate_slash] Extracted to: {temp_dir}")
224-
result_msgs = await self._apply_template_from_dir(interaction.guild, temp_dir, interaction=interaction)
246+
result_msgs = await self._apply_template_from_dir(
247+
interaction.guild, temp_dir, interaction=interaction
248+
)
225249
for msg in result_msgs:
226250
self.logger.info(f"[applytemplate_slash] {msg}")
227251
await interaction.followup.send("\n".join(result_msgs))
@@ -231,7 +255,9 @@ async def applytemplate(self, interaction: discord.Interaction, url: str, folder
231255
self.logger.error(f"[applytemplate_slash] Error: {e}", exc_info=True)
232256
await self.send_interaction_error(interaction, "❌ Template Error", str(e))
233257

234-
def _download_and_extract_github(self, url: str, folder: str = None, branch: str = "main") -> str:
258+
def _download_and_extract_github(
259+
self, url: str, folder: str = None, branch: str = "main"
260+
) -> str:
235261
"""Download a GitHub repo/folder as zip, extract to temp dir, return path."""
236262
# Parse the URL
237263
parsed = urlparse(url)
@@ -246,7 +272,9 @@ def _download_and_extract_github(self, url: str, folder: str = None, branch: str
246272
# Download zip
247273
resp = requests.get(zip_url, stream=True, timeout=30)
248274
if resp.status_code != 200:
249-
raise ValueError(f"Failed to download zip from {zip_url} (status {resp.status_code})")
275+
raise ValueError(
276+
f"Failed to download zip from {zip_url} (status {resp.status_code})"
277+
)
250278
temp_dir = tempfile.mkdtemp(prefix="gitcord-template-")
251279
zip_path = os.path.join(temp_dir, "repo.zip")
252280
with open(zip_path, "wb") as f:
@@ -272,7 +300,9 @@ def _download_and_extract_github(self, url: str, folder: str = None, branch: str
272300
return folder_path
273301
return extracted_root
274302

275-
async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interaction=None):
303+
async def _apply_template_from_dir(
304+
self, guild, template_dir, ctx=None, interaction=None
305+
):
276306
result_msgs = []
277307
template_category_names = set()
278308
template_channel_names = set()
@@ -282,7 +312,9 @@ async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interact
282312
for root, dirs, files in os.walk(template_dir):
283313
if "category.yaml" in files:
284314
cat_path = os.path.join(root, "category.yaml")
285-
self.logger.info(f"[apply_template_from_dir] Found category.yaml: {cat_path}")
315+
self.logger.info(
316+
f"[apply_template_from_dir] Found category.yaml: {cat_path}"
317+
)
286318
with open(cat_path, "r", encoding="utf-8") as f:
287319
cat_yaml = f.read()
288320
try:
@@ -298,12 +330,16 @@ async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interact
298330
template_channel_names.add(ch_name)
299331
template_category_channel_pairs.add((category_name, ch_name))
300332
# Create or update the category
301-
existing_category = discord.utils.get(guild.categories, name=category_name)
333+
existing_category = discord.utils.get(
334+
guild.categories, name=category_name
335+
)
302336
if existing_category:
303337
category = existing_category
304338
msg = f"ℹ️ Category '{category_name}' already exists. Will update channels."
305339
else:
306-
category = await guild.create_category(name=category_name, position=category_config.get("position", 0))
340+
category = await guild.create_category(
341+
name=category_name, position=category_config.get("position", 0)
342+
)
307343
msg = f"✅ Created category: {category_name}"
308344
self.logger.info(f"[apply_template_from_dir] {msg}")
309345
result_msgs.append(msg)
@@ -328,16 +364,32 @@ async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interact
328364
skipped += 1
329365
continue
330366
# Check if channel exists
331-
existing_channel = discord.utils.get(category.channels, name=channel_config["name"])
367+
existing_channel = discord.utils.get(
368+
category.channels, name=channel_config["name"]
369+
)
332370
channel_type = channel_config["type"].lower()
333371
if existing_channel:
334372
# Update topic/nsfw/position if needed
335373
update_kwargs = {}
336-
if channel_type == "text" and hasattr(existing_channel, "topic") and existing_channel.topic != channel_config.get("topic", ""):
374+
if (
375+
channel_type == "text"
376+
and hasattr(existing_channel, "topic")
377+
and existing_channel.topic
378+
!= channel_config.get("topic", "")
379+
):
337380
update_kwargs["topic"] = channel_config.get("topic", "")
338-
if channel_type in ("text", "voice") and hasattr(existing_channel, "nsfw") and existing_channel.nsfw != channel_config.get("nsfw", False):
381+
if (
382+
channel_type in ("text", "voice")
383+
and hasattr(existing_channel, "nsfw")
384+
and existing_channel.nsfw
385+
!= channel_config.get("nsfw", False)
386+
):
339387
update_kwargs["nsfw"] = channel_config.get("nsfw", False)
340-
if "position" in channel_config and hasattr(existing_channel, "position") and existing_channel.position != channel_config["position"]:
388+
if (
389+
"position" in channel_config
390+
and hasattr(existing_channel, "position")
391+
and existing_channel.position != channel_config["position"]
392+
):
341393
update_kwargs["position"] = channel_config["position"]
342394
if update_kwargs:
343395
await existing_channel.edit(**update_kwargs)
@@ -350,7 +402,10 @@ async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interact
350402
result_msgs.append(msg)
351403
else:
352404
# Create new channel
353-
channel_kwargs = {"name": channel_config["name"], "category": category}
405+
channel_kwargs = {
406+
"name": channel_config["name"],
407+
"category": category,
408+
}
354409
if "position" in channel_config:
355410
channel_kwargs["position"] = channel_config["position"]
356411
if channel_type == "text":
@@ -373,12 +428,18 @@ async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interact
373428
result_msgs.append(msg)
374429
# After all channels processed, check for extra channels in this category
375430
template_channel_names = set(category_config["channels"])
376-
extra_channels = [ch for ch in category.channels if ch.name not in template_channel_names]
431+
extra_channels = [
432+
ch
433+
for ch in category.channels
434+
if ch.name not in template_channel_names
435+
]
377436
if extra_channels:
378437
msg = f"⚠️ Extra channels not in template for category '{category_name}': {', '.join(ch.name for ch in extra_channels)}"
379438
self.logger.warning(f"[apply_template_from_dir] {msg}")
380439
result_msgs.append(msg)
381-
view = DeleteExtraObjectsView(extra_channels, object_type_label='channel')
440+
view = DeleteExtraObjectsView(
441+
extra_channels, object_type_label="channel"
442+
)
382443
if interaction:
383444
await interaction.followup.send(msg, view=view)
384445
elif ctx:
@@ -387,29 +448,38 @@ async def _apply_template_from_dir(self, guild, template_dir, ctx=None, interact
387448
result_msgs.append(summary)
388449
# After all categories processed, check for extra categories
389450
existing_category_names = set(cat.name for cat in guild.categories)
390-
extra_categories = [cat for cat in guild.categories if cat.name not in template_category_names]
451+
extra_categories = [
452+
cat for cat in guild.categories if cat.name not in template_category_names
453+
]
391454
if extra_categories:
392455
msg = f"⚠️ Extra categories not in template: {', '.join(cat.name for cat in extra_categories)}"
393456
self.logger.warning(f"[apply_template_from_dir] {msg}")
394457
result_msgs.append(msg)
395-
view = DeleteExtraObjectsView(extra_categories, object_type_label='category')
458+
view = DeleteExtraObjectsView(
459+
extra_categories, object_type_label="category"
460+
)
396461
if interaction:
397462
await interaction.followup.send(msg, view=view)
398463
elif ctx:
399464
await ctx.send(msg, view=view)
400465
# After all categories processed, check for orphan channels
401466
orphan_channels = []
402467
for ch in guild.channels:
403-
if getattr(ch, 'category', None) is None and isinstance(ch, (discord.TextChannel, discord.VoiceChannel)):
468+
if getattr(ch, "category", None) is None and isinstance(
469+
ch, (discord.TextChannel, discord.VoiceChannel)
470+
):
404471
# If template does not support uncategorized channels, all orphans are extra
405472
# If template_uncategorized_channel_names is empty, all orphans are extra
406-
if not template_uncategorized_channel_names or ch.name not in template_uncategorized_channel_names:
473+
if (
474+
not template_uncategorized_channel_names
475+
or ch.name not in template_uncategorized_channel_names
476+
):
407477
orphan_channels.append(ch)
408478
if orphan_channels:
409479
msg = f"⚠️ Uncategorized channels not in template: {', '.join(ch.name for ch in orphan_channels)}"
410480
self.logger.warning(f"[apply_template_from_dir] {msg}")
411481
result_msgs.append(msg)
412-
view = DeleteExtraObjectsView(orphan_channels, object_type_label='channel')
482+
view = DeleteExtraObjectsView(orphan_channels, object_type_label="channel")
413483
if interaction:
414484
await interaction.followup.send(msg, view=view)
415485
elif ctx:

src/gitcord/utils/helpers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ def parse_category_config(yaml_path: str) -> dict:
140140
def parse_category_config_from_str(yaml_str: str) -> dict:
141141
"""Parse and validate category YAML configuration from a string."""
142142
import yaml
143+
143144
try:
144145
category_config = yaml.safe_load(yaml_str)
145146
except yaml.YAMLError as e:
@@ -156,6 +157,7 @@ def parse_category_config_from_str(yaml_str: str) -> dict:
156157
def parse_channel_config_from_str(yaml_str: str) -> dict:
157158
"""Parse and validate channel YAML configuration from a string."""
158159
import yaml
160+
159161
channel_config = yaml.safe_load(yaml_str)
160162
if channel_config is None:
161163
raise ValueError("YAML is empty or invalid.")

src/gitcord/views/base_views.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class DeleteExtraObjectsView(View):
113113
Generic view for confirming deletion of extra Discord objects (channels, categories, etc.).
114114
Objects must have .name and .delete().
115115
"""
116+
116117
def __init__(self, extra_objects, object_type_label, timeout=60):
117118
super().__init__(timeout=timeout)
118119
self.extra_objects = extra_objects
@@ -129,6 +130,7 @@ async def delete_callback(self, interaction: discord.Interaction):
129130
# Check permissions (manage_channels for channels, manage_channels for categories)
130131
if not interaction.user.guild_permissions.manage_channels: # type: ignore
131132
from ..utils.helpers import create_embed
133+
132134
embed = create_embed(
133135
title="❌ Permission Denied",
134136
description=f"You need the 'Manage Channels' permission to delete {self.object_type_label}s.",
@@ -137,23 +139,35 @@ async def delete_callback(self, interaction: discord.Interaction):
137139
await interaction.response.send_message(embed=embed, ephemeral=True)
138140
return
139141
# Create confirmation embed
140-
object_list = "\n".join([f"• {getattr(obj, 'mention', '#' + obj.name)}" for obj in self.extra_objects])
142+
object_list = "\n".join(
143+
[
144+
f"• {getattr(obj, 'mention', '#' + obj.name)}"
145+
for obj in self.extra_objects
146+
]
147+
)
141148
from ..utils.helpers import create_embed
149+
142150
embed = create_embed(
143-
title=f"⚠️ Confirm Deletion",
151+
title="⚠️ Confirm Deletion",
144152
description=(
145153
f"Are you sure you want to delete the following {self.object_type_label}s?\n\n{object_list}\n\n**This action is irreversible!**"
146154
),
147155
color=discord.Color.orange(),
148156
)
149-
confirm_view = ConfirmDeleteObjectsView(self.extra_objects, self.object_type_label)
150-
await interaction.response.send_message(embed=embed, view=confirm_view, ephemeral=True)
157+
confirm_view = ConfirmDeleteObjectsView(
158+
self.extra_objects, self.object_type_label
159+
)
160+
await interaction.response.send_message(
161+
embed=embed, view=confirm_view, ephemeral=True
162+
)
163+
151164

152165
class ConfirmDeleteObjectsView(View):
153166
"""
154167
View for final confirmation of object deletion.
155168
Objects must have .name and .delete().
156169
"""
170+
157171
def __init__(self, extra_objects, object_type_label, timeout=60):
158172
super().__init__(timeout=timeout)
159173
self.extra_objects = extra_objects
@@ -183,15 +197,24 @@ async def confirm_callback(self, interaction: discord.Interaction):
183197
except Exception:
184198
failed.append(obj.name)
185199
from ..utils.helpers import create_embed
200+
186201
if deleted:
187202
embed = create_embed(
188203
title=f"✅ {self.object_type_label.title()}s Deleted",
189204
description=f"Successfully deleted {len(deleted)} {self.object_type_label}s.",
190205
color=discord.Color.green(),
191206
)
192-
embed.add_field(name="Deleted", value="\n".join([f"• {name}" for name in deleted]), inline=False)
207+
embed.add_field(
208+
name="Deleted",
209+
value="\n".join([f"• {name}" for name in deleted]),
210+
inline=False,
211+
)
193212
if failed:
194-
embed.add_field(name="Failed", value="\n".join([f"• {name}" for name in failed]), inline=False)
213+
embed.add_field(
214+
name="Failed",
215+
value="\n".join([f"• {name}" for name in failed]),
216+
inline=False,
217+
)
195218
else:
196219
embed = create_embed(
197220
title="❌ Deletion Failed",
@@ -202,6 +225,7 @@ async def confirm_callback(self, interaction: discord.Interaction):
202225

203226
async def cancel_callback(self, interaction: discord.Interaction):
204227
from ..utils.helpers import create_embed
228+
205229
embed = create_embed(
206230
title="❌ Deletion Cancelled",
207231
description=f"{self.object_type_label.title()} deletion was cancelled.",

0 commit comments

Comments
 (0)