Aleksmorshen commited on
Commit
e8d754e
·
verified ·
1 Parent(s): e39e00e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +290 -225
app.py CHANGED
@@ -14,7 +14,7 @@ import threading
14
  from huggingface_hub import HfApi, hf_hub_download
15
  from huggingface_hub.utils import RepositoryNotFoundError
16
 
17
- BOT_TOKEN = os.getenv("BOT_TOKEN", "7566834146:AAGiG4MaTZZvvtTVsqEJVG5SYK5hUlc_Ewo")
18
  HOST = '0.0.0.0'
19
  PORT = 7860
20
  DATA_FILE = 'data.json'
@@ -37,6 +37,7 @@ def download_data_from_hf():
37
  logging.warning("HF_TOKEN_READ not set.")
38
  return False
39
  try:
 
40
  hf_hub_download(
41
  repo_id=REPO_ID,
42
  filename=HF_DATA_FILE_PATH,
@@ -47,15 +48,18 @@ def download_data_from_hf():
47
  force_download=True,
48
  etag_timeout=10
49
  )
 
50
  with _data_lock:
51
  try:
52
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
53
  visitor_data_cache = json.load(f)
 
54
  except (FileNotFoundError, json.JSONDecodeError) as e:
 
55
  visitor_data_cache = {}
56
  return True
57
  except RepositoryNotFoundError:
58
- logging.error(f"Hugging Face repository '{REPO_ID}' not found.")
59
  except Exception as e:
60
  logging.error(f"Error downloading data from Hugging Face: {e}")
61
  return False
@@ -67,11 +71,15 @@ def load_visitor_data():
67
  try:
68
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
69
  visitor_data_cache = json.load(f)
 
70
  except FileNotFoundError:
 
71
  visitor_data_cache = {}
72
  except json.JSONDecodeError:
 
73
  visitor_data_cache = {}
74
  except Exception as e:
 
75
  visitor_data_cache = {}
76
  return visitor_data_cache
77
 
@@ -81,14 +89,17 @@ def save_visitor_data(data):
81
  visitor_data_cache.update(data)
82
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
83
  json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4)
 
84
  upload_data_to_hf_async()
85
  except Exception as e:
86
  logging.error(f"Error saving visitor data: {e}")
87
 
88
  def upload_data_to_hf():
89
  if not HF_TOKEN_WRITE:
 
90
  return
91
  if not os.path.exists(DATA_FILE):
 
92
  return
93
 
94
  try:
@@ -96,8 +107,10 @@ def upload_data_to_hf():
96
  with _data_lock:
97
  file_content_exists = os.path.getsize(DATA_FILE) > 0
98
  if not file_content_exists:
 
99
  return
100
 
 
101
  api.upload_file(
102
  path_or_fileobj=DATA_FILE,
103
  path_in_repo=HF_DATA_FILE_PATH,
@@ -106,6 +119,7 @@ def upload_data_to_hf():
106
  token=HF_TOKEN_WRITE,
107
  commit_message=f"Update visitor data {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
108
  )
 
109
  except Exception as e:
110
  logging.error(f"Error uploading data to Hugging Face: {e}")
111
 
@@ -115,9 +129,11 @@ def upload_data_to_hf_async():
115
 
116
  def periodic_backup():
117
  if not HF_TOKEN_WRITE:
 
118
  return
119
  while True:
120
  time.sleep(3600)
 
121
  upload_data_to_hf()
122
 
123
  def verify_telegram_data(init_data_str):
@@ -140,7 +156,7 @@ def verify_telegram_data(init_data_str):
140
  auth_date = int(parsed_data.get('auth_date', [0])[0])
141
  current_time = int(time.time())
142
  if current_time - auth_date > 86400:
143
- pass
144
  return parsed_data, True
145
  else:
146
  logging.warning(f"Data verification failed. Calculated: {calculated_hash}, Received: {received_hash}")
@@ -156,11 +172,8 @@ TEMPLATE = """
156
  <meta charset="UTF-8">
157
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no, viewport-fit=cover">
158
  <title>Morshen Group</title>
159
- <!-- Telegram WebApp script MUST be loaded first -->
160
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
161
- <!-- TON Connect SDK script with defer to avoid blocking -->
162
- <script src="https://unpkg.com/@tonconnect/[email protected]/dist/tonconnect-sdk.min.js" defer></script>
163
-
164
  <link rel="preconnect" href="https://fonts.googleapis.com">
165
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
166
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
@@ -261,14 +274,6 @@ TEMPLATE = """
261
  background: rgba(44, 44, 46, 0.95);
262
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), var(--shadow-medium);
263
  }
264
- .btn-red {
265
- background: linear-gradient(95deg, #ff3b30, #ff453a);
266
- color: var(--tg-theme-button-text-color);
267
- }
268
- .btn-red:hover {
269
- background: linear-gradient(95deg, #ff453a, #ff3b30);
270
- }
271
-
272
  .tag {
273
  display: inline-block; background: var(--tag-bg); color: var(--text-secondary-color);
274
  padding: 6px 12px; border-radius: var(--border-radius-s); font-size: 0.85em;
@@ -392,6 +397,37 @@ TEMPLATE = """
392
  .modal-text b { color: var(--tg-theme-link-color); font-weight: 600; }
393
  .modal-instruction { font-size: 1em; color: var(--text-secondary-color); margin-top: var(--padding-m); }
394
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  .icon { display: inline-block; width: 1.2em; text-align: center; margin-right: 8px; opacity: 0.9; }
396
  .icon-save::before { content: '💾'; }
397
  .icon-web::before { content: '🌐'; }
@@ -413,7 +449,6 @@ TEMPLATE = """
413
  .icon-leader::before { content: '🏆'; }
414
  .icon-company::before { content: '🏢'; }
415
  .icon-ton::before { content: '💎'; }
416
- .icon-wallet::before { content: '👛'; }
417
 
418
  @media (max-width: 480px) {
419
  .section-title { font-size: 1.8em; }
@@ -423,6 +458,8 @@ TEMPLATE = """
423
  .stats-grid { grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: var(--padding-s); }
424
  .stat-value { font-size: 1.5em; }
425
  .modal-content { margin: 25% auto; width: 92%; }
 
 
426
  }
427
  </style>
428
  </head>
@@ -450,24 +487,6 @@ TEMPLATE = """
450
  </a>
451
  </section>
452
 
453
- <section class="ton-wallet-section section-card">
454
- <h2 class="section-title"><i class="icon icon-ton"></i>TON Кошелек</h2>
455
- <p class="description" id="ton-wallet-status">Статус: Не подключен</p>
456
- <div id="ton-info" style="display: none;">
457
- <p class="modal-text" style="font-size: 1.1em; margin-bottom: var(--padding-s);">
458
- <b>Адрес:</b> <span id="ton-wallet-address" style="word-break: break-all;"></span>
459
- </p>
460
- <p class="modal-text" style="font-size: 1.1em;">
461
- <b>Баланс:</b> <span id="ton-balance"></span> TON
462
- </p>
463
- </div>
464
- <div style="display: flex; gap: var(--padding-s); margin-top: var(--padding-m);">
465
- <button id="connect-ton-btn" class="btn" style="flex-grow: 1;"><i class="icon icon-wallet"></i>Подключить кошелек</button>
466
- <button id="disconnect-ton-btn" class="btn btn-red" style="flex-grow: 1; display: none;">Отключить</button>
467
- </div>
468
- <p id="ton-error" style="color: var(--admin-danger); font-size: 0.9em; margin-top: var(--padding-s); display: none;"></p>
469
- </section>
470
-
471
  <section class="ecosystem-header">
472
  <h2 class="section-title"><i class="icon icon-company"></i>Экосистема инноваций</h2>
473
  <p class="description">
@@ -552,6 +571,18 @@ TEMPLATE = """
552
  </div>
553
  </section>
554
 
 
 
 
 
 
 
 
 
 
 
 
 
555
  <footer class="footer-greeting">
556
  <p id="greeting">Инициализация...</p>
557
  </footer>
@@ -572,10 +603,8 @@ TEMPLATE = """
572
  </div>
573
  </div>
574
 
575
-
576
  <script>
577
  const tg = window.Telegram.WebApp;
578
- let tonConnectSDK = null;
579
 
580
  function applyTheme(themeParams) {
581
  const root = document.documentElement;
@@ -598,58 +627,44 @@ TEMPLATE = """
598
  }
599
  }
600
 
601
- async function fetchTonBalance(address) {
602
- // Using public RPC endpoint provided by TON Center
603
- const rpcUrl = 'https://rpc.toncenter.com/api/v2/jsonRPC';
604
- try {
605
- const response = await fetch(rpcUrl, {
606
- method: 'POST',
607
- headers: {
608
- 'Content-Type': 'application/json',
609
- },
610
- body: JSON.stringify({
611
- jsonrpc: '2.0',
612
- id: 1, // Request ID, can be anything
613
- method: 'getBalance',
614
- params: {
615
- address: address
616
- }
617
- })
618
- });
619
- if (!response.ok) throw new Error(`RPC error: ${response.status}`);
620
- const data = await response.json();
621
- if (data.error) throw new Error(`RPC error: ${data.error.message}`);
622
- const balanceNanoTon = parseInt(data.result);
623
- const balanceTon = balanceNanoTon / 1e9; // Convert NanoTON to TON
624
- return balanceTon.toFixed(4); // Format to 4 decimal places
625
- } catch (error) {
626
- console.error('Error fetching TON balance:', error);
627
- document.getElementById('ton-error').textContent = `Ошибка получения баланса: ${error.message}`;
628
- document.getElementById('ton-error').style.display = 'block';
629
- return 'N/A'; // Return N/A on error
630
- }
631
- }
632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
 
634
- function initializeWebApp() {
635
- // Check if Telegram WebApp is ready
636
  if (!tg || !tg.initData) {
637
  console.error("Telegram WebApp script not loaded or initData is missing.");
638
  const greetingElement = document.getElementById('greeting');
639
  if(greetingElement) greetingElement.textContent = 'Не удалось связаться с Telegram.';
640
- document.body.style.visibility = 'visible'; // Show body even on error
641
  return;
642
  }
643
 
644
- // Initialize Telegram WebApp features
645
  tg.ready();
646
  tg.expand();
647
 
648
- // Apply theme based on Telegram app theme
649
  applyTheme(tg.themeParams);
650
  tg.onEvent('themeChanged', () => applyTheme(tg.themeParams));
651
 
652
- // Send initData to backend for verification and logging
653
  fetch('/verify', {
654
  method: 'POST',
655
  headers: {
@@ -673,7 +688,7 @@ TEMPLATE = """
673
  console.error('Error sending initData for verification:', error);
674
  });
675
 
676
- // Display user greeting
677
  const user = tg.initDataUnsafe?.user;
678
  const greetingElement = document.getElementById('greeting');
679
  if (user) {
@@ -681,19 +696,17 @@ TEMPLATE = """
681
  greetingElement.textContent = `Добро пожаловать, ${name}! 👋`;
682
  } else {
683
  greetingElement.textContent = 'Добро пожаловать!';
 
684
  }
685
 
686
- // Setup Contact Links to open Telegram chat
687
  const contactButtons = document.querySelectorAll('.contact-link');
688
  contactButtons.forEach(button => {
689
  button.addEventListener('click', (e) => {
690
  e.preventDefault();
691
- // Replace 'morshenkhan' with the actual Telegram username
692
  tg.openTelegramLink('https://t.me/morshenkhan');
693
  });
694
  });
695
 
696
- // Setup Save Card Modal
697
  const modal = document.getElementById("saveModal");
698
  const saveCardBtn = document.getElementById("save-card-btn");
699
  const closeBtn = document.getElementById("modal-close-btn");
@@ -716,113 +729,107 @@ TEMPLATE = """
716
  modal.style.display = "none";
717
  }
718
  });
 
 
719
  }
720
 
721
- // Show body after initial setup is complete
722
- document.body.style.visibility = 'visible';
723
-
724
-
725
- // --- TON Connect Integration ---
726
- const connectTonBtn = document.getElementById('connect-ton-btn');
727
- const disconnectTonBtn = document.getElementById('disconnect-ton-btn');
728
- const tonWalletStatus = document.getElementById('ton-wallet-status');
729
- const tonWalletAddress = document.getElementById('ton-wallet-address');
730
- const tonBalance = document.getElementById('ton-balance');
731
- const tonInfoDiv = document.getElementById('ton-info');
732
- const tonErrorDiv = document.getElementById('ton-error');
733
-
734
- // Check if TON Connect elements exist and TON Connect SDK is loaded
735
- if (connectTonBtn && disconnectTonBtn && tonWalletStatus && tonWalletAddress && tonBalance && tonInfoDiv && tonErrorDiv) {
736
- if (!window.TonConnectSDK) {
737
- console.error("TON Connect SDK script not loaded correctly or window.TonConnectSDK is not defined.");
738
- tonErrorDiv.textContent = 'Ошибка: TON Connect SDK не загружен.';
739
- tonErrorDiv.style.display = 'block';
740
- connectTonBtn.disabled = true; // Disable connect button
741
- return; // Stop TON Connect initialization
742
- }
743
-
744
- try {
745
- // Initialize TON Connect SDK
746
- tonConnectSDK = new window.TonConnectSDK.TonConnectSDK({
747
- manifestUrl: window.location.origin + '/tonconnect-manifest.json',
748
- actionsConfiguration: {
749
- // !!! IMPORTANT: Replace with your actual bot username and Mini App shortname
750
- // Example: 'https://t.me/MyAwesomeBot/MyMiniApp'
751
- twaReturnUrl: 'https://t.me/YOUR_BOT_USERNAME/YOUR_MINIAPP_SHORTNAME'
752
- }
753
- });
754
 
755
- // Subscribe to wallet status changes
756
- tonConnectSDK.onStatusChange(async wallet => {
757
- tonErrorDiv.style.display = 'none'; // Clear previous errors
758
- if (wallet) {
759
- // Wallet is connected
760
- tonWalletStatus.textContent = 'Статус: Подключен ✅';
761
- tonWalletAddress.textContent = wallet.account.address;
762
- tonInfoDiv.style.display = 'block';
763
- connectTonBtn.style.display = 'none';
764
- disconnectTonBtn.style.display = 'inline-flex';
765
- tonBalance.textContent = 'Загрузка...'; // Show loading state for balance
766
- const balance = await fetchTonBalance(wallet.account.address);
767
- tonBalance.textContent = balance;
768
- if (tg.HapticFeedback) {
769
- tg.HapticFeedback.notificationOccurred('success');
770
- }
771
- } else {
772
- // Wallet is disconnected
773
- tonWalletStatus.textContent = 'Статус: Не подключен';
774
- tonWalletAddress.textContent = '';
775
- tonBalance.textContent = '';
776
- tonInfoDiv.style.display = 'none';
777
- connectTonBtn.style.display = 'inline-flex';
778
- disconnectTonBtn.style.display = 'none';
779
- if (tg.HapticFeedback) {
780
- tg.HapticFeedback.notificationOccurred('warning');
781
- }
782
- }
783
- // Hide Telegram's MainButton if it might be interfering
784
- // tg.MainButton.hide();
785
- });
786
 
787
- // Add event listeners for connect/disconnect buttons
788
- connectTonBtn.addEventListener('click', () => {
789
- tonErrorDiv.style.display = 'none'; // Clear error on button click
790
- tonConnectSDK.connectWallet(); // Trigger wallet connection flow
791
- });
 
792
 
793
- disconnectTonBtn.addEventListener('click', () => {
794
- tonErrorDiv.style.display = 'none'; // Clear error on button click
795
- tonConnectSDK.disconnect(); // Trigger wallet disconnection
796
- });
 
 
 
 
 
 
 
 
797
 
798
- // Attempt to restore previous connection on startup
799
- tonConnectSDK.restoreConnection();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
 
801
- } catch (e) {
802
- // Catch errors specifically during SDK instantiation
803
- console.error("Error initializing TON Connect SDK:", e);
804
- tonErrorDiv.textContent = `Ошибка инициализации TON Connect: ${e.message}`;
805
- tonErrorDiv.style.display = 'block';
806
- connectTonBtn.disabled = true; // Disable button on initialization error
 
 
 
 
 
 
 
 
 
 
 
807
  }
808
- } else {
809
- console.error("TON Wallet section HTML elements not found!");
 
 
810
  }
811
- // --- End TON Connect Integration ---
812
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
 
814
- // Wait for the window to fully load all resources (including deferred scripts)
815
- window.addEventListener('load', initializeWebApp);
816
-
817
- // Fallback timeout in case something prevents 'load' or Telegram script
818
- setTimeout(() => {
819
- if (document.body.style.visibility !== 'visible') {
820
- console.error("WebApp initialization fallback timeout triggered.");
821
- const greetingElement = document.getElementById('greeting');
822
- if(greetingElement) greetingElement.textContent = 'Ошибка загрузки интерфейса.';
823
- document.body.style.visibility = 'visible';
824
- }
825
- }, 5000); // Increased timeout slightly to 5 seconds
826
  </script>
827
  </body>
828
  </html>
@@ -894,6 +901,7 @@ ADMIN_TEMPLATE = """
894
  }
895
  .user-card .name { font-weight: 600; font-size: 1.2em; margin-bottom: 0.3rem; color: var(--admin-primary); }
896
  .user-card .username { color: var(--admin-secondary); margin-bottom: 0.8rem; font-size: 0.95em; }
 
897
  .user-card .details { font-size: 0.9em; color: #495057; word-break: break-word; }
898
  .user-card .detail-item { margin-bottom: 0.3rem; }
899
  .user-card .detail-item strong { color: var(--admin-text); }
@@ -975,7 +983,7 @@ ADMIN_TEMPLATE = """
975
  <img src="{{ user.photo_url if user.photo_url else 'data:image/svg+xml;charset=UTF-8,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 100 100%27%3e%3crect width=%27100%27 height=%27100%27 fill=%27%23e9ecef%27/%3e%3ctext x=%2750%25%27 y=%2755%25%27 dominant-baseline=%27middle%27 text-anchor=%27middle%27 font-size=%2745%27 font-family=%27sans-serif%27 fill=%27%23adb5bd%27%3e?%3c/text%3e%3c/svg%3e' }}" alt="User Avatar">
976
  <div class="name">{{ user.first_name or '' }} {{ user.last_name or '' }}</div>
977
  {% if user.username %}
978
- <div class="username"><a href="https://t.me/{{ user.username }}" target="_blank" style="color: inherit; text-decoration: none;">@{{ user.username }}</a></div>
979
  {% else %}
980
  <div class="username" style="height: 1.3em;"></div>
981
  {% endif %}
@@ -984,6 +992,9 @@ ADMIN_TEMPLATE = """
984
  <div class="detail-item"><strong>Язык:</strong> {{ user.language_code or 'N/A' }}</div>
985
  <div class="detail-item"><strong>Premium:</strong> {{ 'Да' if user.is_premium else 'Нет' }}</div>
986
  <div class="detail-item"><strong>Телефон:</strong> {{ user.phone_number or 'Недоступен' }}</div>
 
 
 
987
  </div>
988
  <div class="timestamp">Визит: {{ user.visited_at_str }}</div>
989
  </div>
@@ -1017,6 +1028,7 @@ ADMIN_TEMPLATE = """
1017
  } catch (error) {
1018
  statusMessage.textContent = `Ошибка ${action}: ${error.message}`;
1019
  statusMessage.style.color = 'var(--admin-danger)';
 
1020
  } finally {
1021
  loader.style.display = 'none';
1022
  }
@@ -1034,6 +1046,14 @@ ADMIN_TEMPLATE = """
1034
  </html>
1035
  """
1036
 
 
 
 
 
 
 
 
 
1037
  @app.route('/')
1038
  def index():
1039
  theme_params = {}
@@ -1050,56 +1070,55 @@ def verify_data():
1050
  user_data_parsed, is_valid = verify_telegram_data(init_data_str)
1051
 
1052
  user_info_dict = {}
 
1053
  if user_data_parsed and 'user' in user_data_parsed:
1054
  try:
1055
  user_json_str = unquote(user_data_parsed['user'][0])
1056
  user_info_dict = json.loads(user_json_str)
 
1057
  except Exception as e:
1058
  logging.error(f"Could not parse user JSON: {e}")
1059
  user_info_dict = {}
1060
 
1061
- if is_valid:
1062
- user_id = user_info_dict.get('id')
1063
- if user_id:
1064
- now = time.time()
1065
- user_entry = {
1066
- str(user_id): {
1067
- 'id': user_id,
1068
- 'first_name': user_info_dict.get('first_name'),
1069
- 'last_name': user_info_dict.get('last_name'),
1070
- 'username': user_info_dict.get('username'),
1071
- 'photo_url': user_info_dict.get('photo_url'),
1072
- 'language_code': user_info_dict.get('language_code'),
1073
- 'is_premium': user_info_dict.get('is_premium', False),
1074
- 'phone_number': user_info_dict.get('phone_number'),
1075
- 'visited_at': now,
1076
- 'visited_at_str': datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
1077
- }
1078
- }
1079
- save_visitor_data(user_entry)
 
 
 
 
 
 
 
 
 
 
 
1080
  return jsonify({"status": "ok", "verified": True, "user": user_info_dict}), 200
1081
  else:
1082
- logging.warning(f"Verification failed for user: {user_info_dict.get('id')}")
1083
- return jsonify({"status": "error", "verified": False, "message": "Invalid data"}), 403
1084
 
1085
  except Exception as e:
1086
  logging.exception("Error in /verify endpoint")
1087
  return jsonify({"status": "error", "message": "Internal server error"}), 500
1088
 
1089
- @app.route('/tonconnect-manifest.json')
1090
- def tonconnect_manifest():
1091
- # You need to serve your icon file, e.g., place morshengroup.png in a 'static' folder
1092
- icon_url = request.url_root.rstrip('/') + "/static/morshengroup.png"
1093
- manifest = {
1094
- "url": request.url_root.rstrip('/') + "/tonconnect-manifest.json",
1095
- "name": "Morshen Group Mini App",
1096
- "iconUrl": icon_url,
1097
- # !!! IMPORTANT: Replace with actual URLs for your terms of use and privacy policy
1098
- "termsOfUseUrl": "https://example.com/terms",
1099
- "privacyPolicyUrl": "https://example.com/privacy"
1100
- }
1101
- return jsonify(manifest)
1102
-
1103
  @app.route('/admin')
1104
  def admin_panel():
1105
  current_data = load_visitor_data()
@@ -1121,30 +1140,76 @@ def admin_trigger_upload():
1121
  upload_data_to_hf_async()
1122
  return jsonify({"status": "ok", "message": "Загрузка данных на Hugging Face запущена в фоновом режиме."})
1123
 
1124
- @app.route('/static/<path:filename>')
1125
- def static_files(filename):
1126
- # Assuming you place your icon file (e.g., morshengroup.png) in a 'static' folder
1127
- # relative to your app.py file.
1128
- # For the manifest, a PNG is usually required, but the original code used JPG.
1129
- # Let's assume you have a morshengroup.png in a static folder.
1130
- static_dir = os.path.join(app.root_path, 'static')
1131
- return send_from_directory(static_dir, filename)
 
 
 
 
 
 
 
 
1132
 
1133
- # Import send_from_directory for the static route
1134
- from flask import send_from_directory
 
 
 
 
 
 
 
 
 
 
 
 
1135
 
1136
 
1137
  if __name__ == '__main__':
1138
- # Ensure the static directory exists if you're using it for icons
1139
- if not os.path.exists('static'):
1140
- os.makedirs('static')
1141
- # You should place your morshengroup.png file inside the new 'static' folder
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1142
 
1143
- download_data_from_hf()
1144
  load_visitor_data()
1145
 
 
 
 
 
 
 
1146
  if HF_TOKEN_WRITE:
1147
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
1148
  backup_thread.start()
 
 
 
1149
 
 
1150
  app.run(host=HOST, port=PORT, debug=False)
 
14
  from huggingface_hub import HfApi, hf_hub_download
15
  from huggingface_hub.utils import RepositoryNotFoundError
16
 
17
+ BOT_TOKEN = os.getenv("BOT_TOKEN", "7566834146:AAGiG4MaTZZvvtVsqEJVG5SYK5hUlc_Ewo")
18
  HOST = '0.0.0.0'
19
  PORT = 7860
20
  DATA_FILE = 'data.json'
 
37
  logging.warning("HF_TOKEN_READ not set.")
38
  return False
39
  try:
40
+ logging.info(f"Attempting to download {HF_DATA_FILE_PATH} from {REPO_ID}...")
41
  hf_hub_download(
42
  repo_id=REPO_ID,
43
  filename=HF_DATA_FILE_PATH,
 
48
  force_download=True,
49
  etag_timeout=10
50
  )
51
+ logging.info("Data file successfully downloaded from Hugging Face.")
52
  with _data_lock:
53
  try:
54
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
55
  visitor_data_cache = json.load(f)
56
+ logging.info("Successfully loaded downloaded data into cache.")
57
  except (FileNotFoundError, json.JSONDecodeError) as e:
58
+ logging.error(f"Error reading downloaded data file: {e}. Starting with empty cache.")
59
  visitor_data_cache = {}
60
  return True
61
  except RepositoryNotFoundError:
62
+ logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.")
63
  except Exception as e:
64
  logging.error(f"Error downloading data from Hugging Face: {e}")
65
  return False
 
71
  try:
72
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
73
  visitor_data_cache = json.load(f)
74
+ logging.info("Visitor data loaded from local JSON.")
75
  except FileNotFoundError:
76
+ logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.")
77
  visitor_data_cache = {}
78
  except json.JSONDecodeError:
79
+ logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.")
80
  visitor_data_cache = {}
81
  except Exception as e:
82
+ logging.error(f"Unexpected error loading visitor data: {e}")
83
  visitor_data_cache = {}
84
  return visitor_data_cache
85
 
 
89
  visitor_data_cache.update(data)
90
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
91
  json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4)
92
+ logging.info(f"Visitor data successfully saved to {DATA_FILE}.")
93
  upload_data_to_hf_async()
94
  except Exception as e:
95
  logging.error(f"Error saving visitor data: {e}")
96
 
97
  def upload_data_to_hf():
98
  if not HF_TOKEN_WRITE:
99
+ logging.warning("HF_TOKEN_WRITE not set. Skipping Hugging Face upload.")
100
  return
101
  if not os.path.exists(DATA_FILE):
102
+ logging.warning(f"{DATA_FILE} does not exist. Skipping upload.")
103
  return
104
 
105
  try:
 
107
  with _data_lock:
108
  file_content_exists = os.path.getsize(DATA_FILE) > 0
109
  if not file_content_exists:
110
+ logging.warning(f"{DATA_FILE} is empty. Skipping upload.")
111
  return
112
 
113
+ logging.info(f"Attempting to upload {DATA_FILE} to {REPO_ID}/{HF_DATA_FILE_PATH}...")
114
  api.upload_file(
115
  path_or_fileobj=DATA_FILE,
116
  path_in_repo=HF_DATA_FILE_PATH,
 
119
  token=HF_TOKEN_WRITE,
120
  commit_message=f"Update visitor data {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
121
  )
122
+ logging.info("Visitor data successfully uploaded to Hugging Face.")
123
  except Exception as e:
124
  logging.error(f"Error uploading data to Hugging Face: {e}")
125
 
 
129
 
130
  def periodic_backup():
131
  if not HF_TOKEN_WRITE:
132
+ logging.info("Periodic backup disabled: HF_TOKEN_WRITE not set.")
133
  return
134
  while True:
135
  time.sleep(3600)
136
+ logging.info("Initiating periodic backup...")
137
  upload_data_to_hf()
138
 
139
  def verify_telegram_data(init_data_str):
 
156
  auth_date = int(parsed_data.get('auth_date', [0])[0])
157
  current_time = int(time.time())
158
  if current_time - auth_date > 86400:
159
+ logging.warning(f"Telegram InitData is older than 24 hours (Auth Date: {auth_date}, Current: {current_time}).")
160
  return parsed_data, True
161
  else:
162
  logging.warning(f"Data verification failed. Calculated: {calculated_hash}, Received: {received_hash}")
 
172
  <meta charset="UTF-8">
173
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no, viewport-fit=cover">
174
  <title>Morshen Group</title>
 
175
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
176
+ <script src="https://unpkg.com/@tonconnect/ui@latest/dist/tonconnect-ui.min.js"></script>
 
 
177
  <link rel="preconnect" href="https://fonts.googleapis.com">
178
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
179
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
274
  background: rgba(44, 44, 46, 0.95);
275
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), var(--shadow-medium);
276
  }
 
 
 
 
 
 
 
 
277
  .tag {
278
  display: inline-block; background: var(--tag-bg); color: var(--text-secondary-color);
279
  padding: 6px 12px; border-radius: var(--border-radius-s); font-size: 0.85em;
 
397
  .modal-text b { color: var(--tg-theme-link-color); font-weight: 600; }
398
  .modal-instruction { font-size: 1em; color: var(--text-secondary-color); margin-top: var(--padding-m); }
399
 
400
+ .ton-section {
401
+ margin-top: var(--padding-l);
402
+ text-align: center;
403
+ padding: var(--padding-l);
404
+ border-radius: var(--border-radius-l);
405
+ background-color: var(--card-bg);
406
+ box-shadow: var(--shadow-medium);
407
+ border: 1px solid rgba(255, 255, 255, 0.08);
408
+ backdrop-filter: blur(var(--backdrop-blur));
409
+ -webkit-backdrop-filter: blur(var(--backdrop-blur));
410
+ }
411
+ .ton-section h3 {
412
+ font-size: 1.5em;
413
+ font-weight: 700;
414
+ margin-bottom: var(--padding-s);
415
+ color: var(--text-color);
416
+ }
417
+ .ton-connect-widget-container {
418
+ margin-top: var(--padding-m);
419
+ }
420
+ .ton-info {
421
+ margin-top: var(--padding-m);
422
+ font-size: 1.1em;
423
+ color: var(--text-secondary-color);
424
+ }
425
+ .ton-info div { margin-bottom: var(--padding-s); word-break: break-all; }
426
+ .ton-info strong { color: var(--text-color); font-weight: 600; }
427
+ .ton-address { color: var(--tg-theme-link-color); font-weight: 500;}
428
+ .ton-balance { color: var(--accent-gradient-green, #34c759); font-weight: 600; }
429
+
430
+
431
  .icon { display: inline-block; width: 1.2em; text-align: center; margin-right: 8px; opacity: 0.9; }
432
  .icon-save::before { content: '💾'; }
433
  .icon-web::before { content: '🌐'; }
 
449
  .icon-leader::before { content: '🏆'; }
450
  .icon-company::before { content: '🏢'; }
451
  .icon-ton::before { content: '💎'; }
 
452
 
453
  @media (max-width: 480px) {
454
  .section-title { font-size: 1.8em; }
 
458
  .stats-grid { grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: var(--padding-s); }
459
  .stat-value { font-size: 1.5em; }
460
  .modal-content { margin: 25% auto; width: 92%; }
461
+ .ton-section h3 { font-size: 1.3em; }
462
+ .ton-info { font-size: 1em; }
463
  }
464
  </style>
465
  </head>
 
487
  </a>
488
  </section>
489
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  <section class="ecosystem-header">
491
  <h2 class="section-title"><i class="icon icon-company"></i>Экосистема инноваций</h2>
492
  <p class="description">
 
571
  </div>
572
  </section>
573
 
574
+ <section class="ton-section">
575
+ <h3><i class="icon icon-ton"></i> TON Wallet</h3>
576
+ <p class="description" style="margin-bottom: var(--padding-m); font-size: 1em;">Подключите свой TON кошелек для авторизации и просмотра баланса.</p>
577
+ <div id="ton-connect-ui" class="ton-connect-widget-container"></div>
578
+ <div class="ton-info">
579
+ <div id="ton-status">Статус: Не подключен</div>
580
+ <div id="ton-address" style="display: none;">Адрес: <span class="ton-address"></span></div>
581
+ <div id="ton-balance" style="display: none;">Баланс: <span class="ton-balance"></span></div>
582
+ </div>
583
+ </section>
584
+
585
+
586
  <footer class="footer-greeting">
587
  <p id="greeting">Инициализация...</p>
588
  </footer>
 
603
  </div>
604
  </div>
605
 
 
606
  <script>
607
  const tg = window.Telegram.WebApp;
 
608
 
609
  function applyTheme(themeParams) {
610
  const root = document.documentElement;
 
627
  }
628
  }
629
 
630
+ let tonConnectUI = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
632
+ async function fetchTonBalance(address) {
633
+ const tonCenterUrl = `https://toncenter.com/api/v3/account/getBalance?address=${address}`;
634
+ try {
635
+ const response = await fetch(tonCenterUrl);
636
+ if (!response.ok) {
637
+ throw new Error(`HTTP error! status: ${response.status}`);
638
+ }
639
+ const data = await response.json();
640
+ if (data.error) {
641
+ throw new Error(data.error);
642
+ }
643
+ // Balance is in NanoTONs, convert to TON
644
+ const balanceNano = BigInt(data.result.balance);
645
+ const balanceTon = Number(balanceNano) / 1e9;
646
+ return balanceTon.toFixed(2); // Format to 2 decimal places
647
+ } catch (error) {
648
+ console.error("Error fetching TON balance:", error);
649
+ return "Ошибка";
650
+ }
651
+ }
652
 
653
+ function setupTelegram() {
 
654
  if (!tg || !tg.initData) {
655
  console.error("Telegram WebApp script not loaded or initData is missing.");
656
  const greetingElement = document.getElementById('greeting');
657
  if(greetingElement) greetingElement.textContent = 'Не удалось связаться с Telegram.';
658
+ document.body.style.visibility = 'visible';
659
  return;
660
  }
661
 
 
662
  tg.ready();
663
  tg.expand();
664
 
 
665
  applyTheme(tg.themeParams);
666
  tg.onEvent('themeChanged', () => applyTheme(tg.themeParams));
667
 
 
668
  fetch('/verify', {
669
  method: 'POST',
670
  headers: {
 
688
  console.error('Error sending initData for verification:', error);
689
  });
690
 
691
+
692
  const user = tg.initDataUnsafe?.user;
693
  const greetingElement = document.getElementById('greeting');
694
  if (user) {
 
696
  greetingElement.textContent = `Добро пожаловать, ${name}! 👋`;
697
  } else {
698
  greetingElement.textContent = 'Добро пожаловать!';
699
+ console.warn('Telegram User data not available (initDataUnsafe.user is empty).');
700
  }
701
 
 
702
  const contactButtons = document.querySelectorAll('.contact-link');
703
  contactButtons.forEach(button => {
704
  button.addEventListener('click', (e) => {
705
  e.preventDefault();
 
706
  tg.openTelegramLink('https://t.me/morshenkhan');
707
  });
708
  });
709
 
 
710
  const modal = document.getElementById("saveModal");
711
  const saveCardBtn = document.getElementById("save-card-btn");
712
  const closeBtn = document.getElementById("modal-close-btn");
 
729
  modal.style.display = "none";
730
  }
731
  });
732
+ } else {
733
+ console.error("Modal elements not found!");
734
  }
735
 
736
+ setupTonConnect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
 
738
+ document.body.style.visibility = 'visible';
739
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
 
741
+ function setupTonConnect() {
742
+ if (!window.TonConnectUI) {
743
+ console.error("TonConnectUI script not loaded.");
744
+ document.getElementById('ton-status').textContent = 'Ошибка загрузки TonConnect.';
745
+ return;
746
+ }
747
 
748
+ try {
749
+ tonConnectUI = new window.TonConnectUI({
750
+ manifestUrl: window.location.origin + '/tonconnect-manifest.json',
751
+ uiPreferences: {
752
+ colorsSet: tg.themeParams.bg_color ? 'STANDARD' : 'DARK', // Use standard or dark based on TG theme
753
+ // You can customize colors further based on tg.themeParams if needed
754
+ // colors: {
755
+ // dark: { ... },
756
+ // light: { ... }
757
+ // }
758
+ },
759
+ });
760
 
761
+ tonConnectUI.onStatusChange(async wallet => {
762
+ const tonStatusElement = document.getElementById('ton-status');
763
+ const tonAddressElement = document.getElementById('ton-address');
764
+ const tonBalanceElement = document.getElementById('ton-balance');
765
+
766
+ if (wallet) {
767
+ const address = wallet.account.address;
768
+ tonStatusElement.textContent = 'Статус: Подключен';
769
+ tonAddressElement.style.display = 'block';
770
+ tonAddressElement.querySelector('span').textContent = address;
771
+
772
+ tonBalanceElement.style.display = 'block';
773
+ tonBalanceElement.querySelector('span').textContent = 'Загрузка...';
774
+
775
+ const balance = await fetchTonBalance(address);
776
+ tonBalanceElement.querySelector('span').textContent = balance + ' TON';
777
+
778
+ console.log('TON Wallet connected:', address);
779
+ console.log('TON Account:', wallet.account);
780
+ // You might want to send this address to the backend here
781
+ // to link it to the Telegram user ID if needed for your application logic.
782
+
783
+ } else {
784
+ tonStatusElement.textContent = 'Статус: Не подключен';
785
+ tonAddressElement.style.display = 'none';
786
+ tonBalanceElement.style.display = 'none';
787
+ console.log('TON Wallet disconnected.');
788
+ }
789
+ });
790
 
791
+ // Render the TonConnect button/widget
792
+ const tonConnectUiContainer = document.getElementById('ton-connect-ui');
793
+ if (tonConnectUiContainer) {
794
+ tonConnectUI.renderButtons(tonConnectUiContainer, {
795
+ messages: {
796
+ connectingModalWithoutWallets: 'Пожалуйста, установите кошелек, поддерживающий TonConnect.',
797
+ connectingModalTitle: 'Подключение кошелька TON',
798
+ walletConnectRequestTemporarilyUnavailable: 'Запрос подключения временно недоступен. Попробуйте позже.',
799
+ },
800
+ labels: {
801
+ disconnect: 'Отключить кошелек',
802
+ connectWallet: 'Подключить TON Кошелек',
803
+ connectedWallet: 'Подключен {{walletName}}',
804
+ }
805
+ });
806
+ } else {
807
+ console.error("TON Connect UI container not found!");
808
  }
809
+
810
+ } catch (e) {
811
+ console.error("Error initializing TonConnectUI:", e);
812
+ document.getElementById('ton-status').textContent = 'Ошибка инициализации TonConnect.';
813
  }
814
+ }
815
+
816
+
817
+ if (window.Telegram && window.Telegram.WebApp) {
818
+ setupTelegram();
819
+ } else {
820
+ console.warn("Telegram WebApp script not immediately available, waiting for window.onload");
821
+ window.addEventListener('load', setupTelegram);
822
+ setTimeout(() => {
823
+ if (document.body.style.visibility !== 'visible') {
824
+ console.error("Telegram WebApp script fallback timeout triggered.");
825
+ const greetingElement = document.getElementById('greeting');
826
+ if(greetingElement) greetingElement.textContent = 'Ошибка загрузки интерфейса Telegram.';
827
+ document.body.style.visibility = 'visible';
828
+ setupTonConnect(); // Still try to setup TON even without TG
829
+ }
830
+ }, 3500);
831
+ }
832
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  </script>
834
  </body>
835
  </html>
 
901
  }
902
  .user-card .name { font-weight: 600; font-size: 1.2em; margin-bottom: 0.3rem; color: var(--admin-primary); }
903
  .user-card .username { color: var(--admin-secondary); margin-bottom: 0.8rem; font-size: 0.95em; }
904
+ .user-card .username a { color: inherit; text-decoration: none; }
905
  .user-card .details { font-size: 0.9em; color: #495057; word-break: break-word; }
906
  .user-card .detail-item { margin-bottom: 0.3rem; }
907
  .user-card .detail-item strong { color: var(--admin-text); }
 
983
  <img src="{{ user.photo_url if user.photo_url else 'data:image/svg+xml;charset=UTF-8,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 100 100%27%3e%3crect width=%27100%27 height=%27100%27 fill=%27%23e9ecef%27/%3e%3ctext x=%2750%25%27 y=%2755%25%27 dominant-baseline=%27middle%27 text-anchor=%27middle%27 font-size=%2745%27 font-family=%27sans-serif%27 fill=%27%23adb5bd%27%3e?%3c/text%3e%3c/svg%3e' }}" alt="User Avatar">
984
  <div class="name">{{ user.first_name or '' }} {{ user.last_name or '' }}</div>
985
  {% if user.username %}
986
+ <div class="username"><a href="https://t.me/{{ user.username }}" target="_blank">@{{ user.username }}</a></div>
987
  {% else %}
988
  <div class="username" style="height: 1.3em;"></div>
989
  {% endif %}
 
992
  <div class="detail-item"><strong>Язык:</strong> {{ user.language_code or 'N/A' }}</div>
993
  <div class="detail-item"><strong>Premium:</strong> {{ 'Да' if user.is_premium else 'Нет' }}</div>
994
  <div class="detail-item"><strong>Телефон:</strong> {{ user.phone_number or 'Недоступен' }}</div>
995
+ {% if user.ton_address %}
996
+ <div class="detail-item"><strong>TON:</strong> {{ user.ton_address }}</div>
997
+ {% endif %}
998
  </div>
999
  <div class="timestamp">Визит: {{ user.visited_at_str }}</div>
1000
  </div>
 
1028
  } catch (error) {
1029
  statusMessage.textContent = `Ошибка ${action}: ${error.message}`;
1030
  statusMessage.style.color = 'var(--admin-danger)';
1031
+ console.error(`Error during ${action}:`, error);
1032
  } finally {
1033
  loader.style.display = 'none';
1034
  }
 
1046
  </html>
1047
  """
1048
 
1049
+ TON_MANIFEST = {
1050
+ "url": os.getenv("APP_BASE_URL", "https://localhost:7860") + "/",
1051
+ "name": "Morshen Group TMA",
1052
+ "iconUrl": os.getenv("APP_BASE_URL", "https://localhost:7860") + "/static/morshengroup.jpg", # Or a publicly hosted image
1053
+ "termsOfServiceUrl": "https://example.com/terms", # Replace with your actual terms
1054
+ "privacyPolicyUrl": "https://example.com/privacy" # Replace with your actual privacy policy
1055
+ }
1056
+
1057
  @app.route('/')
1058
  def index():
1059
  theme_params = {}
 
1070
  user_data_parsed, is_valid = verify_telegram_data(init_data_str)
1071
 
1072
  user_info_dict = {}
1073
+ user_id = None
1074
  if user_data_parsed and 'user' in user_data_parsed:
1075
  try:
1076
  user_json_str = unquote(user_data_parsed['user'][0])
1077
  user_info_dict = json.loads(user_json_str)
1078
+ user_id = user_info_dict.get('id')
1079
  except Exception as e:
1080
  logging.error(f"Could not parse user JSON: {e}")
1081
  user_info_dict = {}
1082
 
1083
+ if is_valid and user_id:
1084
+ now = time.time()
1085
+ # Load current data to potentially merge
1086
+ current_data = load_visitor_data()
1087
+ user_key = str(user_id)
1088
+
1089
+ # Get existing user data if any, and update it
1090
+ existing_user_entry = current_data.get(user_key, {})
1091
+
1092
+ # Update with latest TG data
1093
+ updated_user_entry = {
1094
+ 'id': user_id,
1095
+ 'first_name': user_info_dict.get('first_name', existing_user_entry.get('first_name')),
1096
+ 'last_name': user_info_dict.get('last_name', existing_user_entry.get('last_name')),
1097
+ 'username': user_info_dict.get('username', existing_user_entry.get('username')),
1098
+ 'photo_url': user_info_dict.get('photo_url', existing_user_entry.get('photo_url')),
1099
+ 'language_code': user_info_dict.get('language_code', existing_user_entry.get('language_code')),
1100
+ 'is_premium': user_info_dict.get('is_premium', existing_user_entry.get('is_premium', False)),
1101
+ 'phone_number': user_info_dict.get('phone_number', existing_user_entry.get('phone_number')),
1102
+ 'visited_at': now,
1103
+ 'visited_at_str': datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
1104
+ }
1105
+
1106
+ # Keep existing TON address if present
1107
+ if 'ton_address' in existing_user_entry:
1108
+ updated_user_entry['ton_address'] = existing_user_entry['ton_address']
1109
+
1110
+ # Save the updated entry
1111
+ save_visitor_data({user_key: updated_user_entry})
1112
+
1113
  return jsonify({"status": "ok", "verified": True, "user": user_info_dict}), 200
1114
  else:
1115
+ logging.warning(f"Verification failed or user ID missing for data: {user_info_dict}")
1116
+ return jsonify({"status": "error", "verified": False, "message": "Invalid data or missing user ID"}), 403
1117
 
1118
  except Exception as e:
1119
  logging.exception("Error in /verify endpoint")
1120
  return jsonify({"status": "error", "message": "Internal server error"}), 500
1121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
  @app.route('/admin')
1123
  def admin_panel():
1124
  current_data = load_visitor_data()
 
1140
  upload_data_to_hf_async()
1141
  return jsonify({"status": "ok", "message": "Загрузка данных на Hugging Face запущена в фоновом режиме."})
1142
 
1143
+ @app.route('/tonconnect-manifest.json')
1144
+ def tonconnect_manifest():
1145
+ manifest_url = os.getenv("APP_BASE_URL")
1146
+ if not manifest_url:
1147
+ # Fallback for local testing or if env var is not set
1148
+ manifest_url = f"http://{request.host}"
1149
+ logging.warning(f"APP_BASE_URL not set. Using {manifest_url} for manifest.")
1150
+
1151
+ manifest = {
1152
+ "url": manifest_url + "/",
1153
+ "name": "Morshen Group TMA",
1154
+ "iconUrl": manifest_url + "/static/morshengroup.jpg",
1155
+ "termsOfServiceUrl": manifest_url + "/terms", # Placeholder
1156
+ "privacyPolicyUrl": manifest_url + "/privacy" # Placeholder
1157
+ }
1158
+ return jsonify(manifest)
1159
 
1160
+ @app.route('/static/morshengroup.jpg')
1161
+ def serve_logo():
1162
+ # This route serves the logo locally if needed,
1163
+ # but the HF link in the template is likely better.
1164
+ # If you put morshengroup.jpg in a 'static' folder next to app.py,
1165
+ # Flask can serve it. For Hugging Face Spaces, serving static files
1166
+ # from the repo path directly via URL (like in the template) is common.
1167
+ # Keeping this route as a placeholder or for local testing.
1168
+ try:
1169
+ # Attempt to serve from local static folder
1170
+ return app.send_static_file('morshengroup.jpg')
1171
+ except FileNotFoundError:
1172
+ # Fallback or error if file isn't there
1173
+ return "Logo not found", 404
1174
 
1175
 
1176
  if __name__ == '__main__':
1177
+ print("---")
1178
+ print("--- MORSHEN GROUP MINI APP SERVER ---")
1179
+ print("---")
1180
+ print(f"Flask server starting on http://{HOST}:{PORT}")
1181
+ print(f"Using Bot Token ID: {BOT_TOKEN.split(':')[0]}")
1182
+ print(f"Visitor data file: {DATA_FILE}")
1183
+ print(f"Hugging Face Repo: {REPO_ID}")
1184
+ print(f"HF Data Path: {HF_DATA_FILE_PATH}")
1185
+ if os.getenv("APP_BASE_URL"):
1186
+ print(f"App Base URL (for manifest): {os.getenv('APP_BASE_URL')}")
1187
+ else:
1188
+ print("APP_BASE_URL not set. Manifest URL will be based on request host.")
1189
+ if not HF_TOKEN_READ or not HF_TOKEN_WRITE:
1190
+ print("---")
1191
+ print("--- WARNING: HUGGING FACE TOKEN(S) NOT SET ---")
1192
+ print("--- Backup/restore functionality will be limited. Set HF_TOKEN_READ and HF_TOKEN_WRITE environment variables.")
1193
+ print("---")
1194
+ else:
1195
+ print("--- Hugging Face tokens found.")
1196
+ print("--- Attempting initial data download from Hugging Face...")
1197
+ download_data_from_hf()
1198
 
 
1199
  load_visitor_data()
1200
 
1201
+ print("---")
1202
+ print("--- SECURITY WARNING ---")
1203
+ print("--- The /admin route and its sub-routes are NOT protected.")
1204
+ print("--- Implement proper authentication before deploying.")
1205
+ print("---")
1206
+
1207
  if HF_TOKEN_WRITE:
1208
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
1209
  backup_thread.start()
1210
+ print("--- Periodic backup thread started (every hour).")
1211
+ else:
1212
+ print("--- Periodic backup disabled (HF_TOKEN_WRITE missing).")
1213
 
1214
+ print("--- Server Ready ---")
1215
  app.run(host=HOST, port=PORT, debug=False)