diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -12,33 +12,48 @@ from werkzeug.utils import secure_filename import requests from io import BytesIO import uuid -import hashlib import hmac +import hashlib -from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo -from telegram.ext import Application as TelegramApplication, CommandHandler, ContextTypes app = Flask(__name__) -app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey_folders_unique_telegram_mini_app") - +app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey_folders_unique_telegram") DATA_FILE = 'cloudeng_data_tg.json' -REPO_ID = "Eluza133/Z1e1u" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") -HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE +REPO_ID = os.getenv("HF_REPO_ID", "Eluza133/Z1e1u") # Ensure this is set in your environment +HF_TOKEN_WRITE = os.getenv("HF_TOKEN_WRITE") # Ensure this is set +HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE # Ensure this is set +ADMIN_TELEGRAM_IDS = os.getenv("ADMIN_TELEGRAM_IDS", "").split(',') # Comma-separated string of admin Telegram IDs +BOT_TOKEN = "6750208873:AAE2hvPlJ99dBdhGa_Brre0IIpUdOvXxHt4" + UPLOAD_FOLDER = 'uploads_tg' os.makedirs(UPLOAD_FOLDER, exist_ok=True) -BOT_TOKEN = "6750208873:AAE2hvPlJ99dBdhGa_Brre0IIpUdOvXxHt4" -WEB_APP_URL = os.getenv("WEB_APP_URL", "https://your-flask-app-domain.com/launch_mini_app") # IMPORTANT: Update this to your ngrok/deployed URL -ADMIN_USER = os.getenv("ADMIN_USER", "admin_zeus") -ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin_password_zeus") - - cache = Cache(app, config={'CACHE_TYPE': 'simple'}) logging.basicConfig(level=logging.INFO) -logging.getLogger("httpx").setLevel(logging.WARNING) # Reduce verbosity from httpx used by huggingface_hub -# --- Filesystem Helper Functions --- +def verify_telegram_auth(init_data_str, bot_token): + try: + params = dict(item.split("=", 1) for item in init_data_str.split("&")) + hash_received = params.pop("hash", None) + if not hash_received: + return None + + data_check_arr = [] + for key, value in sorted(params.items()): + data_check_arr.append(f"{key}={value}") + data_check_string = "\n".join(data_check_arr) + + secret_key = hmac.new("WebAppData".encode(), bot_token.encode(), hashlib.sha256).digest() + calculated_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest() + + if calculated_hash == hash_received: + user_data = json.loads(params.get("user", "{}")) + return user_data + return None + except Exception as e: + logging.error(f"Telegram auth verification error: {e}") + return None + def find_node_by_id(filesystem, node_id): if not filesystem: return None, None if filesystem.get('id') == node_id: @@ -82,35 +97,19 @@ def get_node_path_string(filesystem, node_id): current_id = parent.get('id') if parent else None return " / ".join(reversed(path_list)) or "Root" -def initialize_user_filesystem(user_data_param): # Renamed to avoid conflict - if 'filesystem' not in user_data_param: - user_data_param['filesystem'] = { - "type": "folder", "id": "root", "name": "root", "children": [] +def initialize_user_filesystem_if_needed(user_data, user_id_for_path_unused): + if 'filesystem' not in user_data or not isinstance(user_data['filesystem'], dict): + user_data['filesystem'] = { + "type": "folder", + "id": "root", + "name": "root", + "children": [] } - if 'files' in user_data_param and isinstance(user_data_param['files'], list): # Migration for old structure - for old_file in user_data_param['files']: - file_id = old_file.get('id', uuid.uuid4().hex) - original_filename = old_file.get('filename', 'unknown_file') - name_part, ext_part = os.path.splitext(original_filename) - unique_suffix = uuid.uuid4().hex[:8] - unique_filename = f"{name_part}_{unique_suffix}{ext_part}" - # session['username'] would not be available here directly if called outside request context - # This part of migration might need username context if hf_path depends on it - # For new users, this block is skipped. - # Let's assume this is called when user_data_param is for current user, session is available - current_user_key = session.get('username', 'unknown_user_during_migration') - - hf_path = f"cloud_files/{current_user_key}/root/{unique_filename}" - file_node = { - 'type': 'file', 'id': file_id, 'original_filename': original_filename, - 'unique_filename': unique_filename, 'path': hf_path, - 'file_type': get_file_type(original_filename), - 'upload_date': old_file.get('upload_date', datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - } - add_node(user_data_param['filesystem'], 'root', file_node) - del user_data_param['files'] - -# --- Data Persistence & HF Sync --- + # Removed old 'files' list migration logic as it's complex with TG ID change + # and relied on session['username'] which is not available globally in load_data. + # New users will get the basic structure. Existing data might need manual migration if format is very old. + + @cache.memoize(timeout=300) def load_data(): try: @@ -118,9 +117,12 @@ def load_data(): with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): + logging.warning("Data is not in dict format, initializing empty database") return {'users': {}} data.setdefault('users', {}) - # Filesystem initialization handled on login/auth if needed + for user_id_str, user_data_item in data['users'].items(): + initialize_user_filesystem_if_needed(user_data_item, user_id_str) + logging.info("Data successfully loaded and initialized for TG app") return data except Exception as e: logging.error(f"Error loading data: {e}") @@ -132,77 +134,60 @@ def save_data(data): json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf() cache.clear() + logging.info("Data saved and uploaded to HF for TG app") except Exception as e: logging.error(f"Error saving data: {e}") raise def upload_db_to_hf(): - if not HF_TOKEN_WRITE: return + if not HF_TOKEN_WRITE: + logging.warning("HF_TOKEN_WRITE not set, skipping database upload.") + return try: api = HfApi() - api.upload_file(path_or_fileobj=DATA_FILE, path_in_repo=DATA_FILE, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, commit_message=f"TGApp DB Backup {datetime.now()}") + api.upload_file( + path_or_fileobj=DATA_FILE, + path_in_repo=DATA_FILE, + repo_id=REPO_ID, + repo_type="dataset", + token=HF_TOKEN_WRITE, + commit_message=f"Backup TG App {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + ) + logging.info("Database uploaded to Hugging Face (TG App)") except Exception as e: - logging.error(f"Error uploading database: {e}") + logging.error(f"Error uploading database (TG App): {e}") def download_db_from_hf(): if not HF_TOKEN_READ: + logging.warning("HF_TOKEN_READ not set, skipping database download.") if not os.path.exists(DATA_FILE): with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f) return try: - hf_hub_download(repo_id=REPO_ID, filename=DATA_FILE, repo_type="dataset", token=HF_TOKEN_READ, local_dir=".", local_dir_use_symlinks=False) - except (hf_utils.RepositoryNotFoundError, hf_utils.EntryNotFoundError): - if not os.path.exists(DATA_FILE): - with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f) + hf_hub_download( + repo_id=REPO_ID, filename=DATA_FILE, repo_type="dataset", + token=HF_TOKEN_READ, local_dir=".", local_dir_use_symlinks=False + ) + logging.info("Database downloaded from Hugging Face (TG App)") except Exception as e: - logging.error(f"Error downloading database: {e}") + logging.error(f"Error downloading database (TG App): {e}") if not os.path.exists(DATA_FILE): with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f) def periodic_backup(): while True: - time.sleep(1800) + time.sleep(1800) # Backup every 30 minutes upload_db_to_hf() + def get_file_type(filename): - ext = filename.lower().split('.')[-1] - if ext in ['mp4', 'mov', 'avi', 'webm', 'mkv']: return 'video' - if ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']: return 'image' - if ext == 'pdf': return 'pdf' - if ext == 'txt': return 'text' + filename_lower = filename.lower() + if filename_lower.endswith(('.mp4', '.mov', '.avi', '.webm', '.mkv')): return 'video' + elif filename_lower.endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg')): return 'image' + elif filename_lower.endswith('.pdf'): return 'pdf' + elif filename_lower.endswith('.txt'): return 'text' return 'other' -# --- Auth Helpers --- -def is_admin(): - return session.get('admin_logged_in', False) - -def validate_telegram_init_data(init_data_str, bot_token_to_validate): - try: - params = {} - for item in init_data_str.split('&'): - key, value = item.split('=', 1) - params[key] = value - - hash_received = params.pop('hash') - - data_check_string_parts = [] - for key in sorted(params.keys()): - data_check_string_parts.append(f"{key}={params[key]}") - data_check_string = "\n".join(data_check_string_parts) - - secret_key = hmac.new("WebAppData".encode(), bot_token_to_validate.encode(), hashlib.sha256).digest() - calculated_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest() - - if calculated_hash == hash_received: - user_data_str = params.get('user') - if user_data_str: - return json.loads(requests.utils.unquote(user_data_str)) - return None - except Exception as e: - logging.error(f"Error validating Telegram initData: {e}, Data: {init_data_str[:200]}") - return None - -# --- CSS --- BASE_STYLE = ''' :root { --primary: #ff4d6d; --secondary: #00ddeb; --accent: #8b5cf6; @@ -293,368 +278,185 @@ body.dark .modal-close-btn { color: #555; background: rgba(255,255,255,0.2); } } ''' -# --- HTML Templates --- -LAUNCH_MINI_APP_HTML = ''' -
-Инициализация...
Пользователь: {{ username }}
-{% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %}{% for category, message in messages %}{{ item.name }}
-{{ item.original_filename | truncate(25, True) }}
-{{ item.upload_date }}
-Эта папка пуста.
{% endif %} -Зарегистрирован: {{ user.created_at }} (TG ID: {{ user.tg_id if user.tg_id else 'N/A' }})
-Файлов: {{ user.file_count }}
- +Это приложение предназначено для использования внутри Telegram.
+Пожалуйста, откройте его через вашего Telegram бота.
+Пользователей нет.
{% endfor %}{{ file.original_filename | truncate(30) }}
-В папке: {{ file.parent_path_str }}
-Загружен: {{ file.upload_date }}
-ID: {{ file.id }}
-Path: {{ file.path }}
-У пользователя нет файлов.
{% endfor %}