1313import zipfile
1414import os
1515import shutil
16- import requests
17- from urllib .parse import urlparse , parse_qs
16+ from urllib .parse import urlparse
1817
1918from .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+ )
2325from ..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 :
0 commit comments