diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -7,7 +7,7 @@ import hashlib import json from urllib.parse import unquote, parse_qs, quote import time -from datetime import datetime, timezone, timedelta +from datetime import datetime, timezone import logging import threading import random @@ -31,19 +31,15 @@ app.secret_key = os.urandom(24) _data_lock = threading.Lock() visitor_data_cache = {} -BISHKEK_TZ = timezone(timedelta(hours=6)) +KYRGYZSTAN_TIMEZONE = timezone(timedelta(hours=6)) # UTC+6 for Bishkek -DEFAULT_ORGANIZATION_DETAILS = { - "name": "Название вашей организации", - "phones": [], - "address": "Ваш адрес", - "links": [] -} +def get_current_time_kyrgyzstan(): + return datetime.now(KYRGYZSTAN_TIMEZONE) -def generate_unique_id(existing_ids_dict): +def generate_unique_id(all_data): while True: new_id = str(random.randint(10000, 99999)) - if new_id not in existing_ids_dict: + if new_id not in all_data: return new_id def download_data_from_hf(): @@ -67,14 +63,11 @@ def download_data_from_hf(): with _data_lock: try: with open(DATA_FILE, 'r', encoding='utf-8') as f: - loaded_data = json.load(f) - visitor_data_cache = loaded_data - if '_organization_details' not in visitor_data_cache: - visitor_data_cache['_organization_details'] = DEFAULT_ORGANIZATION_DETAILS.copy() + visitor_data_cache = json.load(f) logging.info("Successfully loaded downloaded data into cache.") except (FileNotFoundError, json.JSONDecodeError) as e: - logging.error(f"Error reading downloaded data file: {e}. Initializing cache.") - visitor_data_cache = {'_organization_details': DEFAULT_ORGANIZATION_DETAILS.copy()} + logging.error(f"Error reading downloaded data file: {e}. Starting with empty cache.") + visitor_data_cache = {} return True except RepositoryNotFoundError: logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.") @@ -89,32 +82,28 @@ def load_visitor_data(): try: with open(DATA_FILE, 'r', encoding='utf-8') as f: visitor_data_cache = json.load(f) - if '_organization_details' not in visitor_data_cache: - visitor_data_cache['_organization_details'] = DEFAULT_ORGANIZATION_DETAILS.copy() logging.info("Visitor data loaded from local JSON.") except FileNotFoundError: logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.") - visitor_data_cache = {'_organization_details': DEFAULT_ORGANIZATION_DETAILS.copy()} + visitor_data_cache = {} except json.JSONDecodeError: logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.") - visitor_data_cache = {'_organization_details': DEFAULT_ORGANIZATION_DETAILS.copy()} + visitor_data_cache = {} except Exception as e: logging.error(f"Unexpected error loading visitor data: {e}") - visitor_data_cache = {'_organization_details': DEFAULT_ORGANIZATION_DETAILS.copy()} + visitor_data_cache = {} return visitor_data_cache -def save_data_locally_and_upload_async(data_to_update=None): +def save_visitor_data(data): with _data_lock: - if data_to_update: - for key, value in data_to_update.items(): - visitor_data_cache[key] = value try: + visitor_data_cache.update(data) with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4) - logging.info(f"Data successfully saved to {DATA_FILE}.") + logging.info(f"Visitor data successfully saved to {DATA_FILE}.") upload_data_to_hf_async() except Exception as e: - logging.error(f"Error saving data: {e}") + logging.error(f"Error saving visitor data: {e}") def upload_data_to_hf(): if not HF_TOKEN_WRITE: @@ -139,7 +128,7 @@ def upload_data_to_hf(): 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')}" + commit_message=f"Update bonus data {get_current_time_kyrgyzstan().strftime('%Y-%m-%d %H:%M:%S')}" ) logging.info("Bonus data successfully uploaded to Hugging Face.") except Exception as e: @@ -177,7 +166,7 @@ def verify_telegram_data(init_data_str): 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: # 24 hours + if current_time - auth_date > 86400: logging.warning(f"Telegram InitData is older than 24 hours (Auth Date: {auth_date}, Current: {current_time}).") return parsed_data, True else: @@ -203,6 +192,7 @@ TEMPLATE = """ --brand-yellow: #FFC107; --brand-black: #101010; --brand-red: #F44336; + --brand-green: #4CAF50; --card-bg: #1c1c1e; --text-color: #ffffff; --text-secondary-color: #a0a0a0; @@ -227,63 +217,257 @@ TEMPLATE = """ visibility: hidden; min-height: 100vh; } - .container { max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--padding-m); } - .header { text-align: left; padding: var(--padding-m) 0; } - .logo { font-size: 2.5em; font-weight: 800; color: var(--text-color); letter-spacing: -1px; } + .container { + max-width: 600px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: var(--padding-m); + } + .header { + text-align: left; + padding: var(--padding-m) 0; + } + .logo { + font-size: 2.5em; + font-weight: 800; + color: var(--text-color); + letter-spacing: -1px; + } .logo span { color: var(--brand-yellow); } - .welcome-text { font-size: 1em; color: var(--text-secondary-color); margin-top: 4px; } - - .tabs { display: flex; gap: 8px; margin-bottom: var(--padding-m); border-bottom: 1px solid var(--card-bg); } - .tab-button { - padding: 10px 15px; background-color: transparent; color: var(--text-secondary-color); - border: none; border-bottom: 3px solid transparent; cursor: pointer; font-size: 1em; font-weight: 600; - transition: color 0.3s, border-color 0.3s; + .welcome-text { + font-size: 1em; + color: var(--text-secondary-color); + margin-top: 4px; + } + .card-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--padding-m); } - .tab-button.active { color: var(--brand-yellow); border-bottom-color: var(--brand-yellow); } - .tab-content { display: none; flex-direction: column; gap: var(--padding-m); } - .tab-content.active { display: flex; } - - .card-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--padding-m); } .bonus-card, .debt-card { background: linear-gradient(145deg, #2a2a2a, #1c1c1c); - border-radius: calc(var(--border-radius) + 8px); padding: var(--padding-l); text-align: center; - position: relative; overflow: hidden; - } - .bonus-card { box-shadow: var(--shadow-glow); border: 1px solid rgba(255, 193, 7, 0.2); } - .debt-card { box-shadow: var(--shadow-glow-red); border: 1px solid rgba(244, 67, 54, 0.2); } - .card-label { font-size: 1.1em; font-weight: 500; color: var(--text-secondary-color); margin-bottom: 12px; } - .bonus-amount, .debt-amount { font-size: 3em; font-weight: 800; letter-spacing: -2px; line-height: 1; } - .bonus-amount { color: var(--brand-yellow); } - .debt-amount { color: var(--brand-red); } - - .client-id-card { background-color: var(--card-bg); border-radius: var(--border-radius); padding: var(--padding-m); display: flex; justify-content: space-between; align-items: center; } - .client-id-label { font-weight: 500; color: var(--text-secondary-color); } - .client-id-value { font-size: 1.3em; font-weight: 700; color: var(--brand-yellow); letter-spacing: 2px; background-color: rgba(255,193,7,0.1); padding: 4px 10px; border-radius: 8px; } - - .section-card { background-color: var(--card-bg); border-radius: var(--border-radius); padding: var(--padding-l); } - .section-title { font-size: 1.4em; font-weight: 700; margin-bottom: var(--padding-m); padding-bottom: var(--padding-m); border-bottom: 1px solid rgba(255, 255, 255, 0.1); } - - .list { list-style: none; padding: 0; margin: 0; max-height: 40vh; overflow-y: auto; } - .list-item { display: flex; justify-content: space-between; align-items: center; padding: 14px 4px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); } - .list-item:last-child { border-bottom: none; } - .item-details { display: flex; flex-direction: column; } - .item-description { font-size: 1em; font-weight: 500; } - .item-date { font-size: 0.8em; color: var(--text-secondary-color); margin-top: 4px; } - .item-amount { font-size: 1.1em; font-weight: 700; } - .item-amount.accrual { color: #4CAF50; } - .item-amount.deduction { color: #F44336; } - .no-items { text-align: center; color: var(--text-secondary-color); padding: 2rem 0; } - - .visiting-card-item { margin-bottom: 12px; } - .visiting-card-item strong { color: var(--brand-yellow); display: block; margin-bottom: 4px; font-size: 0.9em; } - .visiting-card-item span, .visiting-card-item a { color: var(--text-color); text-decoration: none; font-size: 1.1em; } - .visiting-card-item a:hover { text-decoration: underline; } - .phone-actions button { - background-color: rgba(255, 193, 7, 0.2); color: var(--brand-yellow); - border: none; padding: 8px 12px; margin-right: 8px; margin-top: 4px; border-radius: 8px; cursor: pointer; - } - .invoice-item-details { background-color: rgba(255,255,255,0.05); padding: 10px; margin-top: 10px; border-radius: 8px;} - .invoice-item-product { display: flex; justify-content: space-between; font-size: 0.9em; margin-bottom: 5px; } + border-radius: calc(var(--border-radius) + 8px); + padding: var(--padding-l); + text-align: center; + position: relative; + overflow: hidden; + } + .bonus-card { + box-shadow: var(--shadow-glow); + border: 1px solid rgba(255, 193, 7, 0.2); + } + .debt-card { + box-shadow: var(--shadow-glow-red); + border: 1px solid rgba(244, 67, 54, 0.2); + } + .card-label { + font-size: 1.1em; + font-weight: 500; + color: var(--text-secondary-color); + margin-bottom: 12px; + } + .bonus-amount { + font-size: 3em; + font-weight: 800; + color: var(--brand-yellow); + letter-spacing: -2px; + line-height: 1; + } + .debt-amount { + font-size: 3em; + font-weight: 800; + color: var(--brand-red); + letter-spacing: -2px; + line-height: 1; + } + .client-id-card { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: var(--padding-m); + display: flex; + justify-content: space-between; + align-items: center; + } + .client-id-label { + font-weight: 500; + color: var(--text-secondary-color); + } + .client-id-value { + font-size: 1.3em; + font-weight: 700; + color: var(--brand-yellow); + letter-spacing: 2px; + background-color: rgba(255,193,7,0.1); + padding: 4px 10px; + border-radius: 8px; + } + .navigation-buttons { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--padding-m); + margin-top: var(--padding-m); + } + .nav-button { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: var(--padding-m); + text-align: center; + cursor: pointer; + transition: background-color 0.2s ease, transform 0.2s ease; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 80px; + border: 1px solid rgba(255, 255, 255, 0.05); + } + .nav-button:hover { + background-color: #2a2a2c; + transform: translateY(-3px); + } + .nav-button-icon { + font-size: 1.8em; + margin-bottom: 8px; + color: var(--brand-yellow); + } + .nav-button-text { + font-size: 0.9em; + font-weight: 500; + color: var(--text-secondary-color); + } + .history-section { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: var(--padding-l); + margin-top: var(--padding-m); + } + .history-title { + font-size: 1.4em; + font-weight: 700; + margin-bottom: var(--padding-m); + padding-bottom: var(--padding-m); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + .history-list { + list-style: none; + padding: 0; + margin: 0; + max-height: 35vh; + overflow-y: auto; + } + .history-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 4px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + .history-item:last-child { border-bottom: none; } + .history-details { display: flex; flex-direction: column; } + .history-description { font-size: 1em; font-weight: 500; } + .history-date { font-size: 0.8em; color: var(--text-secondary-color); margin-top: 4px; } + .history-amount { font-size: 1.1em; font-weight: 700; } + .history-amount.accrual { color: var(--brand-green); } + .history-amount.deduction { color: var(--brand-red); } + .no-history { + text-align: center; + color: var(--text-secondary-color); + padding: 2rem 0; + } + .modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.5); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + padding: 20px; + box-sizing: border-box; + } + .modal-content { + background-color: var(--card-bg); + margin: 20px auto; + padding: var(--padding-l); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: var(--border-radius); + width: 100%; + max-width: 700px; + box-shadow: 0 0 30px rgba(255, 193, 7, 0.1); + } + .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--padding-m); + padding-bottom: var(--padding-m); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + .modal-header h2 { + font-size: 1.5em; + font-weight: 700; + color: var(--brand-yellow); + } + .modal-close { + font-size: 2em; + cursor: pointer; + color: var(--text-secondary-color); + line-height: 1; + } + .modal-close:hover { + color: var(--text-color); + } + .invoice-section { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: var(--padding-m); + margin-bottom: var(--padding-m); + border: 1px solid rgba(255, 255, 255, 0.05); + } + .invoice-title { + font-size: 1.3em; + font-weight: 700; + margin-bottom: var(--padding-m); + color: var(--brand-yellow); + display: flex; + justify-content: space-between; + align-items: center; + } + .invoice-date { + font-size: 0.9em; + color: var(--text-secondary-color); + } + .invoice-item { + display: flex; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px dashed rgba(255, 255, 255, 0.1); + font-size: 0.95em; + } + .invoice-item:last-child { + border-bottom: none; + } + .invoice-item .name { flex-grow: 1; margin-right: 10px; } + .invoice-item .quantity { min-width: 50px; text-align: right; margin-right: 10px; } + .invoice-item .price { min-width: 80px; text-align: right; } + .invoice-total { + text-align: right; + font-size: 1.2em; + font-weight: 700; + margin-top: var(--padding-m); + padding-top: var(--padding-m); + border-top: 1px solid rgba(255, 255, 255, 0.1); + } + .no-invoices { + text-align: center; + color: var(--text-secondary-color); + padding: 2rem 0; + }
@@ -293,127 +477,157 @@ TEMPLATE = """Добро пожаловать!
-Ваши бонусы
+{{ "%.2f"|format(user.bonuses|float) }}
+Ваш долг
+{{ "%.2f"|format(user.debts|float) }}
+Ваш ID клиента
+{{ user.id }}
+Операций пока не было.
+ {% endif %} +Ваши бонусы
-{{ "%.2f"|format(user.bonuses|float) }}
-Ваш долг
-{{ "%.2f"|format(user.debts|float) }}
-Ваш ID клиента
-{{ user.id }}
-Операций пока не было.
+ +Операций пока не было.
+ {% endif %}