diff --git a/.gitignore b/.gitignore index 46b1086..a907cef 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ venv/ ENV/ # Configuration and Secrets +config.test.yaml config.yaml !config.example.yaml diff --git a/README.md b/README.md index 419adf3..13d8a7d 100644 --- a/README.md +++ b/README.md @@ -4,31 +4,6 @@ Fluxer Reaper is a simple tool to help you move your Discord server content over ![Fluxer Reaper](fluxer-reaper.jpg) -## Getting Started - -### 1. Prerequisites -Make sure you have Python installed on your computer. - -### 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 -``` - -### 4. Run it! -Start the tool by running: -```bash -python fluxer-reaper.py -``` - ## Features * **Clone Server Structure**: Automatically creates all your Discord categories and channels in Fluxer. @@ -38,3 +13,29 @@ python fluxer-reaper.py * **Server Identity**: Syncs your server name, icon, and banner. * **Danger Zone**: Option to wipe existing channels, roles, and content in the fluxer community. + +## Getting Started + +### 1. Download +Download the latest executable from the [Releases](https://github.com/rambros3d/fluxer-reaper/releases) page. + +### 2. Run +Simply run the downloaded file to start the tool. No Python installation is required. + + +## Build tool +To create a standalone executable, simply run: + +```bash +chmod +x build.sh +./build.sh +``` + +The script will automatically set up a virtual environment, install dependencies, and generate the binary in the `dist/` folder. + + +## Vibe Code Notice + +Code is provided as is; This tool was developed largely by AI. + +Take it, use it, modify it, feel free to whatever you wish. \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..00fe258 --- /dev/null +++ b/build.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Exit on error +set -e + +echo "--- Fluxer Reaper Cross-Distro Build Script ---" +echo "NOTE: For maximum compatibility (glibc), run this on your oldest target Linux distribution." + +# Check for venv +if [ ! -d "venv" ]; then + echo "Creating virtual environment..." + python3 -m venv venv || { + echo "Error: Failed to create venv. You might need to install 'python3-venv'." + echo "Try: sudo apt install python3-venv" + exit 1 + } +fi + +echo "Activating virtual environment..." +source venv/bin/activate + +# Self-healing pip check +if ! python3 -m pip --version > /dev/null 2>&1; then + echo "Warning: pip is missing or broken in venv. Attempting repair..." + python3 -m ensurepip --default-pip || { + echo "Error: Failed to repair pip automatically." + echo "Try recreating the venv: rm -rf venv && python3 -m venv venv" + exit 1 + } +fi + +echo "Ensuring build dependencies are up to date..." +pip install --upgrade pip --quiet +pip install pyinstaller --quiet +pip install -r requirements.txt --quiet + +echo "Cleaning previous build artifacts..." +rm -rf build/ dist/ + +echo "Starting PyInstaller build..." +pyinstaller --clean fluxer-reaper.spec + +echo "Generating Launch-Reaper.sh launcher..." +cat << 'EOF' > dist/Launch-Reaper.sh +#!/bin/bash +# Convenient launcher for Fluxer Reaper +# Ensures it runs in a terminal if possible, but the binary also has internal auto-terminal logic. + +BASEDIR=$(dirname "$0") +cd "$BASEDIR" + +if [ -f "./fluxer-reaper" ]; then + ./fluxer-reaper +else + echo "Error: fluxer-reaper binary not found in $(pwd)" + read -p "Press enter to exit..." +fi +EOF +chmod +x dist/Launch-Reaper.sh + +echo "-----------------------------------" +echo "Build complete!" +echo "Standalone executable: dist/fluxer-reaper" +echo "Launcher script: dist/Launch-Reaper.sh" +echo "---" diff --git a/fluxer-reaper.py b/fluxer-reaper.py index 5fb1ba6..f537dd5 100644 --- a/fluxer-reaper.py +++ b/fluxer-reaper.py @@ -23,7 +23,70 @@ def setup_logging(): handlers=handlers ) +def relaunch_in_terminal(): + """Detects if running without a terminal on Linux and relaunches in one.""" + import os + import sys + import subprocess + import shutil + + # Only attempt on Linux + if sys.platform != "linux": + return + + # Check if we have a TTY on stdin or stdout, or if already relaunched + is_tty = sys.stdin.isatty() or sys.stdout.isatty() + if is_tty or os.environ.get("FLUXER_REAPER_RELAUNCHED"): + return + + # Diagnostic logging to help debug why it fails on some distros + debug_log = "/tmp/reaper_terminal_debug.log" + with open(debug_log, "a") as f: + f.write(f"Relaunching... isatty={is_tty}, env={os.environ.get('FLUXER_REAPER_RELAUNCHED')}\n") + + # List of terminals to try with their specific execution flags + terminals = [ + ("gnome-terminal", ["--"]), # Modern GNOME Terminal + ("ptyxis", ["--"]), # Fedora/Modern GNOME + ("x-terminal-emulator", ["-e"]), # Ubuntu/Debian standard + ("kgx", ["-e"]), # GNOME Console + ("konsole", ["-e"]), + ("xfce4-terminal", ["-e"]), + ("lxterminal", ["-e"]), + ("mate-terminal", ["-e"]), + ("alacritty", ["-e"]), + ("kitty", []), + ("xterm", ["-e"]), + ] + + # Resolve the absolute path to ourselves. + # frozen=True means we are running from a PyInstaller bundle. + if getattr(sys, 'frozen', False): + executable = os.path.abspath(sys.argv[0]) + else: + executable = sys.executable + + args = [executable] + sys.argv[1:] + + # Set env var to prevent loops + env = os.environ.copy() + env["FLUXER_REAPER_RELAUNCHED"] = "1" + + for term, cmd_args in terminals: + if shutil.which(term): + with open(debug_log, "a") as f: + f.write(f"Found terminal: {term}\n") + try: + # Construct command: term [args] executable [sys.argv] + subprocess.Popen([term] + cmd_args + args, env=env) + sys.exit(0) + except Exception as e: + with open(debug_log, "a") as f: + f.write(f"Failed to launch {term}: {e}\n") + continue + def main(): + relaunch_in_terminal() setup_logging() try: asyncio.run(run_cli()) diff --git a/src/config.py b/src/config.py index a58f6cb..23fe761 100644 --- a/src/config.py +++ b/src/config.py @@ -17,7 +17,16 @@ class AppConfig(BaseModel): def load_config(config_path: str | Path = "config.yaml") -> AppConfig: path = Path(config_path) if not path.exists(): - raise FileNotFoundError(f"Configuration file not found: {config_path}") + # Create dummy config if missing + config = AppConfig( + discord_bot_token="YOUR_DISCORD_TOKEN", + fluxer_bot_token="YOUR_FLUXER_TOKEN", + discord_server_id="000000000000000000", + fluxer_community_id="000000000000000000" + ) + save_config(config, path) + print(f"Created default configuration: {config_path}") + return config with open(path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) diff --git a/src/ui/app.py b/src/ui/app.py index ed9fe74..2dab1af 100644 --- a/src/ui/app.py +++ b/src/ui/app.py @@ -157,7 +157,7 @@ class MigrationCLI: console.print("(Q) Exit") - choice = Prompt.ask("Select an option", choices=["1", "2", "3", "4", "5", "6", "7", "Q", "q"], default="Q").upper() + choice = Prompt.ask("\nSelect an option", choices=["1", "2", "3", "4", "5", "6", "7", "Q", "q"], default="Q").upper() if choice == "1": await self.clone_server_template() @@ -272,7 +272,7 @@ class MigrationCLI: 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) + table.add_row("[bold yellow]Category[/bold yellow]", 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]