diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,4 +1,3 @@ - #!/usr/bin/env python3 import os @@ -9,32 +8,34 @@ import json from urllib.parse import unquote, parse_qs, quote import time from datetime import datetime -import logging import threading import random +import re +import pytz from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError -import pytz BOT_TOKEN = os.getenv("BOT_TOKEN", "7835463659:AAGNePbelZIAOeaglyQi1qulOqnjs4BGQn4") HOST = '0.0.0.0' PORT = 7860 DATA_FILE = 'data.json' +ORG_INFO_FILE = 'organization_info.json' REPO_ID = "flpolprojects/examplebonus" HF_DATA_FILE_PATH = "data.json" +HF_ORG_INFO_FILE_PATH = "organization_info.json" HF_TOKEN_WRITE = os.getenv("HF_TOKEN_WRITE") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") BISHKEK_TZ = pytz.timezone('Asia/Bishkek') app = Flask(__name__) -logging.basicConfig(level=logging.INFO) app.secret_key = os.urandom(24) _data_lock = threading.Lock() +_org_info_lock = threading.Lock() visitor_data_cache = {} -org_contact_info_cache = {} +org_info_cache = {} def generate_unique_id(all_data): while True: @@ -42,171 +43,134 @@ def generate_unique_id(all_data): if new_id not in all_data: return new_id -def download_data_from_hf(): - global visitor_data_cache, org_contact_info_cache +def download_files_from_hf(): + global visitor_data_cache, org_info_cache if not HF_TOKEN_READ: - logging.warning("HF_TOKEN_READ not set. Skipping Hugging Face download.") - return False + return try: - logging.info(f"Attempting to download {HF_DATA_FILE_PATH} from {REPO_ID}...") - hf_hub_download( - repo_id=REPO_ID, - filename=HF_DATA_FILE_PATH, - repo_type="dataset", - token=HF_TOKEN_READ, - local_dir=".", - local_dir_use_symlinks=False, - force_download=True, - etag_timeout=10 - ) - logging.info("Data file successfully downloaded from Hugging Face.") + hf_hub_download(repo_id=REPO_ID, filename=HF_DATA_FILE_PATH, repo_type="dataset", token=HF_TOKEN_READ, local_dir=".", local_dir_use_symlinks=False, force_download=True, etag_timeout=10) with _data_lock: try: with open(DATA_FILE, 'r', encoding='utf-8') as f: - full_data = json.load(f) - visitor_data_cache = full_data.get('users', {}) - org_contact_info_cache = full_data.get('org_contact_info', {}) - logging.info("Successfully loaded downloaded data into cache.") - except (FileNotFoundError, json.JSONDecodeError) as e: - logging.error(f"Error reading downloaded data file: {e}. Starting with empty cache.") + visitor_data_cache = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): visitor_data_cache = {} - org_contact_info_cache = {} - return True - except RepositoryNotFoundError: - logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.") - except Exception as e: - logging.error(f"Error downloading data from Hugging Face: {e}") - return False + except Exception: + pass -def load_full_data(): - global visitor_data_cache, org_contact_info_cache - with _data_lock: - # Check cache first - if visitor_data_cache or org_contact_info_cache: - return {'users': visitor_data_cache, 'org_contact_info': org_contact_info_cache} - - try: - with open(DATA_FILE, 'r', encoding='utf-8') as f: - full_data = json.load(f) - visitor_data_cache = full_data.get('users', {}) - org_contact_info_cache = full_data.get('org_contact_info', {}) - logging.info("Full data loaded from local JSON.") - except FileNotFoundError: - logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.") - visitor_data_cache = {} - org_contact_info_cache = {} - except json.JSONDecodeError: - logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.") - visitor_data_cache = {} - org_contact_info_cache = {} - except Exception as e: - logging.error(f"Unexpected error loading full data: {e}") - visitor_data_cache = {} - org_contact_info_cache = {} - - return {'users': visitor_data_cache, 'org_contact_info': org_contact_info_cache} - - -def save_full_data(): + try: + hf_hub_download(repo_id=REPO_ID, filename=HF_ORG_INFO_FILE_PATH, repo_type="dataset", token=HF_TOKEN_READ, local_dir=".", local_dir_use_symlinks=False, force_download=True, etag_timeout=10) + with _org_info_lock: + try: + with open(ORG_INFO_FILE, 'r', encoding='utf-8') as f: + org_info_cache = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + org_info_cache = {} + except Exception: + pass + +def load_visitor_data(): + global visitor_data_cache with _data_lock: - try: - full_data_to_save = { - 'users': visitor_data_cache, - 'org_contact_info': org_contact_info_cache - } - with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump(full_data_to_save, f, ensure_ascii=False, indent=4) - logging.info(f"Full data successfully saved to {DATA_FILE}.") - upload_data_to_hf_async() - except Exception as e: - logging.error(f"Error saving full data: {e}") - + if not visitor_data_cache: + try: + with open(DATA_FILE, 'r', encoding='utf-8') as f: + visitor_data_cache = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + visitor_data_cache = {} + return visitor_data_cache def save_visitor_data(data): with _data_lock: visitor_data_cache.update(data) - save_full_data() + with open(DATA_FILE, 'w', encoding='utf-8') as f: + json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4) + upload_data_to_hf_async() + +def load_org_info(): + global org_info_cache + with _org_info_lock: + if not org_info_cache: + try: + with open(ORG_INFO_FILE, 'r', encoding='utf-8') as f: + org_info_cache = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + org_info_cache = { + "name": "Название вашей организации", + "phones": ["+996 (555) 123-456"], + "address": "г. Бишкек, ул. Примерная, 123", + "links": [{"label": "Наш сайт", "url": "https://example.com"}] + } + return org_info_cache -def save_org_contact_info(info): - global org_contact_info_cache - with _data_lock: - org_contact_info_cache = info - save_full_data() +def save_org_info(data): + global org_info_cache + with _org_info_lock: + org_info_cache = data + with open(ORG_INFO_FILE, 'w', encoding='utf-8') as f: + json.dump(org_info_cache, f, ensure_ascii=False, indent=4) + upload_data_to_hf_async(is_org_info=True) -def upload_data_to_hf(): +def upload_data_to_hf(is_org_info=False): if not HF_TOKEN_WRITE: - logging.warning("HF_TOKEN_WRITE not set. Skipping Hugging Face upload.") return - if not os.path.exists(DATA_FILE): - logging.warning(f"{DATA_FILE} does not exist. Skipping upload.") + + file_to_upload = ORG_INFO_FILE if is_org_info else DATA_FILE + path_in_repo = HF_ORG_INFO_FILE_PATH if is_org_info else HF_DATA_FILE_PATH + commit_msg = f"Update org info {datetime.now(BISHKEK_TZ).strftime('%Y-%m-%d %H:%M:%S')}" if is_org_info else f"Update bonus data {datetime.now(BISHKEK_TZ).strftime('%Y-%m-%d %H:%M:%S')}" + + if not os.path.exists(file_to_upload) or os.path.getsize(file_to_upload) == 0: return try: api = HfApi() - with _data_lock: - file_content_exists = os.path.getsize(DATA_FILE) > 0 - if not file_content_exists: - logging.warning(f"{DATA_FILE} is empty. Skipping upload.") - return - - logging.info(f"Attempting to upload {DATA_FILE} to {REPO_ID}/{HF_DATA_FILE_PATH}...") - api.upload_file( - path_or_fileobj=DATA_FILE, - path_in_repo=HF_DATA_FILE_PATH, - repo_id=REPO_ID, - repo_type="dataset", - token=HF_TOKEN_WRITE, - commit_message=f"Update bonus data {datetime.now(BISHKEK_TZ).strftime('%Y-%m-%d %H:%M:%S')}" - ) - logging.info("Bonus data successfully uploaded to Hugging Face.") + api.upload_file( + path_or_fileobj=file_to_upload, + path_in_repo=path_in_repo, + repo_id=REPO_ID, + repo_type="dataset", + token=HF_TOKEN_WRITE, + commit_message=commit_msg + ) except Exception as e: - logging.error(f"Error uploading data to Hugging Face: {e}") + # Silently fail for now + pass -def upload_data_to_hf_async(): - upload_thread = threading.Thread(target=upload_data_to_hf, daemon=True) +def upload_data_to_hf_async(is_org_info=False): + upload_thread = threading.Thread(target=upload_data_to_hf, args=(is_org_info,), daemon=True) upload_thread.start() def periodic_backup(): if not HF_TOKEN_WRITE: - logging.info("Periodic backup disabled: HF_TOKEN_WRITE not set.") return while True: time.sleep(3600) - logging.info("Initiating periodic backup...") - save_full_data() # Save locally and trigger HF upload + upload_data_to_hf(is_org_info=False) + time.sleep(5) + upload_data_to_hf(is_org_info=True) def verify_telegram_data(init_data_str): try: parsed_data = parse_qs(init_data_str) received_hash = parsed_data.pop('hash', [None])[0] - if not received_hash: return None, False - - data_check_list = [] - for key, value in sorted(parsed_data.items()): - data_check_list.append(f"{key}={value[0]}") - data_check_string = "\n".join(data_check_list) - + data_check_list = sorted([(k, v[0]) for k, v in parsed_data.items()]) + data_check_string = "\n".join([f"{k}={v}" for k, v in data_check_list]) 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 == received_hash: - auth_date = int(parsed_data.get('auth_date', [0])[0]) - current_time = int(time.time()) - if current_time - auth_date > 86400: - logging.warning(f"Telegram InitData is older than 24 hours (Auth Date: {auth_date}, Current: {current_time}).") - # Decide if you want to fail verification for old data - # return parsed_data, False # Uncomment to fail old data return parsed_data, True - else: - logging.warning(f"Data verification failed. Calculated: {calculated_hash}, Received: {received_hash}") - return parsed_data, False - except Exception as e: - logging.error(f"Error verifying Telegram data: {e}") + return parsed_data, False + except Exception: return None, False -TEMPLATE = """ +def clean_phone_number(phone_str): + return re.sub(r'\D', '', phone_str) + +# =========== CLIENT-SIDE TEMPLATES =========== + +MAIN_TEMPLATE = """
@@ -214,287 +178,33 @@ TEMPLATE = """Добро пожаловать!
-Ваши бонусы
@@ -514,20 +223,95 @@ TEMPLATE = """{{ "%.2f"|format(user.debts|float) }}
Ваш ID клиента
{{ user.id }}