diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,4 +1,4 @@ -from flask import Flask, render_template_string, request, redirect, url_for, jsonify, session, flash +from flask import Flask, render_template_string, request, redirect, url_for, jsonify import json import os import logging @@ -7,73 +7,68 @@ import time from datetime import datetime from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError, HfHubHTTPError -from werkzeug.utils import secure_filename -from dotenv import load_dotenv import requests import uuid -import hmac -import hashlib - -load_dotenv() app = Flask(__name__) -app.secret_key = os.getenv("FLASK_SECRET_KEY", 'tontalent_secret_key_telegram_app_12345') -TONTALENT_DATA_FILE = 'tontalent_data.json' +app.secret_key = 'tontalent_secret_key_telegram_mini_app_unique_12345' +DATA_FILE = 'tontalent_data.json' -SYNC_FILES = [TONTALENT_DATA_FILE] +SYNC_FILES = [DATA_FILE] +REPO_ID = "Kgshop/tontalent2" # Replace with your actual Hugging Face repo if used +HF_TOKEN_WRITE = os.getenv("HF_TOKEN_WRITE") # For Hugging Face uploads +HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # For Hugging Face downloads -REPO_ID = "Kgshop/tontalent2" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") -HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") -BOT_TOKEN = "7549355625:AAGhdbf6x1JEzpH0mUtuxTF83Soi7MFVNZ8" +TELEGRAM_BOT_TOKEN = "7549355625:AAGhdbf6x1JEzpH0mUtuxTF83Soi7MFVNZ8" # Provided by user DOWNLOAD_RETRIES = 3 DOWNLOAD_DELAY = 5 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -# --- Hugging Face Sync Functions (largely unchanged, adapted for TONTALENT_DATA_FILE) --- def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY): if not HF_TOKEN_READ and not HF_TOKEN_WRITE: logging.warning("HF_TOKEN_READ/HF_TOKEN_WRITE not set. Download might fail for private repos.") + return False token_to_use = HF_TOKEN_READ if HF_TOKEN_READ else HF_TOKEN_WRITE files_to_download = [specific_file] if specific_file else SYNC_FILES logging.info(f"Attempting download for {files_to_download} from {REPO_ID}...") all_successful = True - for file_name in files_to_download: success = False for attempt in range(retries + 1): try: logging.info(f"Downloading {file_name} (Attempt {attempt + 1}/{retries + 1})...") - local_path = hf_hub_download( - repo_id=REPO_ID, filename=file_name, repo_type="dataset", token=token_to_use, - local_dir=".", local_dir_use_symlinks=False, force_download=True, resume_download=False + hf_hub_download( + repo_id=REPO_ID, filename=file_name, repo_type="dataset", + token=token_to_use, local_dir=".", local_dir_use_symlinks=False, + force_download=True, resume_download=False ) - logging.info(f"Successfully downloaded {file_name} to {local_path}.") + logging.info(f"Successfully downloaded {file_name}.") success = True break except RepositoryNotFoundError: - logging.error(f"Repository {REPO_ID} not found. Download cancelled for all files.") + logging.error(f"Repository {REPO_ID} not found. Download cancelled.") return False except HfHubHTTPError as e: if e.response.status_code == 404: - logging.warning(f"File {file_name} not found in repo {REPO_ID} (404). Skipping this file.") - if attempt == 0 and not os.path.exists(file_name) and file_name == TONTALENT_DATA_FILE: + logging.warning(f"File {file_name} not found in repo {REPO_ID} (404).") + if attempt == 0 and not os.path.exists(file_name): try: - with open(file_name, 'w', encoding='utf-8') as f: - json.dump({'resumes': {}, 'vacancies': {}, 'freelance_offers': {}}, f) - logging.info(f"Created empty local file {file_name} because it was not found on HF.") + if file_name == DATA_FILE: + with open(file_name, 'w', encoding='utf-8') as f: + json.dump({'resumes': [], 'vacancies': [], 'freelance_offers': []}, f) + logging.info(f"Created empty local file {file_name}.") except Exception as create_e: logging.error(f"Failed to create empty local file {file_name}: {create_e}") - success = False # Should be false if file not found unless it's the initial creation - break # Don't retry 404 + success = False + break else: - logging.error(f"HTTP error downloading {file_name} (Attempt {attempt + 1}): {e}. Retrying in {delay}s...") + logging.error(f"HTTP error downloading {file_name} (Attempt {attempt + 1}): {e}. Retrying...") except requests.exceptions.RequestException as e: - logging.error(f"Network error downloading {file_name} (Attempt {attempt + 1}): {e}. Retrying in {delay}s...") + logging.error(f"Network error downloading {file_name} (Attempt {attempt + 1}): {e}. Retrying...") except Exception as e: - logging.error(f"Unexpected error downloading {file_name} (Attempt {attempt + 1}): {e}. Retrying in {delay}s...", exc_info=True) + logging.error(f"Unexpected error downloading {file_name} (Attempt {attempt + 1}): {e}. Retrying...") if attempt < retries: time.sleep(delay) if not success: logging.error(f"Failed to download {file_name} after {retries + 1} attempts.") @@ -83,7 +78,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN def upload_db_to_hf(specific_file=None): if not HF_TOKEN_WRITE: - logging.warning("HF_TOKEN (for writing) not set. Skipping upload to Hugging Face.") + logging.warning("HF_TOKEN_WRITE not set. Skipping upload to Hugging Face.") return try: api = HfApi() @@ -93,17 +88,18 @@ def upload_db_to_hf(specific_file=None): if os.path.exists(file_name): try: api.upload_file( - path_or_fileobj=file_name, path_in_repo=file_name, repo_id=REPO_ID, repo_type="dataset", - token=HF_TOKEN_WRITE, commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + path_or_fileobj=file_name, path_in_repo=file_name, repo_id=REPO_ID, + repo_type="dataset", token=HF_TOKEN_WRITE, + commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) - logging.info(f"File {file_name} successfully uploaded to Hugging Face.") + logging.info(f"File {file_name} successfully uploaded.") except Exception as e: - logging.error(f"Error uploading file {file_name} to Hugging Face: {e}") + logging.error(f"Error uploading file {file_name}: {e}") else: logging.warning(f"File {file_name} not found locally, skipping upload.") logging.info("Finished uploading files to HF.") except Exception as e: - logging.error(f"General error during Hugging Face upload initialization or process: {e}", exc_info=True) + logging.error(f"General error during Hugging Face upload: {e}") def periodic_backup(): backup_interval = 1800 @@ -114,1108 +110,839 @@ def periodic_backup(): upload_db_to_hf() logging.info("Periodic backup finished.") -# --- Data Loading and Saving Functions --- def load_data(): - default_data = {'resumes': {}, 'vacancies': {}, 'freelance_offers': {}} + default_data = {'resumes': [], 'vacancies': [], 'freelance_offers': []} try: - with open(TONTALENT_DATA_FILE, 'r', encoding='utf-8') as file: + with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): raise FileNotFoundError for key in default_data: - if key not in data: data[key] = default_data[key] - if not isinstance(data[key], dict): data[key] = default_data[key] # Ensure correct type - logging.info(f"Local data loaded successfully from {TONTALENT_DATA_FILE}") + if key not in data: data[key] = [] + logging.info(f"Local data loaded from {DATA_FILE}") return data - except (FileNotFoundError, json.JSONDecodeError) as e: - logging.warning(f"Error loading local file {TONTALENT_DATA_FILE} ({e}). Attempting download from HF.") - - if download_db_from_hf(specific_file=TONTALENT_DATA_FILE): + except (FileNotFoundError, json.JSONDecodeError): + logging.warning(f"Local {DATA_FILE} not found or corrupt. Attempting download.") + if download_db_from_hf(specific_file=DATA_FILE): try: - with open(TONTALENT_DATA_FILE, 'r', encoding='utf-8') as file: - data = json.load(file) - if not isinstance(data, dict): - logging.error(f"Downloaded {TONTALENT_DATA_FILE} is not a dictionary. Using default.") - return default_data + with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) + if not isinstance(data, dict): return default_data for key in default_data: - if key not in data: data[key] = default_data[key] - if not isinstance(data[key], dict): data[key] = default_data[key] - logging.info(f"Data loaded successfully from {TONTALENT_DATA_FILE} after download.") + if key not in data: data[key] = [] + logging.info(f"Data loaded from {DATA_FILE} after download.") return data - except Exception as ex: - logging.error(f"Error loading downloaded {TONTALENT_DATA_FILE}: {ex}. Using default.", exc_info=True) + except Exception as e: + logging.error(f"Error loading downloaded {DATA_FILE}: {e}. Using default.") return default_data else: - logging.error(f"Failed to download {TONTALENT_DATA_FILE} from HF. Using default data structure.") - if not os.path.exists(TONTALENT_DATA_FILE): + logging.error(f"Failed to download {DATA_FILE}. Using empty default data.") + if not os.path.exists(DATA_FILE): try: - with open(TONTALENT_DATA_FILE, 'w', encoding='utf-8') as f: json.dump(default_data, f) - logging.info(f"Created empty local file {TONTALENT_DATA_FILE} after failed download.") + with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump(default_data, f) + logging.info(f"Created empty local file {DATA_FILE}.") except Exception as create_e: - logging.error(f"Failed to create empty local file {TONTALENT_DATA_FILE}: {create_e}") + logging.error(f"Failed to create empty local file {DATA_FILE}: {create_e}") return default_data def save_data(data): try: if not isinstance(data, dict): - logging.error("Attempted to save invalid data structure (not a dict). Aborting save.") + logging.error("Attempted to save invalid data structure. Aborting save.") return - for key in ['resumes', 'vacancies', 'freelance_offers']: # Ensure keys exist - if key not in data: data[key] = {} - - with open(TONTALENT_DATA_FILE, 'w', encoding='utf-8') as file: + default_keys = ['resumes', 'vacancies', 'freelance_offers'] + for key in default_keys: + if key not in data: data[key] = [] + with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) - logging.info(f"Data successfully saved to {TONTALENT_DATA_FILE}") - upload_db_to_hf(specific_file=TONTALENT_DATA_FILE) - except Exception as e: - logging.error(f"Error saving data to {TONTALENT_DATA_FILE}: {e}", exc_info=True) - -# --- Telegram Authentication --- -def validate_telegram_data(init_data_str): - try: - params = dict(kv.split('=', 1) for kv in init_data_str.split('&')) - hash_received = params.pop('hash', None) - if not hash_received: return None - - data_check_string = "\n".join(sorted([f"{k}={v}" for k, v in params.items()])) - - 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 = params.get('user') - if user_data_json: - return json.loads(requests.utils.unquote(user_data_json)) # Telegram user data needs unquoting - return {} - return None + logging.info(f"Data saved to {DATA_FILE}") + upload_db_to_hf(specific_file=DATA_FILE) except Exception as e: - logging.error(f"Error validating Telegram data: {e}", exc_info=True) - return None + logging.error(f"Error saving data to {DATA_FILE}: {e}") -# --- Templates --- -# Base template including Telegram WebApp JS and basic styling -BASE_TEMPLATE = """ +APP_HTML_TEMPLATE = """
- +