diff --git a/disco-reaper.py b/disco-reaper.py index 07346ef..bd81fc7 100644 --- a/disco-reaper.py +++ b/disco-reaper.py @@ -1,7 +1,7 @@ import sys import asyncio import logging -from src.ui.disco_reaper_app import run_disco_reaper +from src.ui.reaper_app import run_disco_reaper from src.core.configuration import load_config def setup_logging(): diff --git a/server-shuttle.py b/server-shuttle.py index c382add..b93302e 100644 --- a/server-shuttle.py +++ b/server-shuttle.py @@ -2,7 +2,7 @@ import sys import asyncio import logging from pathlib import Path -from src.ui.app import run_cli +from src.ui.shuttle_app import run_cli from src.core.configuration import load_config def setup_logging(): diff --git a/src/ui/disco_reaper_app.py b/src/ui/reaper_app.py similarity index 91% rename from src/ui/disco_reaper_app.py rename to src/ui/reaper_app.py index f3930c9..a87dbea 100644 --- a/src/ui/disco_reaper_app.py +++ b/src/ui/reaper_app.py @@ -85,10 +85,10 @@ class DiscoReaperCLI: console.print("(1) Backup Server Profile") console.print("(2) Backup Messages") console.print("(3) Update & Sync Backup") - console.print("(C) Configuration") + console.print("(4) Configuration") console.print("(Q) Exit") - choice = Prompt.ask("\nSelect an option", choices=["1", "2", "3", "C", "Q"], default="Q", show_choices=False).upper() + choice = Prompt.ask("\nSelect an option", choices=["1", "2", "3", "4", "Q"], default="Q", show_choices=False).upper() if choice == "1": await self.backup_server_profile() @@ -96,7 +96,7 @@ class DiscoReaperCLI: await self.backup_messages() elif choice == "3": await self.sync_backup() - elif choice == "C": + elif choice == "4": await self.edit_configuration() elif choice == "Q": await self.engine.close_connections() @@ -154,7 +154,11 @@ class DiscoReaperCLI: await self.exporter.export_channels_structure() # 2. Select Channels - all_channels = await self.engine.discord_reader.get_channels() + with console.status("[yellow]Fetching channels & categories...[/yellow]"): + all_channels = await self.engine.discord_reader.get_channels() + all_categories = await self.engine.discord_reader.get_categories() + cat_map = {c.id: c.name for c in all_categories} + # Filter for exportable channels eligible_channels = [ c for c in all_channels @@ -166,8 +170,19 @@ class DiscoReaperCLI: return console.print(f"\n[bold]Select Channels to Backup ({len(eligible_channels)} total):[/bold]") + + # Identify duplicate names to show categories for them + name_counts = {} + for chan in eligible_channels: + name_counts[chan.name] = name_counts.get(chan.name, 0) + 1 + for i, chan in enumerate(eligible_channels): - console.print(f"({i+1}) {chan.name}") + display_name = chan.name + if name_counts[chan.name] > 1: + cat_name = cat_map.get(chan.category_id) + if cat_name: + display_name = f"{chan.name} [{cat_name}]" + console.print(f"({i+1}) {display_name}") console.print("(A) [bold green]All Channels[/bold green]") console.print("(B) Back") diff --git a/src/ui/app.py b/src/ui/shuttle_app.py similarity index 97% rename from src/ui/app.py rename to src/ui/shuttle_app.py index 8d56432..83e976d 100644 --- a/src/ui/app.py +++ b/src/ui/shuttle_app.py @@ -1065,17 +1065,30 @@ class MigrationCLI: return try: - with console.status("[yellow]Fetching Discord channels...[/yellow]"): + with console.status("[yellow]Fetching Discord channels & categories...[/yellow]"): await self.engine.start_connections() d_channels = await self.engine.discord_reader.get_channels() + d_categories = await self.engine.discord_reader.get_categories() + d_cat_map = {c.id: c.name for c in d_categories} if not d_channels: console.print("[yellow]No text channels found in Discord server.[/yellow]") return console.print("\n[bold]Select Source Discord Channel:[/bold]") + + # Identify duplicate names to show categories for them + d_name_counts = {} + for ch in d_channels: + d_name_counts[ch.name] = d_name_counts.get(ch.name, 0) + 1 + for i, ch in enumerate(d_channels): - console.print(f"({i+1}) {ch.name}") + display_name = ch.name + if d_name_counts[ch.name] > 1: + cat_name = d_cat_map.get(ch.category_id) + if cat_name: + display_name = f"{ch.name} [{cat_name}]" + console.print(f"({i+1}) {display_name}") console.print("(B) Back") d_choices = [str(i+1) for i in range(len(d_channels))] + ["B", "b"] @@ -1113,8 +1126,28 @@ class MigrationCLI: console.print(f"\n[bold]Select Target {platform_name} Channel:[/bold]") + # Identify duplicate names to show categories for them + f_name_counts = {} + for ch in f_channels: + name = ch.get('name', 'Unnamed Channel') + f_name_counts[name] = f_name_counts.get(name, 0) + 1 + + # Get category map for target platform + # For Fluxer/Stoat, the channel object in f_channels contains 'parent_id' + # We need to resolve these IDs to names. + target_cat_names = {} + for ch in full_f_channels: # use full list including categories (type 4) + if ch.get('type') == 4: + target_cat_names[str(ch.get('id'))] = ch.get('name') + for i, ch in enumerate(f_channels): - console.print(f"({i+1}) {ch.get('name', 'Unnamed Channel')}") + name = ch.get('name', 'Unnamed Channel') + display_name = name + if f_name_counts[name] > 1: + parent_id = ch.get('parent_id') + if parent_id and str(parent_id) in target_cat_names: + display_name = f"{name} [{target_cat_names[str(parent_id)]}]" + console.print(f"({i+1}) {display_name}") f_choices = [str(i+1) for i in range(len(f_channels))] + ["B", "b", "N", "n"]