diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..959c295
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,158 @@
+name: Build and Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-windows:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+
+ - name: Build EXE with PyInstaller
+ run: |
+ pyinstaller --onefile --name "ABBB" --icon=abbb.ico --add-data "README.md;." abbb_v2.py
+ continue-on-error: true
+
+ - name: Build EXE without icon (fallback)
+ if: failure()
+ run: |
+ pyinstaller --onefile --name "ABBB" --add-data "README.md;." abbb_v2.py
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ABBB-Windows
+ path: dist/ABBB.exe
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v1
+ with:
+ files: dist/ABBB.exe
+ body: |
+ ## AternosBringBackBackups v${{ github.ref_name }}
+
+ ### Что нового:
+ - Прямое подключение к Aternos через веб-API
+ - Скачивание всех файлов сервера (миры, плагины, конфиги)
+ - Интерактивный режим выбора сервера и типа скачивания
+ - Сохранение учётных данных для быстрого входа
+
+ ### Как использовать:
+ 1. Запустите ABBB.exe
+ 2. Введите логин и пароль от аккаунта Aternos
+ 3. Выберите сервер
+ 4. Выберите что скачать (всё/миры/плагины)
+ 5. Файлы будут сохранены на рабочем столе в папке AternosBackups
+
+ ### Требования:
+ - Действующий аккаунт Aternos
+ - Интернет-соединение
+ draft: false
+ prerelease: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ build-linux:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+
+ - name: Build executable with PyInstaller
+ run: |
+ pyinstaller --onefile --name "ABBB" --add-data "README.md:." abbb_v2.py
+ continue-on-error: true
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ABBB-Linux
+ path: dist/ABBB
+
+ - name: Add to Release
+ uses: softprops/action-gh-release@v1
+ with:
+ files: dist/ABBB
+ body: |
+ ## AternosBringBackBackups v${{ github.ref_name }}
+
+ ### Linux версия
+ Сделайте файл исполняемым: `chmod +x ABBB`
+ Запуск: `./ABBB`
+ draft: false
+ prerelease: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ build-macos:
+ runs-on: macos-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+
+ - name: Build executable with PyInstaller
+ run: |
+ pyinstaller --onefile --name "ABBB" --add-data "README.md:." abbb_v2.py
+ continue-on-error: true
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ABBB-macOS
+ path: dist/ABBB
+
+ - name: Add to Release
+ uses: softprops/action-gh-release@v1
+ with:
+ files: dist/ABBB
+ body: |
+ ## AternosBringBackBackups v${{ github.ref_name }}
+
+ ### macOS версия
+ Запуск: `./ABBB` или дважды кликните в Finder
+ draft: false
+ prerelease: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..994a02f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+```gitignore
+# Python
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+
+# Dependencies
+venv/
+.venv/
+.env
+.env.local
+*.env.*
+
+# Logs and temp files
+*.log
+*.tmp
+
+# Editors
+.vscode/
+.idea/
+
+# Build artifacts
+dist/
+build/
+*.spec
+```
\ No newline at end of file
diff --git a/README.md b/README.md
index 7ca6cc7..12eaa5f 100644
--- a/README.md
+++ b/README.md
@@ -11,31 +11,86 @@
This utility is built on an optimized script designed to download backups (by default) to your desktop from your Aternos servers; if you are planning to switch hosting providers for your server, this utility is the perfect solution!
+## 🆕 Версия 2.0 - Прямое подключение к Aternos!
+
+Новая версия программы теперь использует **прямое подключение к API Aternos**, что делает её более надёжной и функциональной:
+
+- ✅ **Прямая авторизация** через логин/пароль Aternos
+- ✅ **Скачивание всех файлов сервера**: миры, плагины, конфиги, логи
+- ✅ **Интерактивный выбор** сервера и типа данных для скачивания
+- ✅ **Сохранение учётных данных** для быстрого входа
+- ✅ **Автоматическая сборка** в .exe через GitHub Actions
+


-### For Developers
-- Go to the [Google Cloud Console](https://console.cloud.google.com/?spm=a2ty_o01.29997173.0.0.64a155fbvmooUf)
-- Create a new project
-- Enable the Google Drive API
-- Go to APIs & Services > Credentials
-- Create an OAuth 2.0 Client ID (type: Desktop app)
-- Download the JSON file and save it as `client_secret.json` in the program folder
-
-### For user
-- Go in latest release
-- Download zip and extract folder
-- open exubetle file
-- start working
+
+### Быстрый старт
+
+#### Вариант 1: Скачать готовый релиз
+1. Перейдите в раздел [Releases](https://github.com/JunDevX/AternosBringBackBackups/releases)
+2. Скачайте последнюю версию для вашей ОС
+3. Запустите и следуйте инструкциям
+
+#### Вариант 2: Собрать из исходников
+```bash
+# Установка зависимостей
+pip install -r requirements.txt
+
+# Запуск скрипта
+python abbb_v2.py
+
+# Или собрать .exe (требуется PyInstaller)
+pyinstaller --onefile --name "ABBB" abbb_v2.py
+```
+
+

+
+

+
+### Пошаговая инструкция
+
+1. **Запустите программу** (ABBB.exe или `python abbb_v2.py`)
+2. **Введите учётные данные** от вашего аккаунта Aternos
+3. **Выберите сервер** из списка ваших серверов
+4. **Выберите тип скачивания**:
+ - Всё (миры, плагины, конфиги)
+ - Только миры
+ - Только плагины
+5. **Дождитесь завершения** — файлы будут сохранены на рабочем столе в папке `AternosBackups`


-If you encounter technical issues, first check the Issues section—a solution has likely already been found there; if that doesn't help, reach out via the Discord channel.
+Если вы encounter технические проблемы, сначала проверьте раздел Issues — возможно решение уже найдено; если это не поможет, обратитесь через канал Discord.


-not now
+### Часто задаваемые вопросы
+
+**Q: Безопасно ли вводить пароль от Aternos?**
+A: Да, программа отправляет данные напрямую на серверы Aternos через защищённое HTTPS-соединение. Пароль нигде не сохраняется (если вы сами не включите опцию сохранения).
+
+**Q: Почему не работает старая версия через Google Drive?**
+A: Aternos изменил способ создания резервных копий. Новая версия использует прямое API, что надёжнее.
+
+**Q: Можно ли скачать конкретный файл?**
+A: В текущей версии можно выбрать тип данных (миры/плагины/всё). Индивидуальный выбор файлов планируется в будущих версиях.
+
+**Q: Программа не запускается на Windows**
+A: Убедитесь, что у вас установлены Visual C++ Redistributable. Также попробуйте запустить от имени администратора.
+
+

+
+

+
+[](https://github.com/JunDevX/AternosBringBackBackups/actions/workflows/build.yml)
+
+

+
+**Лицензия:** MIT
+**Автор:** JunDevX
+**Поддержка:** [Discord](https://dsc.gg/minecraftheal)
diff --git a/abbb.spec.cfg b/abbb.spec.cfg
new file mode 100644
index 0000000..e643782
--- /dev/null
+++ b/abbb.spec.cfg
@@ -0,0 +1,15 @@
+[Build]
+app_name=ABBB
+app_version=2.0.0
+app_author=JunDevX
+
+[Output]
+onefile=True
+console=True
+icon=abbb.ico
+
+[Data]
+readme=README.md
+
+[Hook]
+hidden_imports=requests,bs4,urllib3
diff --git a/abbb_v2.py b/abbb_v2.py
new file mode 100644
index 0000000..4ccb70c
--- /dev/null
+++ b/abbb_v2.py
@@ -0,0 +1,562 @@
+#!/usr/bin/env python3
+"""
+AternosBringBackBackups (ABBB) v2.0
+CLI-утилита для скачивания всех файлов сервера Aternos через официальное API
+Скачивает миры, плагины, конфиги и другие файлы сервера
+"""
+
+import os
+import sys
+import argparse
+import json
+import time
+import zipfile
+import io
+from pathlib import Path
+from datetime import datetime
+
+try:
+ import requests
+ from bs4 import BeautifulSoup
+except ImportError:
+ print("❌ Ошибка: Не установлены необходимые библиотеки!")
+ print("Установите их командой: pip install -r requirements.txt")
+ sys.exit(1)
+
+# Константы
+ATEMOS_API_BASE = "https://aternos.org"
+APP_NAME = 'AternosBringBackBackups'
+VERSION = '2.0.0'
+
+
+class AternosAPI:
+ """Класс для работы с Aternos API через веб-интерфейс"""
+
+ def __init__(self):
+ self.session = requests.Session()
+ self.session.headers.update({
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
+ 'Accept-Language': 'en-US,en;q=0.5',
+ 'Accept-Encoding': 'gzip, deflate, br',
+ 'Connection': 'keep-alive',
+ })
+ self.server_id = None
+ self.server_name = None
+
+ def login(self, username, password):
+ """Авторизация в аккаунте Aternos"""
+ print("🔐 Авторизация в Aternos...")
+
+ # Получаем токен CSRF со страницы входа
+ try:
+ login_page = self.session.get(f"{ATEMOS_API_BASE}/login")
+ login_page.raise_for_status()
+ except requests.RequestException as e:
+ print(f"❌ Ошибка получения страницы входа: {e}")
+ return False
+
+ soup = BeautifulSoup(login_page.text, 'html.parser')
+ csrf_token = soup.find('input', {'name': 'token'})
+
+ if not csrf_token:
+ print("❌ Не удалось получить CSRF токен")
+ return False
+
+ csrf_value = csrf_token.get('value')
+
+ # Выполняем вход
+ login_data = {
+ 'user': username,
+ 'password': password,
+ 'token': csrf_value
+ }
+
+ try:
+ response = self.session.post(
+ f"{ATEMOS_API_BASE}/login",
+ data=login_data,
+ allow_redirects=True
+ )
+ response.raise_for_status()
+ except requests.RequestException as e:
+ print(f"❌ Ошибка авторизации: {e}")
+ return False
+
+ # Проверяем успешность входа
+ if "logout" in response.text.lower() or "dashboard" in response.url:
+ print("✅ Авторизация успешна!")
+ return True
+ else:
+ print("❌ Неверный логин или пароль")
+ return False
+
+ def get_servers(self):
+ """Получение списка серверов"""
+ print("📋 Получение списка серверов...")
+
+ try:
+ dashboard = self.session.get(f"{ATEMOS_API_BASE}/console")
+ dashboard.raise_for_status()
+ except requests.RequestException as e:
+ print(f"❌ Ошибка получения дашборда: {e}")
+ return []
+
+ soup = BeautifulSoup(dashboard.text, 'html.parser')
+ servers = []
+
+ # Ищем сервера в HTML
+ server_elements = soup.find_all('div', class_='server-item')
+ for server_elem in server_elements:
+ server_id = server_elem.get('data-id')
+ server_name = server_elem.find('span', class_='server-name')
+
+ if server_id and server_name:
+ servers.append({
+ 'id': server_id,
+ 'name': server_name.text.strip()
+ })
+
+ # Альтернативный поиск через select
+ if not servers:
+ select_options = soup.find_all('option', {'name': 'server'})
+ for option in select_options:
+ servers.append({
+ 'id': option.get('value'),
+ 'name': option.text.strip()
+ })
+
+ if servers:
+ print(f"✅ Найдено серверов: {len(servers)}")
+ for i, server in enumerate(servers, 1):
+ print(f" {i}. {server['name']} (ID: {server['id']})")
+
+ return servers
+
+ def select_server(self, server_id):
+ """Выбор сервера для работы"""
+ self.server_id = server_id
+ print(f"✅ Выбран сервер ID: {server_id}")
+ return True
+
+ def get_worlds(self):
+ """Получение списка миров"""
+ print("🌍 Получение списка миров...")
+
+ try:
+ worlds_page = self.session.get(f"{ATEMOS_API_BASE}/console/{self.server_id}/worlds")
+ worlds_page.raise_for_status()
+ except requests.RequestException as e:
+ print(f"❌ Ошибка получения миров: {e}")
+ return []
+
+ soup = BeautifulSoup(worlds_page.text, 'html.parser')
+ worlds = []
+
+ # Ищем миры в таблице
+ world_rows = soup.find_all('tr', class_='world-row')
+ for row in world_rows:
+ world_name = row.find('span', class_='world-name')
+ world_size = row.find('span', class_='world-size')
+ world_date = row.find('span', class_='world-date')
+
+ if world_name:
+ worlds.append({
+ 'name': world_name.text.strip(),
+ 'size': world_size.text.strip() if world_size else 'Unknown',
+ 'date': world_date.text.strip() if world_date else 'Unknown'
+ })
+
+ if worlds:
+ print(f"✅ Найдено миров: {len(worlds)}")
+
+ return worlds
+
+ def download_world(self, world_name, download_dir):
+ """Скачивание мира"""
+ print(f"📥 Скачивание мира: {world_name}")
+
+ try:
+ # Запрос на скачивание мира
+ download_url = f"{ATEMOS_API_BASE}/console/{self.server_id}/worlds/{world_name}/download"
+ response = self.session.get(download_url, stream=True)
+ response.raise_for_status()
+
+ # Сохраняем файл
+ file_path = download_dir / f"{world_name}.zip"
+ with open(file_path, 'wb') as f:
+ for chunk in response.iter_content(chunk_size=8192):
+ f.write(chunk)
+
+ print(f"✅ Мир сохранён: {file_path}")
+ return True
+
+ except Exception as e:
+ print(f"❌ Ошибка скачивания мира {world_name}: {e}")
+ return False
+
+ def get_plugins(self):
+ """Получение списка плагинов"""
+ print("🔌 Получение списка плагинов...")
+
+ try:
+ plugins_page = self.session.get(f"{ATEMOS_API_BASE}/console/{self.server_id}/plugins")
+ plugins_page.raise_for_status()
+ except requests.RequestException as e:
+ print(f"❌ Ошибка получения плагинов: {e}")
+ return []
+
+ soup = BeautifulSoup(plugins_page.text, 'html.parser')
+ plugins = []
+
+ # Ищем установленные плагины
+ plugin_elements = soup.find_all('div', class_='plugin-item')
+ for plugin in plugin_elements:
+ plugin_name = plugin.find('span', class_='plugin-name')
+ plugin_version = plugin.find('span', class_='plugin-version')
+
+ if plugin_name:
+ plugins.append({
+ 'name': plugin_name.text.strip(),
+ 'version': plugin_version.text.strip() if plugin_version else 'Unknown'
+ })
+
+ if plugins:
+ print(f"✅ Найдено плагинов: {len(plugins)}")
+
+ return plugins
+
+ def get_files_list(self, path="/"):
+ """Получение списка файлов сервера"""
+ print(f"📁 Получение списка файлов в {path}...")
+
+ try:
+ files_page = self.session.get(
+ f"{ATEMOS_API_BASE}/console/{self.server_id}/files",
+ params={'path': path}
+ )
+ files_page.raise_for_status()
+ except requests.RequestException as e:
+ print(f"❌ Ошибка получения файлов: {e}")
+ return []
+
+ soup = BeautifulSoup(files_page.text, 'html.parser')
+ files = []
+
+ # Ищем файлы в таблице
+ file_rows = soup.find_all('tr', class_='file-row')
+ for row in file_rows:
+ file_name = row.find('span', class_='file-name')
+ file_size = row.find('span', class_='file-size')
+ file_type = row.find('span', class_='file-type')
+
+ if file_name:
+ files.append({
+ 'name': file_name.text.strip(),
+ 'size': file_size.text.strip() if file_size else 'Unknown',
+ 'type': file_type.text.strip() if file_type else 'file'
+ })
+
+ return files
+
+ def download_file(self, file_path, download_dir):
+ """Скачивание отдельного файла"""
+ print(f"📥 Скачивание файла: {file_path}")
+
+ try:
+ download_url = f"{ATEMOS_API_BASE}/console/{self.server_id}/files/download"
+ response = self.session.get(
+ download_url,
+ params={'path': file_path},
+ stream=True
+ )
+ response.raise_for_status()
+
+ # Сохраняем файл
+ file_name = Path(file_path).name
+ save_path = download_dir / file_name
+ with open(save_path, 'wb') as f:
+ for chunk in response.iter_content(chunk_size=8192):
+ f.write(chunk)
+
+ print(f"✅ Файл сохранён: {save_path}")
+ return True
+
+ except Exception as e:
+ print(f"❌ Ошибка скачивания {file_path}: {e}")
+ return False
+
+ def download_all_files(self, download_dir):
+ """Скачивание всех файлов сервера"""
+ print("📦 Скачивание всех файлов сервера...")
+
+ # Создаём структуру папок
+ server_dir = download_dir / f"server_{self.server_id}"
+ server_dir.mkdir(parents=True, exist_ok=True)
+
+ # Основные директории для скачивания
+ directories = [
+ '/world',
+ '/world_nether',
+ '/world_the_end',
+ '/plugins',
+ '/config',
+ '/logs'
+ ]
+
+ success_count = 0
+ fail_count = 0
+
+ for directory in directories:
+ try:
+ files = self.get_files_list(directory)
+ for file_info in files:
+ if self.download_file(f"{directory}/{file_info['name']}", server_dir):
+ success_count += 1
+ else:
+ fail_count += 1
+ except Exception as e:
+ print(f"⚠️ Ошибка обработки директории {directory}: {e}")
+
+ print(f"\n{'='*50}")
+ print(f"📊 Итого скачано файлов: {success_count}")
+ print(f"❌ Ошибок: {fail_count}")
+ print(f"{'='*50}")
+
+ return success_count > 0
+
+
+class ABBB:
+ """Основной класс приложения"""
+
+ def __init__(self):
+ self.api = AternosAPI()
+ self.setup_directories()
+
+ def setup_directories(self):
+ """Настройка базовых директорий"""
+ self.home_dir = Path.home()
+ self.desktop_dir = self.home_dir / 'Desktop'
+ self.downloads_dir = self.home_dir / 'Downloads'
+
+ # Создаём директорию для загрузок ABBB
+ self.abbb_dir = self.desktop_dir / 'AternosBackups'
+ self.abbb_dir.mkdir(parents=True, exist_ok=True)
+
+ def load_credentials(self):
+ """Загрузка учётных данных из файла"""
+ cred_file = Path('aternos_credentials.json')
+
+ if cred_file.exists():
+ try:
+ with open(cred_file, 'r') as f:
+ creds = json.load(f)
+ return creds.get('username'), creds.get('password')
+ except Exception as e:
+ print(f"⚠️ Ошибка загрузки учётных данных: {e}")
+
+ return None, None
+
+ def save_credentials(self, username, password, save=False):
+ """Сохранение учётных данных"""
+ if save:
+ cred_file = Path('aternos_credentials.json')
+ try:
+ with open(cred_file, 'w') as f:
+ json.dump({'username': username, 'password': password}, f)
+ print("✅ Учётные данные сохранены")
+ except Exception as e:
+ print(f"⚠️ Ошибка сохранения учётных данных: {e}")
+
+ def interactive_login(self):
+ """Интерактивная авторизация"""
+ # Пробуем загрузить сохранённые данные
+ username, password = self.load_credentials()
+
+ if username and password:
+ print(f"💾 Найдены сохранённые учётные данные для: {username}")
+ response = input("Использовать сохранённые данные? (y/n): ").strip().lower()
+ if response == 'y':
+ if self.api.login(username, password):
+ return True
+
+ # Запрашиваем новые данные
+ print("\n📝 Введите учётные данные Aternos:")
+ username = input("Логин/Email: ").strip()
+
+ # Маскируем ввод пароля
+ try:
+ import getpass
+ password = getpass.getpass("Пароль: ")
+ except:
+ password = input("Пароль: ").strip()
+
+ if self.api.login(username, password):
+ # Предлагаем сохранить данные
+ save_response = input("Сохранить учётные данные для следующего входа? (y/n): ").strip().lower()
+ self.save_credentials(username, password, save_response == 'y')
+ return True
+
+ return False
+
+ def select_server_interactive(self):
+ """Интерактивный выбор сервера"""
+ servers = self.api.get_servers()
+
+ if not servers:
+ print("❌ Сервера не найдены!")
+ return False
+
+ if len(servers) == 1:
+ return self.api.select_server(servers[0]['id'])
+
+ # Выбор сервера
+ while True:
+ try:
+ choice = input(f"\nВыберите сервер (1-{len(servers)}): ").strip()
+ choice_idx = int(choice) - 1
+
+ if 0 <= choice_idx < len(servers):
+ return self.api.select_server(servers[choice_idx]['id'])
+ else:
+ print("❌ Неверный номер")
+ except ValueError:
+ print("❌ Введите число")
+
+ def download_backup(self, download_type='all', custom_path=None):
+ """Скачивание резервной копии"""
+ # Определяем директорию для скачивания
+ if custom_path:
+ download_dir = Path(custom_path)
+ download_dir.mkdir(parents=True, exist_ok=True)
+ else:
+ download_dir = self.abbb_dir
+
+ print(f"\n📂 Директория для скачивания: {download_dir}\n")
+
+ if download_type == 'world':
+ worlds = self.api.get_worlds()
+ if worlds:
+ print("\nДоступные миры:")
+ for i, world in enumerate(worlds, 1):
+ print(f" {i}. {world['name']} ({world['size']})")
+
+ try:
+ choice = input(f"\nВыберите мир (1-{len(worlds)}) или 'all' для всех: ").strip()
+ if choice.lower() == 'all':
+ for world in worlds:
+ self.api.download_world(world['name'], download_dir)
+ else:
+ choice_idx = int(choice) - 1
+ if 0 <= choice_idx < len(worlds):
+ self.api.download_world(worlds[choice_idx]['name'], download_dir)
+ except ValueError:
+ print("❌ Неверный выбор")
+
+ elif download_type == 'plugins':
+ plugins = self.api.get_plugins()
+ if plugins:
+ print(f"\n✅ Найдено плагинов: {len(plugins)}")
+ # Здесь можно добавить логику скачивания плагинов
+
+ elif download_type == 'all':
+ print("🔄 Начинается полное скачивание сервера...")
+ self.api.download_all_files(download_dir)
+
+ print(f"\n🎉 Готово! Файлы сохранены в: {download_dir}")
+
+
+def main():
+ """Главная функция"""
+ parser = argparse.ArgumentParser(
+ description=f'{APP_NAME} v{VERSION} - Скачивание файлов сервера Aternos',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+Примеры использования:
+ abbb_v2.py # Интерактивный режим
+ abbb_v2.py --download all # Скачать всё
+ abbb_v2.py --download world # Скачать только миры
+ abbb_v2.py --download plugins # Скачать только плагины
+ abbb_v2.py --output ./backups # Указать свою папку для скачивания
+ """
+ )
+
+ parser.add_argument(
+ '--download',
+ choices=['all', 'world', 'plugins', 'files'],
+ default=None,
+ help='Что скачивать (по умолчанию: интерактивный выбор)'
+ )
+
+ parser.add_argument(
+ '--output',
+ type=str,
+ help='Папка для скачивания файлов'
+ )
+
+ parser.add_argument(
+ '--no-save-creds',
+ action='store_true',
+ help='Не сохранять учётные данные'
+ )
+
+ parser.add_argument(
+ '--version',
+ action='version',
+ version=f'{APP_NAME} {VERSION}'
+ )
+
+ args = parser.parse_args()
+
+ # Выводим заголовок
+ print(f"\n{'='*60}")
+ print(f"🎮 {APP_NAME} v{VERSION}")
+ print(f"{'='*60}\n")
+
+ # Инициализация приложения
+ app = ABBB()
+
+ try:
+ # Авторизация
+ if not app.interactive_login():
+ print("❌ Авторизация не удалась")
+ sys.exit(1)
+
+ # Выбор сервера
+ if not app.select_server_interactive():
+ print("❌ Сервер не выбран")
+ sys.exit(1)
+
+ # Скачивание
+ if args.download:
+ app.download_backup(args.download, args.output)
+ else:
+ # Интерактивный выбор
+ print("\nЧто вы хотите скачать?")
+ print("1. Всё (миры, плагины, конфиги)")
+ print("2. Только миры")
+ print("3. Только плагины")
+ print("4. Отмена")
+
+ choice = input("\nВаш выбор (1-4): ").strip()
+
+ if choice == '1':
+ app.download_backup('all', args.output)
+ elif choice == '2':
+ app.download_backup('world', args.output)
+ elif choice == '3':
+ app.download_backup('plugins', args.output)
+ else:
+ print("❌ Отменено")
+ sys.exit(0)
+
+ except KeyboardInterrupt:
+ print("\n\n⚠️ Прервано пользователем")
+ sys.exit(1)
+ except Exception as e:
+ print(f"\n❌ Критическая ошибка: {e}")
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements.txt b/requirements.txt
index e1eccf6..9713f17 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,7 @@
-google-auth>=2.0.0
-google-auth-oauthlib>=1.0.0
-google-auth-httplib2>=0.1.0
-google-api-python-client>=2.0.0
\ No newline at end of file
+google-auth>=2.0.0
+google-auth-oauthlib>=1.0.0
+google-auth-httplib2>=0.1.0
+google-api-python-client>=2.0.0
+requests>=2.31.0
+beautifulsoup4>=4.12.0
+pyinstaller>=6.0.0
\ No newline at end of file