diff --git a/src/core/engine.py b/src/core/engine.py index c606aca..f8f3c5f 100644 --- a/src/core/engine.py +++ b/src/core/engine.py @@ -213,23 +213,47 @@ class MigrationEngine: categories = await self.discord_reader.get_categories() channels = await self.discord_reader.get_channels() - # Filter items if not forcing - if not force: - categories = [cat for cat in categories if not self.state.get_fluxer_category_id(str(cat.id))] - channels = [ch for ch in channels if not self.state.get_fluxer_channel_id(str(ch.id))] + # 1. Identify categories to create + missing_categories = [cat for cat in categories if force or not self.state.get_fluxer_category_id(str(cat.id))] + missing_category_ids = {str(cat.id) for cat in missing_categories} + + # 2. Identify channels to create or move + # Fetch current Fluxer state to check parent_ids + fluxer_channels = await self.fluxer_writer.get_channels() + fluxer_parent_map = {str(c["id"]): (str(c.get("parent_id")) if c.get("parent_id") else None) for c in fluxer_channels} + + channels_to_create = [] + channels_to_move = [] + + for ch in channels: + discord_id = str(ch.id) + fluxer_id = self.state.get_fluxer_channel_id(discord_id) + discord_parent_id = str(ch.category_id) if ch.category_id else None + + if force or not fluxer_id: + # We'll resolve the parent_id in the loop after categories are created + channels_to_create.append(ch) + else: + current_fluxer_parent = fluxer_parent_map.get(fluxer_id) + # Case A: Its category is being created right now + # Case B: It has a category that exists but is not set in Fluxer correctly + will_create_parent = discord_parent_id in missing_category_ids + expected_parent_fluxer_id = self.state.get_fluxer_category_id(discord_parent_id) if discord_parent_id else None + + if will_create_parent or current_fluxer_parent != expected_parent_fluxer_id: + channels_to_move.append((ch, fluxer_id)) - total = len(categories) + len(channels) + total = len(missing_categories) + len(channels_to_create) + len(channels_to_move) current_idx = 0 if total == 0: return # Migrate Categories first - for cat in categories: + for cat in missing_categories: if not self.is_running: break state_key = str(cat.id) - # 4 corresponds to Category type in Discord/Fluxer typically fluxer_id = await self.fluxer_writer.create_channel(cat.name, type=4) self.state.set_category_mapping(state_key, fluxer_id) @@ -237,8 +261,8 @@ class MigrationEngine: if progress_callback: await progress_callback(f"Cat: {cat.name}", "Copying", current_idx, total) await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) - # Migrate Text Channels - for channel in channels: + # Create missing channels + for channel in channels_to_create: if not self.is_running: break state_key = str(channel.id) @@ -257,6 +281,17 @@ class MigrationEngine: if progress_callback: await progress_callback(channel.name, "Copying", current_idx, total) await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) + # Move existing channels if needed + for channel, fluxer_id in channels_to_move: + if not self.is_running: break + + parent_id = self.state.get_fluxer_category_id(str(channel.category_id)) if channel.category_id else None + await self.fluxer_writer.move_channel(fluxer_id, parent_id) + + current_idx += 1 + if progress_callback: await progress_callback(channel.name, "Moving", current_idx, total) + await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) + async def sync_permissions(self, progress_callback: Callable[[str, int, int], Awaitable[None]] | None = None): """Syncs category and channel role overrides/permissions.""" categories = await self.discord_reader.get_categories() diff --git a/src/discord_bot/reader.py b/src/discord_bot/reader.py index f62f683..06be458 100644 --- a/src/discord_bot/reader.py +++ b/src/discord_bot/reader.py @@ -138,4 +138,5 @@ class DiscordReader: return await attachment.read() async def close(self): - await self.client.close() + if self.client: + await self.client.close() diff --git a/src/fluxer_bot/writer.py b/src/fluxer_bot/writer.py index 917df40..b967447 100644 --- a/src/fluxer_bot/writer.py +++ b/src/fluxer_bot/writer.py @@ -119,6 +119,17 @@ class FluxerWriter: ) return str(guild_channel["id"]) + async def move_channel(self, channel_id: str, parent_id: Optional[str]) -> bool: + """ + Updates the parent category of an existing channel. + """ + assert self.client is not None + await self.client.modify_channel( + channel_id=channel_id, + parent_id=parent_id + ) + return True + async def get_channels(self) -> List[Dict[str, Any]]: """Returns all channels in the community.""" assert self.client is not None diff --git a/src/ui/app.py b/src/ui/app.py index 6fdf126..ed9fe74 100644 --- a/src/ui/app.py +++ b/src/ui/app.py @@ -143,7 +143,7 @@ class MigrationCLI: f_display = f"[bold green]\"{f_name}\"[/bold green]" if f_name else "[bold red]NOT SET UP[/bold red]" console.print(f"[bold cyan]Discord Server:[/bold cyan] {d_display}") - console.print(f"[bold magenta]Fluxer Community:[/bold magenta] {f_display}") + console.print(f"[bold #4641D9]Fluxer Community:[/bold #4641D9] {f_display}") console.print("[bold]Main Menu[/bold]") console.print("(1) Clone Server Template (Channels & Categories)") console.print("(2) Copy Roles & Permissions") @@ -249,7 +249,7 @@ class MigrationCLI: await self.engine.close_connections() return - table = Table(show_header=True, header_style="bold magenta") + table = Table(show_header=True, header_style="bold #4641D9") table.add_column("Type", width=12) table.add_column("Discord Name") table.add_column("Status", justify="right") @@ -370,7 +370,7 @@ class MigrationCLI: roles = await self.engine.discord_reader.get_roles() - table = Table(show_header=True, header_style="bold magenta") + table = Table(show_header=True, header_style="bold #4641D9") table.add_column("Discord Role") table.add_column("Status", justify="center") table.add_column("Fluxer ID", justify="right") @@ -484,7 +484,7 @@ class MigrationCLI: emojis = await self.engine.discord_reader.get_emojis() stickers = await self.engine.discord_reader.get_stickers() - table = Table(show_header=True, header_style="bold magenta") + table = Table(show_header=True, header_style="bold #4641D9") table.add_column("Type", width=10) table.add_column("Name") table.add_column("Status", justify="right") @@ -804,7 +804,7 @@ class MigrationCLI: finally: self.engine.is_running = False - table = Table(show_header=True, header_style="bold magenta", title="Migration Summary & Estimates") + table = Table(show_header=True, header_style="bold #4641D9", title="Migration Summary & Estimates") table.add_column("Item", width=15) table.add_column("Count", justify="right", width=10) table.add_column("Overhead/Details") @@ -829,7 +829,7 @@ class MigrationCLI: console.print("") console.print(table) - if not Confirm.ask(f"\nMigrate messages from Discord [cyan]#{source_channel.name}[/cyan] to Fluxer [magenta]#{target_channel.get('name')}[/magenta]?"): + if not Confirm.ask(f"\nMigrate messages from Discord [cyan]#{source_channel.name}[/cyan] to Fluxer [#4641D9]#{target_channel.get('name')}[/#4641D9]?"): return # 5. Migration Execution