add danger zone menu

This commit is contained in:
rambros 2026-02-21 18:53:13 +05:30
parent 8740d5db45
commit 7762922d4c
4 changed files with 345 additions and 15 deletions

View file

@ -1,40 +1,40 @@
# Fluxer Reaper 🚀
# Fluxer Reaper
Fluxer Reaper is a simple tool to help you move your Discord server content over to a Fluxer community. It handles channels, roles, emojis, and even your message history.
![Fluxer Reaper](fluxer-reaper.jpg)
## 🛠️ Getting Started
## Getting Started
### 1. Prerequisites
Make sure you have Python installed on your computer.
### 2. Install the tool
### 2. Download or Clone
Clone the repository using git:
```bash
git clone https://github.com/rambros3d/fluxer-reaper.git
cd fluxer-reaper
```
Alternatively, you can download the project as a ZIP file and extract its contents.
### 3. Install the tool
Open your terminal and run:
```bash
pip install -r requirements.txt
```
### 3. Setup your config
Open the `config.yaml` file and fill in your details:
* **Discord Bot Token**: Your bot's token from the Discord Developer Portal.
* **Discord Server ID**: The ID of the server you want to copy.
* **Fluxer Bot Token**: Your Fluxer bot token.
* **Fluxer Community ID**: The ID of the Fluxer community where you want to move everything.
### 4. Run it!
Start the tool by running:
```bash
python fluxer-reaper.py
```
## ✨ Features
## Features
* **Clone Server Structure**: Automatically creates all your Discord categories and channels in Fluxer.
* **Copy Roles**: Copies your roles and their basic permissions.
* **Copy Emojis & Stickers**: Copies your custom emojis and stickers over.
* **Message History**: Select a specific channel and migrate messages starting from the oldest or a custom point.
* **Server Identity**: Syncs your server name, icon, and banner.
* **Danger Zone**: Option to wipe existing channels, roles, and content in the fluxer community.
## ⚠️ Important Tips
* **Token Validation**: The tool will double-check your tokens every time you start it or change settings.
* **Safety**: You will always be asked for confirmation before any major action (like deleting or creating many items).

View file

@ -293,3 +293,25 @@ class MigrationEngine:
def stop(self):
self.is_running = False
# ──────────────── DANGER ZONE ────────────────
async def danger_remove_logo_and_banner(self) -> None:
"""Removes the community logo and banner image."""
await self.fluxer_writer.remove_community_logo_and_banner()
async def danger_delete_all_channels(self, progress_callback=None) -> int:
"""Deletes every channel and category in the Fluxer community."""
return await self.fluxer_writer.delete_all_channels(progress_callback=progress_callback)
async def danger_reset_channel_permissions(self, progress_callback=None) -> int:
"""Resets all permission overwrites on every channel and category."""
return await self.fluxer_writer.reset_channel_permissions(progress_callback=progress_callback)
async def danger_delete_all_roles(self, progress_callback=None) -> int:
"""Deletes all deletable roles (skips managed/bot roles and @everyone)."""
return await self.fluxer_writer.delete_all_roles(progress_callback=progress_callback)
async def danger_delete_all_emojis_and_stickers(self, progress_callback=None) -> int:
"""Deletes all custom emojis and stickers from the Fluxer community."""
return await self.fluxer_writer.delete_all_emojis_and_stickers(progress_callback=progress_callback)

View file

@ -256,6 +256,146 @@ class FluxerWriter:
except Exception as e:
print(f"Failed to update community metadata: {e}")
async def remove_community_logo_and_banner(self) -> None:
"""
Removes the community logo (icon) and banner by setting them to None.
"""
assert self.client is not None
try:
await self.client.modify_guild(
guild_id=self.community_id,
icon=None,
banner=None
)
except Exception as e:
print(f"Failed to remove community logo/banner: {e}")
async def delete_all_channels(self, progress_callback=None) -> int:
"""
Deletes all channels and categories in the Fluxer community.
Returns the count of deleted channels.
"""
assert self.client is not None
channels = await self.client.get_guild_channels(self.community_id)
total = len(channels)
deleted = 0
# Delete non-category channels first, then categories
sorted_channels = sorted(channels, key=lambda c: 0 if c.get("type") == 4 else -1)
for ch in sorted_channels:
try:
await self.client.delete_channel(ch["id"])
deleted += 1
if progress_callback:
await progress_callback(ch.get("name", "Unknown"), deleted, total)
except Exception as e:
print(f"Failed to delete channel {ch.get('name')}: {e}")
return deleted
async def reset_channel_permissions(self, progress_callback=None) -> int:
"""
Resets all permission overwrites on every channel and category.
Returns the count of channels processed.
"""
assert self.client is not None
channels = await self.client.get_guild_channels(self.community_id)
total = len(channels)
processed = 0
for ch in channels:
try:
# Fetch existing overwrites and delete each one
overwrites = ch.get("permission_overwrites", [])
for ow in overwrites:
try:
await self.client.delete_channel_permission(ch["id"], ow["id"])
except Exception:
pass
processed += 1
if progress_callback:
await progress_callback(ch.get("name", "Unknown"), processed, total)
except Exception as e:
print(f"Failed to reset permissions for channel {ch.get('name')}: {e}")
return processed
async def delete_all_roles(self, progress_callback=None) -> int:
"""
Deletes all non-managed, non-default roles in the Fluxer community,
while safely skipping the bot's own managed role.
Returns the count of deleted roles.
"""
assert self.client is not None
# Fetch the bot's user ID so we can skip its managed role
bot_user_id = None
try:
if self.bot and self.bot.user:
bot_user_id = str(self.bot.user.id)
else:
me = await self.client.get_current_user()
if me:
bot_user_id = str(me.get("id"))
except Exception:
pass
roles = await self.client.get_guild_roles(self.community_id)
deletable = []
for r in roles:
# Skip @everyone (position 0) and managed roles (e.g. bot roles)
if r.get("managed") or r.get("name") == "@everyone":
continue
deletable.append(r)
total = len(deletable)
deleted = 0
for role in deletable:
try:
await self.client.delete_guild_role(self.community_id, role["id"])
deleted += 1
if progress_callback:
await progress_callback(role.get("name", "Unknown"), deleted, total)
except Exception as e:
print(f"Failed to delete role {role.get('name')}: {e}")
return deleted
async def delete_all_emojis_and_stickers(self, progress_callback=None) -> int:
"""
Deletes all custom emojis and stickers in the Fluxer community.
Returns the total count of deleted items.
"""
assert self.client is not None
deleted = 0
# Delete emojis
try:
emojis = await self.client.get_guild_emojis(self.community_id)
emoji_total = len(emojis)
for idx, emoji in enumerate(emojis):
try:
await self.client.delete_guild_emoji(self.community_id, emoji["id"])
deleted += 1
if progress_callback:
await progress_callback(emoji.get("name", "Unknown"), "Emoji", deleted, emoji_total)
except Exception as e:
print(f"Failed to delete emoji {emoji.get('name')}: {e}")
except Exception as e:
print(f"Failed to fetch emojis: {e}")
# Delete stickers
try:
stickers = await self.client.get_guild_stickers(self.community_id)
sticker_total = len(stickers)
for idx, sticker in enumerate(stickers):
try:
await self.client.delete_guild_sticker(self.community_id, sticker["id"])
deleted += 1
if progress_callback:
await progress_callback(sticker.get("name", "Unknown"), "Sticker", deleted, sticker_total)
except Exception as e:
print(f"Failed to delete sticker {sticker.get('name')}: {e}")
except Exception as e:
print(f"Failed to fetch stickers: {e}")
return deleted
async def close(self):
"""Cleanly close connection and stop bot task."""
if self.bot:

View file

@ -112,10 +112,11 @@ class MigrationCLI:
val_status = "[bold green][VALID][/bold green]" if self.tokens_valid else "[bold red][INVALID][/bold red]"
console.print(f"(6) Configuration {val_status}")
console.print("(7) [bold red]⚠ Danger Zone[/bold red]")
console.print("(Q) Exit")
choice = Prompt.ask("Select an option", choices=["1", "2", "3", "4", "5", "6", "Q", "q"], default="Q").upper()
choice = Prompt.ask("Select an option", choices=["1", "2", "3", "4", "5", "6", "7", "Q", "q"], default="Q").upper()
if choice == "1":
await self.clone_server_template()
@ -129,6 +130,8 @@ class MigrationCLI:
await self.migrate_message_history()
elif choice == "6":
await self.edit_configuration()
elif choice == "7":
await self.danger_zone()
elif choice == "Q":
console.print("[yellow]Exiting tool...[/yellow]")
await self.engine.close_connections()
@ -557,6 +560,171 @@ class MigrationCLI:
self.engine.is_running = False
await self.engine.close_connections()
async def danger_zone(self):
"""Danger Zone irreversible destructive operations on the Fluxer community."""
console.print("")
console.print(Panel.fit(
"[bold red]⚠ DANGER ZONE ⚠[/bold red]\n"
"[yellow]These actions are PERMANENT and IRREVERSIBLE on your Fluxer community.[/yellow]\n"
"[yellow]Always double-check before confirming.[/yellow]",
style="bold red"
))
console.print("(1) Remove Community Logo and Banner")
console.print("(2) Delete all Channels & Categories")
console.print("(3) Reset Channel & Category Permissions")
console.print("(4) Delete all Roles [dim](bot role is protected)[/dim]")
console.print("(5) Delete all custom Emojis & Stickers")
console.print("(B) Back")
choice = Prompt.ask(
"Select a Danger Zone option",
choices=["1", "2", "3", "4", "5", "B", "b"],
default="B"
).upper()
if choice == "B":
return
# ---- (1) Remove Logo & Banner ----
if choice == "1":
console.print("")
console.print("[bold red]This will REMOVE the community logo and banner image.[/bold red]")
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
return
if not Confirm.ask("[bold red]Last chance confirm permanent removal?[/bold red]"):
return
try:
await self.engine.start_connections()
with console.status("[red]Removing logo and banner...[/red]"):
await self.engine.danger_remove_logo_and_banner()
console.print("[bold green]Community logo and banner removed.[/bold green]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
finally:
await self.engine.close_connections()
# ---- (2) Delete all Channels & Categories ----
elif choice == "2":
console.print("")
console.print("[bold red]This will DELETE every channel and category in the Fluxer community.[/bold red]")
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
return
if not Confirm.ask("[bold red]Last chance this cannot be undone. Continue?[/bold red]"):
return
try:
await self.engine.start_connections()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=console
) as progress:
del_task = progress.add_task("[red]Deleting channels...", total=100)
async def on_channel_deleted(name: str, current: int, total: int):
progress.update(del_task, total=total, completed=current,
description=f"[red]Deleting: {name}")
count = await self.engine.danger_delete_all_channels(progress_callback=on_channel_deleted)
console.print(f"[bold green]Done. {count} channels/categories deleted.[/bold green]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
finally:
await self.engine.close_connections()
# ---- (3) Reset Channel & Category Permissions ----
elif choice == "3":
console.print("")
console.print("[bold red]This will RESET all permission overwrites on every channel and category.[/bold red]")
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
return
if not Confirm.ask("[bold red]Last chance all custom permission overrides will be wiped. Continue?[/bold red]"):
return
try:
await self.engine.start_connections()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=console
) as progress:
perm_task = progress.add_task("[red]Resetting permissions...", total=100)
async def on_perm_reset(name: str, current: int, total: int):
progress.update(perm_task, total=total, completed=current,
description=f"[red]Resetting: {name}")
count = await self.engine.danger_reset_channel_permissions(progress_callback=on_perm_reset)
console.print(f"[bold green]Done. Permissions reset on {count} channels/categories.[/bold green]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
finally:
await self.engine.close_connections()
# ---- (4) Delete all Roles ----
elif choice == "4":
console.print("")
console.print("[bold red]This will DELETE all roles in the Fluxer community.[/bold red]")
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]"):
return
if not Confirm.ask("[bold red]Last chance confirm permanent role deletion?[/bold red]"):
return
try:
await self.engine.start_connections()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=console
) as progress:
role_task = progress.add_task("[red]Deleting roles...", total=100)
async def on_role_deleted(name: str, current: int, total: int):
progress.update(role_task, total=total, completed=current,
description=f"[red]Deleting role: {name}")
count = await self.engine.danger_delete_all_roles(progress_callback=on_role_deleted)
console.print(f"[bold green]Done. {count} roles deleted.[/bold green]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
finally:
await self.engine.close_connections()
# ---- (5) Delete all Emojis & Stickers ----
elif choice == "5":
console.print("")
console.print("[bold red]This will DELETE all custom emojis and stickers in the Fluxer community.[/bold red]")
if not Confirm.ask("[bold red]Are you absolutely sure?[/bold red]"):
return
if not Confirm.ask("[bold red]Last chance this cannot be undone. Continue?[/bold red]"):
return
try:
await self.engine.start_connections()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=console
) as progress:
asset_task = progress.add_task("[red]Deleting assets...", total=100)
async def on_asset_deleted(name: str, asset_type: str, current: int, total: int):
progress.update(asset_task, total=total, completed=current,
description=f"[red]Deleting {asset_type}: {name}")
count = await self.engine.danger_delete_all_emojis_and_stickers(progress_callback=on_asset_deleted)
console.print(f"[bold green]Done. {count} emojis/stickers deleted.[/bold green]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
finally:
await self.engine.close_connections()
async def run_cli():
cli = MigrationCLI()
await cli.run()