implement table view in cli

This commit is contained in:
rambros 2026-02-21 23:29:36 +05:30
parent 667cb4e902
commit 27ace55242
2 changed files with 227 additions and 166 deletions

View file

@ -213,26 +213,28 @@ class MigrationEngine:
categories = await self.discord_reader.get_categories() categories = await self.discord_reader.get_categories()
channels = await self.discord_reader.get_channels() 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))]
total = len(categories) + len(channels) total = len(categories) + len(channels)
current_idx = 0 current_idx = 0
if total == 0:
return
# Migrate Categories first # Migrate Categories first
for cat in categories: for cat in categories:
if not self.is_running: break if not self.is_running: break
state_key = str(cat.id) state_key = str(cat.id)
fluxer_id = None if force else self.state.get_fluxer_category_id(state_key) # 4 corresponds to Category type in Discord/Fluxer typically
status = "Copying" fluxer_id = await self.fluxer_writer.create_channel(cat.name, type=4)
self.state.set_category_mapping(state_key, fluxer_id)
if not fluxer_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)
else:
status = "Skipping"
current_idx += 1 current_idx += 1
if progress_callback: await progress_callback(f"Cat: {cat.name}", status, current_idx, total) if progress_callback: await progress_callback(f"Cat: {cat.name}", "Copying", current_idx, total)
await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) await asyncio.sleep(self.config.migration.rate_limit_delay_seconds)
# Migrate Text Channels # Migrate Text Channels
@ -240,25 +242,19 @@ class MigrationEngine:
if not self.is_running: break if not self.is_running: break
state_key = str(channel.id) state_key = str(channel.id)
fluxer_id = None if force else self.state.get_fluxer_channel_id(state_key) topic = channel.topic if channel.topic else ""
status = "Copying" parent_id = self.state.get_fluxer_category_id(str(channel.category_id)) if channel.category_id else None
if not fluxer_id: fluxer_id = await self.fluxer_writer.create_channel(
topic = channel.topic if channel.topic else "" name=channel.name,
parent_id = self.state.get_fluxer_category_id(str(channel.category_id)) if channel.category_id else None topic=topic,
type=0,
fluxer_id = await self.fluxer_writer.create_channel( parent_id=parent_id
name=channel.name, )
topic=topic, self.state.set_channel_mapping(state_key, fluxer_id)
type=0,
parent_id=parent_id
)
self.state.set_channel_mapping(state_key, fluxer_id)
else:
status = "Skipping"
current_idx += 1 current_idx += 1
if progress_callback: await progress_callback(channel.name, status, current_idx, total) if progress_callback: await progress_callback(channel.name, "Copying", current_idx, total)
await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) 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): async def sync_permissions(self, progress_callback: Callable[[str, int, int], Awaitable[None]] | None = None):
@ -266,32 +262,34 @@ class MigrationEngine:
categories = await self.discord_reader.get_categories() categories = await self.discord_reader.get_categories()
channels = await self.discord_reader.get_channels() channels = await self.discord_reader.get_channels()
# Only sync for items that are already mapped
categories = [c for c in categories if self.state.get_fluxer_category_id(str(c.id))]
channels = [c for c in channels if self.state.get_fluxer_channel_id(str(c.id))]
total = len(categories) + len(channels) total = len(categories) + len(channels)
current_idx = 0 current_idx = 0
if total == 0:
return
# Sync Category Permissions (Role Overwrites) # Sync Category Permissions (Role Overwrites)
for cat in categories: for cat in categories:
if not self.is_running: break if not self.is_running: break
fluxer_id = self.state.get_fluxer_channel_id(str(cat.id)) fluxer_id = self.state.get_fluxer_channel_id(str(cat.id))
if fluxer_id: # In a real implementation, we would diff discord perms
# In a real implementation, we would diff discord perms # and apply them to fluxer_id using client methods.
# and apply them to fluxer_id using client methods.
pass
current_idx += 1 current_idx += 1
if progress_callback: await progress_callback(f"Cat: {cat.name}", current_idx, total) if progress_callback: await progress_callback(f"Cat: {cat.name}", current_idx, total)
await asyncio.sleep(self.config.migration.rate_limit_delay_seconds)
# Sync Channel Permissions # Sync Channel Permissions
for channel in channels: for channel in channels:
if not self.is_running: break if not self.is_running: break
fluxer_id = self.state.get_fluxer_channel_id(str(channel.id)) fluxer_id = self.state.get_fluxer_channel_id(str(channel.id))
if fluxer_id: # apply perms
pass
current_idx += 1 current_idx += 1
if progress_callback: await progress_callback(channel.name, current_idx, total) if progress_callback: await progress_callback(channel.name, current_idx, total)
await asyncio.sleep(self.config.migration.rate_limit_delay_seconds)
async def analyze_migration(self, source_channel_id: int, after_message_id: int | None = None, progress_callback: Callable[[int], Awaitable[None]] | None = None) -> Dict[str, int]: async def analyze_migration(self, source_channel_id: int, after_message_id: int | None = None, progress_callback: Callable[[int], Awaitable[None]] | None = None) -> Dict[str, int]:
""" """
@ -416,24 +414,29 @@ class MigrationEngine:
return message_count return message_count
async def migrate_roles(self, progress_callback: Callable[[str, int, int], Awaitable[None]] | None = None): async def migrate_roles(self, progress_callback: Callable[[str, int, int], Awaitable[None]] | None = None, force: bool = False):
"""Copies roles and their baseline permissions.""" """Copies roles and their baseline permissions."""
roles = await self.discord_reader.get_roles() roles = await self.discord_reader.get_roles()
if not force:
roles = [r for r in roles if not self.state.get_fluxer_role_id(str(r.id))]
total = len(roles) total = len(roles)
if total == 0:
return
for idx, role in enumerate(roles): for idx, role in enumerate(roles):
if not self.is_running: break if not self.is_running: break
fluxer_id = self.state.get_fluxer_role_id(str(role.id)) fluxer_id = await self.fluxer_writer.create_role(
if not fluxer_id: name=role.name,
fluxer_id = await self.fluxer_writer.create_role( color=role.color.value,
name=role.name, hoist=role.hoist,
color=role.color.value, mentionable=role.mentionable
hoist=role.hoist, )
mentionable=role.mentionable if fluxer_id:
) self.state.set_role_mapping(str(role.id), fluxer_id)
if fluxer_id:
self.state.set_role_mapping(str(role.id), fluxer_id)
if progress_callback: await progress_callback(role.name, idx + 1, total) if progress_callback: await progress_callback(role.name, idx + 1, total)
await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) await asyncio.sleep(self.config.migration.rate_limit_delay_seconds)
@ -452,36 +455,38 @@ class MigrationEngine:
stickers = await self.discord_reader.get_stickers() stickers = await self.discord_reader.get_stickers()
objs.extend([(s, "Sticker") for s in stickers]) objs.extend([(s, "Sticker") for s in stickers])
if not force:
objs = [(obj, obj_type) for obj, obj_type in objs if not (
self.state.get_fluxer_emoji_id(str(obj.id)) if obj_type == "Emoji" else self.state.get_fluxer_sticker_id(str(obj.id))
)]
total = len(objs) total = len(objs)
if total == 0:
return
for idx, (obj, obj_type) in enumerate(objs): for idx, (obj, obj_type) in enumerate(objs):
if not self.is_running: break if not self.is_running: break
if obj_type == "Emoji": try:
fluxer_id = None if force else self.state.get_fluxer_emoji_id(str(obj.id)) if obj_type == "Emoji":
else: img_data = await self.discord_reader.download_emoji(obj)
fluxer_id = None if force else self.state.get_fluxer_sticker_id(str(obj.id)) fluxer_id = await self.fluxer_writer.create_emoji(
name=obj.name,
if not fluxer_id: image_bytes=img_data
try: )
if obj_type == "Emoji": if fluxer_id:
img_data = await self.discord_reader.download_emoji(obj) self.state.set_emoji_mapping(str(obj.id), fluxer_id)
fluxer_id = await self.fluxer_writer.create_emoji( else:
name=obj.name, img_data = await self.discord_reader.download_sticker(obj)
image_bytes=img_data fluxer_id = await self.fluxer_writer.create_sticker(
) name=obj.name,
if fluxer_id: image_bytes=img_data
self.state.set_emoji_mapping(str(obj.id), fluxer_id) )
else: if fluxer_id:
img_data = await self.discord_reader.download_sticker(obj) self.state.set_sticker_mapping(str(obj.id), fluxer_id)
fluxer_id = await self.fluxer_writer.create_sticker( except Exception as e:
name=obj.name, logger.error(f"Error downloading/uploading {obj_type.lower()} {obj.name}: {e}")
image_bytes=img_data
)
if fluxer_id:
self.state.set_sticker_mapping(str(obj.id), fluxer_id)
except Exception as e:
logger.error(f"Error downloading/uploading {obj_type.lower()} {obj.name}: {e}")
if progress_callback: await progress_callback(obj.name, obj_type, idx + 1, total) if progress_callback: await progress_callback(obj.name, obj_type, idx + 1, total)
await asyncio.sleep(self.config.migration.rate_limit_delay_seconds) await asyncio.sleep(self.config.migration.rate_limit_delay_seconds)

View file

@ -4,6 +4,7 @@ import logging
import re import re
from rich.console import Console from rich.console import Console
from rich.prompt import Prompt, Confirm from rich.prompt import Prompt, Confirm
from rich.table import Table
from rich.panel import Panel from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
from src.config import load_config, save_config from src.config import load_config, save_config
@ -248,43 +249,48 @@ class MigrationCLI:
await self.engine.close_connections() await self.engine.close_connections()
return return
console.print("\n[bold]Server Template Preview:[/bold]") table = Table(show_header=True, header_style="bold magenta")
table.add_column("Type", width=12)
# Group channels by category table.add_column("Discord Name")
import discord table.add_column("Status", justify="right")
channels_by_cat = {}
uncategorized = []
for ch in channels:
if ch.category_id:
if ch.category_id not in channels_by_cat:
channels_by_cat[ch.category_id] = []
channels_by_cat[ch.category_id].append(ch)
else:
uncategorized.append(ch)
def print_channel(ch):
if isinstance(ch, discord.TextChannel):
color = "cyan"
elif isinstance(ch, discord.VoiceChannel):
color = "green"
elif isinstance(ch, discord.ForumChannel):
color = "magenta"
else:
color = "white"
console.print(f" [{color}]- {ch.name}[/{color}]")
# Group channels by category for status check
missing_by_cat = {}
missing_uncategorized = []
for ch in channels:
cat_id = str(ch.category_id) if ch.category_id else None
if not self.engine.state.get_fluxer_channel_id(str(ch.id)):
if cat_id:
if cat_id not in missing_by_cat: missing_by_cat[cat_id] = []
missing_by_cat[cat_id].append(ch)
else:
missing_uncategorized.append(ch)
# Build the unified preview table
for cat in categories: for cat in categories:
console.print(f"[bold yellow]{cat.name}[/bold yellow]") cat_id_str = str(cat.id)
cat_channels = channels_by_cat.get(cat.id, []) fluxer_cat_id = self.engine.state.get_fluxer_category_id(cat_id_str)
status = f"[cyan]{fluxer_cat_id}[/cyan]" if fluxer_cat_id else "[bold red]Missing[/bold red]"
table.add_row("Category", f"[bold yellow]{cat.name}[/bold yellow]", status)
# Show ALL channels under this category
cat_channels = [ch for ch in channels if str(ch.category_id) == cat_id_str]
for ch in cat_channels: for ch in cat_channels:
print_channel(ch) fluxer_ch_id = self.engine.state.get_fluxer_channel_id(str(ch.id))
ch_status = f"[cyan]{fluxer_ch_id}[/cyan]" if fluxer_ch_id else "[red]Missing[/red]"
table.add_row(" Channel", ch.name, ch_status)
# Show Uncategorized
uncategorized = [ch for ch in channels if not ch.category_id]
if uncategorized: if uncategorized:
console.print(f"[bold yellow]Uncategorized[/bold yellow]") table.add_row("Uncategorized", "", "")
for ch in uncategorized: for ch in uncategorized:
print_channel(ch) fluxer_ch_id = self.engine.state.get_fluxer_channel_id(str(ch.id))
ch_status = f"[cyan]{fluxer_ch_id}[/cyan]" if fluxer_ch_id else "[red]Missing[/red]"
table.add_row(" Channel", ch.name, ch_status)
console.print(table)
console.print("") console.print("")
# Check for existing mappings to determine if we should suggest a force re-copy # Check for existing mappings to determine if we should suggest a force re-copy
@ -295,42 +301,7 @@ class MigrationCLI:
force = False force = False
if cached_count > 0: if cached_count > 0:
console.print(f"[yellow]\u26a0 {cached_count}/{all_ids_len} item(s) already in state.json cache.[/yellow]") console.print(f"[yellow]\u26a0 {cached_count}/{all_ids_len} item(s) already in state.json cache.[/yellow]")
# End of table consolidated section, back to standard flow logic.
# List missing items
missing_categories = [cat for cat in categories if not self.engine.state.get_fluxer_category_id(str(cat.id))]
missing_channels = [ch for ch in channels if not self.engine.state.get_fluxer_channel_id(str(ch.id))]
if missing_categories or missing_channels:
console.print("\n[bold red]The following channels/categories are missing in your fluxer server:[/bold red]")
# Group missing channels by their categories
missing_by_cat = {}
missing_uncategorized = []
for ch in missing_channels:
cat_id = str(ch.category_id) if ch.category_id else None
if cat_id:
if cat_id not in missing_by_cat: missing_by_cat[cat_id] = []
missing_by_cat[cat_id].append(ch)
else:
missing_uncategorized.append(ch)
# Iterate through all categories to print missing ones or categories with missing children
for cat in categories:
cat_id_str = str(cat.id)
is_cat_missing = not self.engine.state.get_fluxer_category_id(cat_id_str)
child_missing_channels = missing_by_cat.get(cat_id_str, [])
if is_cat_missing or child_missing_channels:
footer = " [dim](Category itself is missing)[/dim]" if is_cat_missing else ""
console.print(f"[bold yellow]{cat.name}[/bold yellow]{footer}")
for ch in child_missing_channels:
print_channel(ch)
if missing_uncategorized:
console.print(f"[bold yellow]Uncategorized[/bold yellow]")
for ch in missing_uncategorized:
print_channel(ch)
console.print("")
console.print("[bold green](Y) Continue with only missing items[/bold green]") console.print("[bold green](Y) Continue with only missing items[/bold green]")
console.print("[bold red](F) Force re-clone, creates duplicate channels![/bold red]") console.print("[bold red](F) Force re-clone, creates duplicate channels![/bold red]")
@ -381,7 +352,7 @@ class MigrationCLI:
async def copy_roles(self): async def copy_roles(self):
console.print("\n[bold]Role & Permission Options[/bold]") console.print("\n[bold]Role & Permission Options[/bold]")
console.print("(1) Copy Roles & Role Permissions") console.print("(1) Clone Roles & Role Permissions")
console.print("(2) Sync Category Permissions & Channel Permissions") console.print("(2) Sync Category Permissions & Channel Permissions")
console.print("(B) Back") console.print("(B) Back")
@ -391,8 +362,47 @@ class MigrationCLI:
return return
if choice == "1": if choice == "1":
console.print("\n[bold green]Starting Role Migration...[/bold green]")
try: try:
await self.engine.start_connections()
with console.status("[yellow]Checking Fluxer for existing roles...[/yellow]"):
await self.engine.sync_roles_state()
roles = await self.engine.discord_reader.get_roles()
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Discord Role")
table.add_column("Status", justify="center")
table.add_column("Fluxer ID", justify="right")
cached_count = 0
for r in roles:
fluxer_id = self.engine.state.get_fluxer_role_id(str(r.id))
status = "[bold green]NEW[/bold green]"
fid_str = "[dim]N/A[/dim]"
if fluxer_id:
status = "[dim]already copied[/dim]"
fid_str = f"[cyan]{fluxer_id}[/cyan]"
cached_count += 1
table.add_row(r.name, status, fid_str)
console.print(table)
force = False
if cached_count > 0:
console.print(f"\n[yellow]{cached_count} role(s) already in state cache.[/yellow]")
console.print("[bold green](Y) Sync missing roles only[/bold green]")
console.print("[bold red](F) Force Overwrite[/bold red]")
console.print("[bold yellow](B) Back[/bold yellow]")
sub_choice = Prompt.ask("Select an option", choices=["Y", "F", "B"], default="Y").upper()
if sub_choice == "B":
return
elif sub_choice == "F":
force = True
console.print("\n[bold green]Starting Role Migration...[/bold green]")
with Progress( with Progress(
SpinnerColumn(), SpinnerColumn(),
TextColumn("[progress.description]{task.description}"), TextColumn("[progress.description]{task.description}"),
@ -401,14 +411,13 @@ class MigrationCLI:
console=console console=console
) as progress: ) as progress:
role_task = progress.add_task("[cyan]Copying Roles...", total=100) role_task = progress.add_task("[cyan]Syncing Roles...", total=100)
async def update_progress(item_name: str, current: int, total: int): async def update_progress(item_name: str, current: int, total: int):
progress.update(role_task, total=total, completed=current, description=f"[cyan]Copying Role: {item_name}") progress.update(role_task, total=total, completed=current, description=f"[cyan]Syncing Role: {item_name}")
await self.engine.start_connections()
self.engine.is_running = True self.engine.is_running = True
await self.engine.migrate_roles(progress_callback=update_progress) await self.engine.migrate_roles(progress_callback=update_progress, force=force)
console.print("[bold green]Role migration complete![/bold green]") console.print("[bold green]Role migration complete![/bold green]")
@ -419,8 +428,27 @@ class MigrationCLI:
self.engine.is_running = False self.engine.is_running = False
elif choice == "2": elif choice == "2":
console.print("\n[bold green]Syncing Category & Channel Permissions...[/bold green]")
try: try:
await self.engine.start_connections()
with console.status("[yellow]Analyzing categories and channels...[/yellow]"):
categories = await self.engine.discord_reader.get_categories()
channels = await self.engine.discord_reader.get_channels()
mapped_cats = sum(1 for c in categories if self.engine.state.get_fluxer_category_id(str(c.id)))
mapped_chs = sum(1 for c in channels if self.engine.state.get_fluxer_channel_id(str(c.id)))
total_mapped = mapped_cats + mapped_chs
console.print(f"\n[yellow]Ready to sync permissions for {total_mapped} items ({mapped_cats} categories, {mapped_chs} channels).[/yellow]")
console.print("[bold green](Y) Proceed with Permission synchronization[/bold green]")
console.print("[bold yellow](B) Back[/bold yellow]")
sub_choice = Prompt.ask("Select an option", choices=["Y", "B"], default="Y").upper()
if sub_choice == "B":
return
console.print("\n[bold green]Syncing Category & Channel Permissions...[/bold green]")
with Progress( with Progress(
SpinnerColumn(), SpinnerColumn(),
TextColumn("[progress.description]{task.description}"), TextColumn("[progress.description]{task.description}"),
@ -434,7 +462,6 @@ class MigrationCLI:
async def update_progress(item_name: str, current: int, total: int): async def update_progress(item_name: str, current: int, total: int):
progress.update(perm_task, total=total, completed=current, description=f"[cyan]Syncing: {item_name}") progress.update(perm_task, total=total, completed=current, description=f"[cyan]Syncing: {item_name}")
await self.engine.start_connections()
self.engine.is_running = True self.engine.is_running = True
await self.engine.sync_permissions(progress_callback=update_progress) await self.engine.sync_permissions(progress_callback=update_progress)
@ -457,22 +484,29 @@ class MigrationCLI:
emojis = await self.engine.discord_reader.get_emojis() emojis = await self.engine.discord_reader.get_emojis()
stickers = await self.engine.discord_reader.get_stickers() stickers = await self.engine.discord_reader.get_stickers()
console.print(f"\n[bold]Custom emojis found: {len(emojis)}[/bold]") table = Table(show_header=True, header_style="bold magenta")
table.add_column("Type", width=10)
table.add_column("Name")
table.add_column("Status", justify="right")
cached_emojis = 0
for e in emojis: for e in emojis:
already = self.engine.state.get_fluxer_emoji_id(str(e.id)) already = self.engine.state.get_fluxer_emoji_id(str(e.id))
tag = " [dim](already copied)[/dim]" if already else "" status = "[dim]already copied[/dim]" if already else "[bold green]NEW[/bold green]"
console.print(f" - Emoji: {e.name}{tag}") if already: cached_emojis += 1
table.add_row("Emoji", e.name, status)
console.print(f"[bold]Custom stickers found: {len(stickers)}[/bold]") cached_stickers = 0
for s in stickers: for s in stickers:
already = self.engine.state.get_fluxer_sticker_id(str(s.id)) already = self.engine.state.get_fluxer_sticker_id(str(s.id))
tag = " [dim](already copied)[/dim]" if already else "" status = "[dim]already copied[/dim]" if already else "[bold green]NEW[/bold green]"
console.print(f" - Sticker: {s.name}{tag}") if already: cached_stickers += 1
table.add_row("Sticker", s.name, status)
console.print(table)
# Warn if everything is already in the state cache # Warn if everything is already in the state cache
force = False force = False
cached_emojis = sum(1 for e in emojis if self.engine.state.get_fluxer_emoji_id(str(e.id)))
cached_stickers = sum(1 for s in stickers if self.engine.state.get_fluxer_sticker_id(str(s.id)))
total_items = len(emojis) + len(stickers) total_items = len(emojis) + len(stickers)
cached_count = cached_emojis + cached_stickers cached_count = cached_emojis + cached_stickers
@ -558,9 +592,17 @@ class MigrationCLI:
icon_status = "[bold green]FOUND[/bold green]" if metadata.get("icon_url") else "[yellow]NOT FOUND[/yellow]" icon_status = "[bold green]FOUND[/bold green]" if metadata.get("icon_url") else "[yellow]NOT FOUND[/yellow]"
banner_status = "[bold green]FOUND[/bold green]" if metadata.get("banner_url") else "[yellow]NOT FOUND[/yellow]" banner_status = "[bold green]FOUND[/bold green]" if metadata.get("banner_url") else "[yellow]NOT FOUND[/yellow]"
console.print(f"\n[bold]Server Name:[/bold] {name}") banner_status = "[bold green]FOUND[/bold green]" if metadata.get("banner_url") else "[yellow]NOT FOUND[/yellow]"
console.print(f"[bold]Server Icon:[/bold] {icon_status}")
console.print(f"[bold]Server Banner:[/bold] {banner_status}") table = Table(show_header=False, box=None)
table.add_column("Property", style="bold")
table.add_column("Value")
table.add_row("Server Name:", name)
table.add_row("Server Icon:", icon_status)
table.add_row("Server Banner:", banner_status)
console.print(Panel(table, title="[bold]Discord Server Metadata[/bold]", expand=False))
console.print("\n(1) Sync Name only") console.print("\n(1) Sync Name only")
console.print("(2) Sync Icon only") console.print("(2) Sync Icon only")
@ -762,16 +804,30 @@ class MigrationCLI:
finally: finally:
self.engine.is_running = False self.engine.is_running = False
console.print(f"\n[bold]Migration Summary:[/bold]") table = Table(show_header=True, header_style="bold magenta", title="Migration Summary & Estimates")
console.print(f"Number of messages: [bold cyan]{stats['messages']}[/bold cyan]") table.add_column("Item", width=15)
console.print(f"Number of threads: [bold cyan]{stats['threads']}[/bold cyan]") table.add_column("Count", justify="right", width=10)
console.print(f"Number of attachments: [bold cyan]{stats['attachments']}[/bold cyan]") table.add_column("Overhead/Details")
console.print("\n[bold yellow]Estimated Overhead:[/bold yellow]")
msg_time = stats['messages'] * self.config.migration.rate_limit_delay_seconds msg_time = stats['messages'] * self.config.migration.rate_limit_delay_seconds
console.print(f"- [bold]Messages:[/bold] ~{msg_time}s delay (rate limiting), {stats['messages']} API writes.") table.add_row(
console.print(f"- [bold]Threads:[/bold] {stats['threads'] * 2} extra marker messages, {stats['threads']} extra history fetches.") "Messages",
console.print(f"- [bold]Attachments:[/bold] {stats['attachments']} downloads and uploads (bandwidth & API calls).") f"[bold cyan]{stats['messages']}[/bold cyan]",
f"~{msg_time}s delay (rate limiting), {stats['messages']} API writes"
)
table.add_row(
"Threads",
f"[bold cyan]{stats['threads']}[/bold cyan]",
f"{stats['threads'] * 2} extra marker messages, {stats['threads']} extra history fetches"
)
table.add_row(
"Attachments",
f"[bold cyan]{stats['attachments']}[/bold cyan]",
f"{stats['attachments']} downloads and uploads (bandwidth & API calls)"
)
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 [magenta]#{target_channel.get('name')}[/magenta]?"):
return return