improve message state handling
This commit is contained in:
parent
6600be74c3
commit
17d9094084
3 changed files with 140 additions and 88 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -20,7 +20,7 @@ wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
temp.txt
|
*.txt
|
||||||
|
|
||||||
# Virtual Environment
|
# Virtual Environment
|
||||||
venv/
|
venv/
|
||||||
|
|
@ -32,8 +32,8 @@ config.yaml
|
||||||
!config.example.yaml
|
!config.example.yaml
|
||||||
|
|
||||||
# Logs and State
|
# Logs and State
|
||||||
migration.log
|
*.log
|
||||||
state.json
|
*.json
|
||||||
|
|
||||||
# Temporary Test Scripts
|
# Temporary Test Scripts
|
||||||
test_*.py
|
test_*.py
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,28 @@ from typing import Dict, Any
|
||||||
class MigrationState:
|
class MigrationState:
|
||||||
"""Manages persistence of the migration state to allow resumability."""
|
"""Manages persistence of the migration state to allow resumability."""
|
||||||
|
|
||||||
def __init__(self, state_file: str | Path = "state.json"):
|
def __init__(self, state_file: str | Path = "state.json", messages_file: str | Path = "messages.json"):
|
||||||
self.state_file = Path(state_file)
|
self.state_file = Path(state_file)
|
||||||
|
self.messages_file = Path(messages_file)
|
||||||
|
|
||||||
# mappings: discord_id -> fluxer_id
|
# mappings: discord_id -> fluxer_id
|
||||||
self.channel_map: Dict[str, str] = {}
|
self.channel_map: Dict[str, str] = {}
|
||||||
self.category_map: Dict[str, str] = {}
|
self.category_map: Dict[str, str] = {}
|
||||||
self.role_map: Dict[str, str] = {}
|
self.role_map: Dict[str, str] = {}
|
||||||
self.emoji_map: Dict[str, str] = {}
|
self.emoji_map: Dict[str, str] = {}
|
||||||
self.sticker_map: Dict[str, str] = {}
|
self.sticker_map: Dict[str, str] = {}
|
||||||
self.message_map: Dict[str, str] = {}
|
|
||||||
|
|
||||||
# tracking last message timestamp per channel to resume
|
# message tracking
|
||||||
|
self.message_map: Dict[str, str] = {}
|
||||||
self.last_message_timestamps: Dict[str, str] = {}
|
self.last_message_timestamps: Dict[str, str] = {}
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
migrated_state = False
|
||||||
|
migrated_messages = False
|
||||||
|
|
||||||
|
# 1. Load primary state.json
|
||||||
if self.state_file.exists():
|
if self.state_file.exists():
|
||||||
with open(self.state_file, "r", encoding="utf-8") as f:
|
with open(self.state_file, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
@ -29,109 +35,133 @@ class MigrationState:
|
||||||
self.role_map = data.get("roles", {})
|
self.role_map = data.get("roles", {})
|
||||||
self.emoji_map = data.get("emojis", {})
|
self.emoji_map = data.get("emojis", {})
|
||||||
self.sticker_map = data.get("stickers", {})
|
self.sticker_map = data.get("stickers", {})
|
||||||
self.message_map = data.get("messages", {})
|
|
||||||
self.last_message_timestamps = data.get("last_message_timestamps", {})
|
|
||||||
|
|
||||||
|
# Check for legacy messages in state.json
|
||||||
|
if "messages" in data or "last_message_timestamps" in data:
|
||||||
|
self.message_map = data.get("messages", {})
|
||||||
|
self.last_message_timestamps = data.get("last_message_timestamps", {})
|
||||||
|
migrated_messages = True # We found legacy data, we should write it out to messages.json later
|
||||||
|
|
||||||
# Legacy Migration: Move role_, emoji_, sticker_ from channel_map to dedicated maps
|
# Legacy Migration: Move role_, emoji_, sticker_ from channel_map to dedicated maps
|
||||||
migrated = False
|
|
||||||
legacy_keys = list(self.channel_map.keys())
|
legacy_keys = list(self.channel_map.keys())
|
||||||
for k in legacy_keys:
|
for k in legacy_keys:
|
||||||
if k.startswith("role_"):
|
if k.startswith("role_"):
|
||||||
discord_id = k.replace("role_", "")
|
discord_id = k.replace("role_", "")
|
||||||
self.role_map[discord_id] = self.channel_map.pop(k)
|
self.role_map[discord_id] = self.channel_map.pop(k)
|
||||||
migrated = True
|
migrated_state = True
|
||||||
elif k.startswith("emoji_"):
|
elif k.startswith("emoji_"):
|
||||||
discord_id = k.replace("emoji_", "")
|
discord_id = k.replace("emoji_", "")
|
||||||
self.emoji_map[discord_id] = self.channel_map.pop(k)
|
self.emoji_map[discord_id] = self.channel_map.pop(k)
|
||||||
migrated = True
|
migrated_state = True
|
||||||
elif k.startswith("sticker_"):
|
elif k.startswith("sticker_"):
|
||||||
discord_id = k.replace("sticker_", "")
|
discord_id = k.replace("sticker_", "")
|
||||||
self.sticker_map[discord_id] = self.channel_map.pop(k)
|
self.sticker_map[discord_id] = self.channel_map.pop(k)
|
||||||
migrated = True
|
migrated_state = True
|
||||||
|
|
||||||
if migrated:
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def save(self):
|
# 2. Load separate messages.json (overrides legacy state.json)
|
||||||
|
if self.messages_file.exists():
|
||||||
|
with open(self.messages_file, "r", encoding="utf-8") as f:
|
||||||
|
msg_data = json.load(f)
|
||||||
|
self.message_map = msg_data.get("messages", {})
|
||||||
|
self.last_message_timestamps = msg_data.get("last_message_timestamps", {})
|
||||||
|
migrated_messages = False # No need to force a migrating save since it already exists
|
||||||
|
|
||||||
|
# 3. Save if we migrated any legacy data to separate maps/files
|
||||||
|
if migrated_state or migrated_messages:
|
||||||
|
self.save_state()
|
||||||
|
if migrated_messages:
|
||||||
|
self.save_messages()
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
"""Saves only the core server configuration (channels, roles, emojis)."""
|
||||||
data = {
|
data = {
|
||||||
"channels": self.channel_map,
|
"channels": self.channel_map,
|
||||||
"categories": self.category_map,
|
"categories": self.category_map,
|
||||||
"roles": self.role_map,
|
"roles": self.role_map,
|
||||||
"emojis": self.emoji_map,
|
"emojis": self.emoji_map,
|
||||||
"stickers": self.sticker_map,
|
"stickers": self.sticker_map
|
||||||
"last_message_timestamps": self.last_message_timestamps,
|
|
||||||
"messages": self.message_map
|
|
||||||
}
|
}
|
||||||
with open(self.state_file, "w", encoding="utf-8") as f:
|
with open(self.state_file, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, indent=4)
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
def save_messages(self):
|
||||||
|
"""Saves only the message tracking data."""
|
||||||
|
data = {
|
||||||
|
"last_message_timestamps": self.last_message_timestamps,
|
||||||
|
"messages": self.message_map
|
||||||
|
}
|
||||||
|
with open(self.messages_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
# --- Type Specific Getters/Setters ---
|
||||||
|
|
||||||
def set_channel_mapping(self, discord_id: str, fluxer_id: str):
|
def set_channel_mapping(self, discord_id: str, fluxer_id: str):
|
||||||
self.channel_map[str(discord_id)] = str(fluxer_id)
|
self.channel_map[str(discord_id)] = str(fluxer_id)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def get_fluxer_channel_id(self, discord_id: str) -> str | None:
|
def get_fluxer_channel_id(self, discord_id: str) -> str | None:
|
||||||
return self.channel_map.get(str(discord_id))
|
return self.channel_map.get(str(discord_id))
|
||||||
|
|
||||||
def remove_channel_mapping(self, discord_id: str):
|
def remove_channel_mapping(self, discord_id: str):
|
||||||
self.channel_map.pop(str(discord_id), None)
|
self.channel_map.pop(str(discord_id), None)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def set_category_mapping(self, discord_id: str, fluxer_id: str):
|
def set_category_mapping(self, discord_id: str, fluxer_id: str):
|
||||||
self.category_map[str(discord_id)] = str(fluxer_id)
|
self.category_map[str(discord_id)] = str(fluxer_id)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def get_fluxer_category_id(self, discord_id: str) -> str | None:
|
def get_fluxer_category_id(self, discord_id: str) -> str | None:
|
||||||
return self.category_map.get(str(discord_id))
|
return self.category_map.get(str(discord_id))
|
||||||
|
|
||||||
def remove_category_mapping(self, discord_id: str):
|
def remove_category_mapping(self, discord_id: str):
|
||||||
self.category_map.pop(str(discord_id), None)
|
self.category_map.pop(str(discord_id), None)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def set_message_mapping(self, discord_id: str, fluxer_id: str):
|
|
||||||
self.message_map[str(discord_id)] = str(fluxer_id)
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def get_fluxer_message_id(self, discord_id: str) -> str | None:
|
|
||||||
return self.message_map.get(str(discord_id))
|
|
||||||
|
|
||||||
def update_last_message_timestamp(self, channel_id: str, timestamp: str):
|
|
||||||
self.last_message_timestamps[str(channel_id)] = timestamp
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
# --- Type Specific Getters/Setters ---
|
|
||||||
|
|
||||||
def set_role_mapping(self, discord_id: str, fluxer_id: str):
|
def set_role_mapping(self, discord_id: str, fluxer_id: str):
|
||||||
self.role_map[str(discord_id)] = str(fluxer_id)
|
self.role_map[str(discord_id)] = str(fluxer_id)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def get_fluxer_role_id(self, discord_id: str) -> str | None:
|
def get_fluxer_role_id(self, discord_id: str) -> str | None:
|
||||||
return self.role_map.get(str(discord_id))
|
return self.role_map.get(str(discord_id))
|
||||||
|
|
||||||
def remove_role_mapping(self, discord_id: str):
|
def remove_role_mapping(self, discord_id: str):
|
||||||
self.role_map.pop(str(discord_id), None)
|
self.role_map.pop(str(discord_id), None)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def set_emoji_mapping(self, discord_id: str, fluxer_id: str):
|
def set_emoji_mapping(self, discord_id: str, fluxer_id: str):
|
||||||
self.emoji_map[str(discord_id)] = str(fluxer_id)
|
self.emoji_map[str(discord_id)] = str(fluxer_id)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def get_fluxer_emoji_id(self, discord_id: str) -> str | None:
|
def get_fluxer_emoji_id(self, discord_id: str) -> str | None:
|
||||||
return self.emoji_map.get(str(discord_id))
|
return self.emoji_map.get(str(discord_id))
|
||||||
|
|
||||||
def remove_emoji_mapping(self, discord_id: str):
|
def remove_emoji_mapping(self, discord_id: str):
|
||||||
self.emoji_map.pop(str(discord_id), None)
|
self.emoji_map.pop(str(discord_id), None)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def set_sticker_mapping(self, discord_id: str, fluxer_id: str):
|
def set_sticker_mapping(self, discord_id: str, fluxer_id: str):
|
||||||
self.sticker_map[str(discord_id)] = str(fluxer_id)
|
self.sticker_map[str(discord_id)] = str(fluxer_id)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def get_fluxer_sticker_id(self, discord_id: str) -> str | None:
|
def get_fluxer_sticker_id(self, discord_id: str) -> str | None:
|
||||||
return self.sticker_map.get(str(discord_id))
|
return self.sticker_map.get(str(discord_id))
|
||||||
|
|
||||||
def remove_sticker_mapping(self, discord_id: str):
|
def remove_sticker_mapping(self, discord_id: str):
|
||||||
self.sticker_map.pop(str(discord_id), None)
|
self.sticker_map.pop(str(discord_id), None)
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
|
# --- Message Management ---
|
||||||
|
|
||||||
|
def set_message_mapping(self, discord_id: str, fluxer_id: str):
|
||||||
|
self.message_map[str(discord_id)] = str(fluxer_id)
|
||||||
|
self.save_messages()
|
||||||
|
|
||||||
|
def get_fluxer_message_id(self, discord_id: str) -> str | None:
|
||||||
|
return self.message_map.get(str(discord_id))
|
||||||
|
|
||||||
|
def update_last_message_timestamp(self, channel_id: str, timestamp: str):
|
||||||
|
self.last_message_timestamps[str(channel_id)] = timestamp
|
||||||
|
self.save_messages()
|
||||||
|
|
||||||
# --- Danger Zone Clearing ---
|
# --- Danger Zone Clearing ---
|
||||||
|
|
||||||
|
|
@ -139,21 +169,21 @@ class MigrationState:
|
||||||
"""Clears all channel and category mappings."""
|
"""Clears all channel and category mappings."""
|
||||||
self.channel_map.clear()
|
self.channel_map.clear()
|
||||||
self.category_map.clear()
|
self.category_map.clear()
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def clear_role_mappings(self):
|
def clear_role_mappings(self):
|
||||||
"""Clears all role mappings."""
|
"""Clears all role mappings."""
|
||||||
self.role_map.clear()
|
self.role_map.clear()
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def clear_asset_mappings(self):
|
def clear_asset_mappings(self):
|
||||||
"""Clears all emoji and sticker mappings."""
|
"""Clears all emoji and sticker mappings."""
|
||||||
self.emoji_map.clear()
|
self.emoji_map.clear()
|
||||||
self.sticker_map.clear()
|
self.sticker_map.clear()
|
||||||
self.save()
|
self.save_state()
|
||||||
|
|
||||||
def clear_message_history(self):
|
def clear_message_history(self):
|
||||||
"""Clears all message mappings and timestamps."""
|
"""Clears all message mappings and timestamps."""
|
||||||
self.message_map.clear()
|
self.message_map.clear()
|
||||||
self.last_message_timestamps.clear()
|
self.last_message_timestamps.clear()
|
||||||
self.save()
|
self.save_messages()
|
||||||
|
|
|
||||||
106
src/ui/app.py
106
src/ui/app.py
|
|
@ -203,7 +203,7 @@ class MigrationCLI:
|
||||||
|
|
||||||
console.print("(Q) Exit")
|
console.print("(Q) Exit")
|
||||||
|
|
||||||
choice = Prompt.ask("\nSelect an option", choices=["1", "2", "3", "4", "5", "6", "7", "Q", "q"], default="Q").upper()
|
choice = Prompt.ask("\nSelect an option [1/2/3/4/5/6/7/Q]", choices=["1", "2", "3", "4", "5", "6", "7", "Q", "q"], default="Q", show_choices=False).upper()
|
||||||
|
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
await self.clone_server_template()
|
await self.clone_server_template()
|
||||||
|
|
@ -381,7 +381,7 @@ class MigrationCLI:
|
||||||
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]")
|
||||||
console.print("[bold yellow](B) Back[/bold yellow]")
|
console.print("[bold yellow](B) Back[/bold yellow]")
|
||||||
|
|
||||||
choice = Prompt.ask("Select an option", choices=["Y", "F", "B"], default="Y").upper()
|
choice = Prompt.ask("Select an option [Y/F/B]", choices=["Y", "y", "F", "f", "B", "b"], default="Y", show_choices=False).upper()
|
||||||
|
|
||||||
if choice == "B":
|
if choice == "B":
|
||||||
await self.engine.close_connections()
|
await self.engine.close_connections()
|
||||||
|
|
@ -430,7 +430,7 @@ class MigrationCLI:
|
||||||
console.print("(2) Sync Category Permissions & Channel Permissions")
|
console.print("(2) Sync Category Permissions & Channel Permissions")
|
||||||
console.print("(B) Back")
|
console.print("(B) Back")
|
||||||
|
|
||||||
choice = Prompt.ask("Select an option", choices=["1", "2", "B", "b"], default="B").upper()
|
choice = Prompt.ask("Select an option [1/2/B]", choices=["1", "2", "B", "b"], default="B", show_choices=False).upper()
|
||||||
|
|
||||||
if choice == "B":
|
if choice == "B":
|
||||||
return
|
return
|
||||||
|
|
@ -469,7 +469,7 @@ class MigrationCLI:
|
||||||
console.print("[bold red](F) Force Overwrite[/bold red]")
|
console.print("[bold red](F) Force Overwrite[/bold red]")
|
||||||
console.print("[bold yellow](B) Back[/bold yellow]")
|
console.print("[bold yellow](B) Back[/bold yellow]")
|
||||||
|
|
||||||
sub_choice = Prompt.ask("Select an option", choices=["Y", "F", "B"], default="Y").upper()
|
sub_choice = Prompt.ask("Select an option [Y/F/B]", choices=["Y", "y", "F", "f", "B", "b"], default="Y", show_choices=False).upper()
|
||||||
|
|
||||||
if sub_choice == "B":
|
if sub_choice == "B":
|
||||||
return
|
return
|
||||||
|
|
@ -524,7 +524,7 @@ class MigrationCLI:
|
||||||
console.print("[bold green](Y) Proceed with Permission synchronization[/bold green]")
|
console.print("[bold green](Y) Proceed with Permission synchronization[/bold green]")
|
||||||
console.print("[bold yellow](B) Back[/bold yellow]")
|
console.print("[bold yellow](B) Back[/bold yellow]")
|
||||||
|
|
||||||
sub_choice = Prompt.ask("Select an option", choices=["Y", "B"], default="Y").upper()
|
sub_choice = Prompt.ask("Select an option [Y/B]", choices=["Y", "y", "B", "b"], default="Y", show_choices=False).upper()
|
||||||
|
|
||||||
if sub_choice == "B":
|
if sub_choice == "B":
|
||||||
return
|
return
|
||||||
|
|
@ -598,7 +598,7 @@ class MigrationCLI:
|
||||||
console.print("(3) Sync Emojis and Stickers")
|
console.print("(3) Sync Emojis and Stickers")
|
||||||
console.print("(B) Back")
|
console.print("(B) Back")
|
||||||
|
|
||||||
choice = Prompt.ask("Select an option", choices=["1", "2", "3", "B", "b"], default="B").upper()
|
choice = Prompt.ask("Select an option [1/2/3/B]", choices=["1", "2", "3", "B", "b"], default="B", show_choices=False).upper()
|
||||||
|
|
||||||
if choice == "B":
|
if choice == "B":
|
||||||
return
|
return
|
||||||
|
|
@ -625,7 +625,7 @@ class MigrationCLI:
|
||||||
console.print("[bold red](F) Force Overwrite[/bold red]")
|
console.print("[bold red](F) Force Overwrite[/bold red]")
|
||||||
console.print("[bold yellow](B) Back[/bold yellow]")
|
console.print("[bold yellow](B) Back[/bold yellow]")
|
||||||
|
|
||||||
choice = Prompt.ask("Select an option", choices=["Y", "F", "B"], default="Y").upper()
|
choice = Prompt.ask("Select an option [Y/F/B]", choices=["Y", "y", "F", "f", "B", "b"], default="Y", show_choices=False).upper()
|
||||||
|
|
||||||
if choice == "B":
|
if choice == "B":
|
||||||
return
|
return
|
||||||
|
|
@ -692,7 +692,7 @@ class MigrationCLI:
|
||||||
console.print("(4) Sync Everything")
|
console.print("(4) Sync Everything")
|
||||||
console.print("(B) Back")
|
console.print("(B) Back")
|
||||||
|
|
||||||
choice = Prompt.ask("Select an option", choices=["1", "2", "3", "4", "B", "b"], default="B").upper()
|
choice = Prompt.ask("Select an option [1/2/3/4/B]", choices=["1", "2", "3", "4", "B", "b"], default="B", show_choices=False).upper()
|
||||||
|
|
||||||
if choice == "B":
|
if choice == "B":
|
||||||
return
|
return
|
||||||
|
|
@ -721,7 +721,7 @@ class MigrationCLI:
|
||||||
await self.engine.close_connections()
|
await self.engine.close_connections()
|
||||||
|
|
||||||
async def migrate_message_history(self):
|
async def migrate_message_history(self):
|
||||||
console.print("\n[bold]Message History Migration (Manual Selection)[/bold]")
|
console.print("\n[bold]Message History Migration[/bold]")
|
||||||
|
|
||||||
if not self.tokens_valid:
|
if not self.tokens_valid:
|
||||||
console.print("[bold red]Error: You must have a valid configuration (Option 6) to migrate messages.[/bold red]")
|
console.print("[bold red]Error: You must have a valid configuration (Option 6) to migrate messages.[/bold red]")
|
||||||
|
|
@ -837,34 +837,51 @@ class MigrationCLI:
|
||||||
if first_msg:
|
if first_msg:
|
||||||
# Link format: https://discord.com/channels/GUILD_ID/CHANNEL_ID/MESSAGE_ID
|
# Link format: https://discord.com/channels/GUILD_ID/CHANNEL_ID/MESSAGE_ID
|
||||||
first_msg_link = f"https://discord.com/channels/{self.config.discord_server_id}/{source_channel.id}/{first_msg.id}"
|
first_msg_link = f"https://discord.com/channels/{self.config.discord_server_id}/{source_channel.id}/{first_msg.id}"
|
||||||
console.print(f"\n[bold green]Channel Found![/bold green]")
|
server_name = self.validation_results.get("discord_server_name", "server")
|
||||||
console.print(f"Oldest Message Link: {first_msg_link}")
|
|
||||||
console.print(f"Author: [blue]{first_msg.author.name}[/blue]")
|
|
||||||
console.print(f"Content Preview: {first_msg.content[:100]}...")
|
|
||||||
|
|
||||||
console.print("\n(Y) [green]Yes, start from oldest message[/green]")
|
while True:
|
||||||
console.print("(M) Provide Specific message link or ID")
|
console.print(f"\n[bold green]Channel Found![/bold green]")
|
||||||
console.print("(B) Back")
|
console.print(f"Oldest Message Link: {first_msg_link}")
|
||||||
|
console.print(f"Author: [blue]{first_msg.author.name}[/blue]")
|
||||||
start_mode = Prompt.ask("Start migration", choices=["Y", "M", "B"], default="Y").upper()
|
console.print(f"Content Preview: {first_msg.content[:100]}...")
|
||||||
|
|
||||||
if start_mode == "B":
|
console.print("\n(Y) [green]Yes, start from oldest message[/green]")
|
||||||
return
|
console.print("(M) Provide Specific message link or ID")
|
||||||
elif start_mode == "M":
|
console.print("(B) Back")
|
||||||
custom_link = Prompt.ask("Enter Discord Message Link")
|
|
||||||
try:
|
start_mode = Prompt.ask("Start migration [Y/M/B]", choices=["Y", "y", "M", "m", "B", "b"], default="Y", show_choices=False).upper()
|
||||||
# Extract message ID from end of link
|
|
||||||
custom_id = int(custom_link.split("/")[-1])
|
if start_mode == "B":
|
||||||
# Check if message exists to give feedback
|
return
|
||||||
msg = await self.engine.discord_reader.get_message(source_channel.id, custom_id)
|
elif start_mode == "Y":
|
||||||
if msg:
|
break
|
||||||
# To include this message, we start 'after' the ID before it
|
elif start_mode == "M":
|
||||||
after_id = custom_id - 1
|
prompt_msg = "Enter Discord Message Link"
|
||||||
console.print(f"[green]Confirmed: Starting from {msg.author.name}'s message.[/green]")
|
valid_message_found = False
|
||||||
else:
|
while True:
|
||||||
console.print("[red]Warning: Message ID not found in this channel. Starting from beginning.[/red]")
|
custom_link = Prompt.ask(prompt_msg)
|
||||||
except Exception as e:
|
if str(custom_link).upper() in ["B", "BACK"]:
|
||||||
console.print(f"[red]Error parsing link: {e}. Starting from beginning.[/red]")
|
break
|
||||||
|
try:
|
||||||
|
# Extract message ID from end of link
|
||||||
|
custom_id = int(str(custom_link).strip().split("/")[-1])
|
||||||
|
# Check if message exists to give feedback
|
||||||
|
msg = await self.engine.discord_reader.get_message(source_channel.id, custom_id)
|
||||||
|
if msg:
|
||||||
|
# To include this message, we start 'after' the ID before it
|
||||||
|
after_id = custom_id - 1
|
||||||
|
console.print(f"\n[green]Confirmed: Starting from {msg.author.name}'s message.[/green]")
|
||||||
|
valid_message_found = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
console.print("\n[red]Error parsing link: 404 Not Found (error code: 10008): Unknown Message.[/red]")
|
||||||
|
prompt_msg = f"[yellow]Provide a valid Message link or Message ID from \"{server_name}\" (or 'B' to go back)[/yellow]"
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"\n[red]Error parsing link: {e}[/red]")
|
||||||
|
prompt_msg = f"[yellow]Provide a valid Message link or Message ID from \"{server_name}\" (or 'B' to go back)[/yellow]"
|
||||||
|
|
||||||
|
if valid_message_found:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
console.print("[yellow]Source channel appears to be empty. Nothing to migrate.[/yellow]")
|
console.print("[yellow]Source channel appears to be empty. Nothing to migrate.[/yellow]")
|
||||||
return
|
return
|
||||||
|
|
@ -968,9 +985,10 @@ class MigrationCLI:
|
||||||
console.print("(B) Back")
|
console.print("(B) Back")
|
||||||
|
|
||||||
choice = Prompt.ask(
|
choice = Prompt.ask(
|
||||||
"Select a Danger Zone option",
|
"Select a Danger Zone option [1/2/3/4/B]",
|
||||||
choices=["1", "2", "3", "4", "B", "b"],
|
choices=["1", "2", "3", "4", "B", "b"],
|
||||||
default="B"
|
default="B",
|
||||||
|
show_choices=False
|
||||||
).upper()
|
).upper()
|
||||||
|
|
||||||
if choice == "B":
|
if choice == "B":
|
||||||
|
|
@ -979,7 +997,8 @@ class MigrationCLI:
|
||||||
# ---- (1) Delete all Channels & Categories ----
|
# ---- (1) Delete all Channels & Categories ----
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
console.print("")
|
console.print("")
|
||||||
console.print("[bold red]This will DELETE every channel and category in the Fluxer community.[/bold red]")
|
community_name = self.validation_results.get("fluxer_community_name", "Unknown")
|
||||||
|
console.print(f"[bold red]This will DELETE every channel and category in the Fluxer community: \"{community_name}\"[/bold red]")
|
||||||
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
||||||
return
|
return
|
||||||
if not Confirm.ask("[bold red]Last chance \u2013 this cannot be undone. Continue?[/bold red]"):
|
if not Confirm.ask("[bold red]Last chance \u2013 this cannot be undone. Continue?[/bold red]"):
|
||||||
|
|
@ -1010,7 +1029,8 @@ class MigrationCLI:
|
||||||
# ---- (2) Reset Channel & Category Permissions ----
|
# ---- (2) Reset Channel & Category Permissions ----
|
||||||
elif choice == "2":
|
elif choice == "2":
|
||||||
console.print("")
|
console.print("")
|
||||||
console.print("[bold red]This will RESET all permission overwrites on every channel and category.[/bold red]")
|
community_name = self.validation_results.get("fluxer_community_name", "Unknown")
|
||||||
|
console.print(f"[bold red]This will RESET all permission overwrites on every channel and category in the Fluxer community: \"{community_name}\"[/bold red]")
|
||||||
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
||||||
return
|
return
|
||||||
if not Confirm.ask("[bold red]Last chance \u2013 all custom permission overrides will be wiped. Continue?[/bold red]"):
|
if not Confirm.ask("[bold red]Last chance \u2013 all custom permission overrides will be wiped. Continue?[/bold red]"):
|
||||||
|
|
@ -1041,7 +1061,8 @@ class MigrationCLI:
|
||||||
# ---- (3) Delete all Roles ----
|
# ---- (3) Delete all Roles ----
|
||||||
elif choice == "3":
|
elif choice == "3":
|
||||||
console.print("")
|
console.print("")
|
||||||
console.print("[bold red]This will DELETE all roles in the Fluxer community.[/bold red]")
|
community_name = self.validation_results.get("fluxer_community_name", "Unknown")
|
||||||
|
console.print(f"[bold red]This will DELETE all roles in the Fluxer community: \"{community_name}\"[/bold red]")
|
||||||
console.print("[dim]Managed roles (including the bot's own role) and @everyone are automatically protected.[/dim]")
|
console.print("[dim]Managed roles (including the bot's own role) and @everyone are automatically protected.[/dim]")
|
||||||
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
||||||
return
|
return
|
||||||
|
|
@ -1073,7 +1094,8 @@ class MigrationCLI:
|
||||||
# ---- (4) Delete all Emojis & Stickers ----
|
# ---- (4) Delete all Emojis & Stickers ----
|
||||||
elif choice == "4":
|
elif choice == "4":
|
||||||
console.print("")
|
console.print("")
|
||||||
console.print("[bold red]This will DELETE all custom emojis and stickers in the Fluxer community.[/bold red]")
|
community_name = self.validation_results.get("fluxer_community_name", "Unknown")
|
||||||
|
console.print(f"[bold red]This will DELETE all custom emojis and stickers in the Fluxer community: \"{community_name}\"[/bold red]")
|
||||||
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
|
||||||
return
|
return
|
||||||
if not Confirm.ask("[bold red]Last chance \u2013 this cannot be undone. Continue?[/bold red]"):
|
if not Confirm.ask("[bold red]Last chance \u2013 this cannot be undone. Continue?[/bold red]"):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue