Aleksmorshen commited on
Commit
fe269d1
·
verified ·
1 Parent(s): adf3645

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -175
app.py CHANGED
@@ -13,9 +13,8 @@ import logging
13
  import threading
14
  from huggingface_hub import HfApi, hf_hub_download
15
  from huggingface_hub.utils import RepositoryNotFoundError
16
- import requests
17
 
18
- BOT_TOKEN = os.getenv("BOT_TOKEN", "7566834146:AAGiG4MaTZZvvbTVsqEJVG5SYK5hUlc_Ewo")
19
  HOST = '0.0.0.0'
20
  PORT = 7860
21
  DATA_FILE = 'data.json'
@@ -35,10 +34,9 @@ visitor_data_cache = {}
35
  def download_data_from_hf():
36
  global visitor_data_cache
37
  if not HF_TOKEN_READ:
38
- logging.warning("HF_TOKEN_READ not set. Skipping Hugging Face download.")
39
  return False
40
  try:
41
- logging.info(f"Attempting to download {HF_DATA_FILE_PATH} from {REPO_ID}...")
42
  hf_hub_download(
43
  repo_id=REPO_ID,
44
  filename=HF_DATA_FILE_PATH,
@@ -49,18 +47,15 @@ def download_data_from_hf():
49
  force_download=True,
50
  etag_timeout=10
51
  )
52
- logging.info("Data file successfully downloaded from Hugging Face.")
53
  with _data_lock:
54
  try:
55
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
56
  visitor_data_cache = json.load(f)
57
- logging.info("Successfully loaded downloaded data into cache.")
58
  except (FileNotFoundError, json.JSONDecodeError) as e:
59
- logging.error(f"Error reading downloaded data file: {e}. Starting with empty cache.")
60
  visitor_data_cache = {}
61
  return True
62
  except RepositoryNotFoundError:
63
- logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.")
64
  except Exception as e:
65
  logging.error(f"Error downloading data from Hugging Face: {e}")
66
  return False
@@ -72,15 +67,11 @@ def load_visitor_data():
72
  try:
73
  with open(DATA_FILE, 'r', encoding='utf-8') as f:
74
  visitor_data_cache = json.load(f)
75
- logging.info("Visitor data loaded from local JSON.")
76
  except FileNotFoundError:
77
- logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.")
78
  visitor_data_cache = {}
79
  except json.JSONDecodeError:
80
- logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.")
81
  visitor_data_cache = {}
82
  except Exception as e:
83
- logging.error(f"Unexpected error loading visitor data: {e}")
84
  visitor_data_cache = {}
85
  return visitor_data_cache
86
 
@@ -90,17 +81,14 @@ def save_visitor_data(data):
90
  visitor_data_cache.update(data)
91
  with open(DATA_FILE, 'w', encoding='utf-8') as f:
92
  json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4)
93
- logging.info(f"Visitor data successfully saved to {DATA_FILE}.")
94
  upload_data_to_hf_async()
95
  except Exception as e:
96
  logging.error(f"Error saving visitor data: {e}")
97
 
98
  def upload_data_to_hf():
99
  if not HF_TOKEN_WRITE:
100
- logging.warning("HF_TOKEN_WRITE not set. Skipping Hugging Face upload.")
101
  return
102
  if not os.path.exists(DATA_FILE):
103
- logging.warning(f"{DATA_FILE} does not exist. Skipping upload.")
104
  return
105
 
106
  try:
@@ -108,10 +96,8 @@ def upload_data_to_hf():
108
  with _data_lock:
109
  file_content_exists = os.path.getsize(DATA_FILE) > 0
110
  if not file_content_exists:
111
- logging.warning(f"{DATA_FILE} is empty. Skipping upload.")
112
  return
113
 
114
- logging.info(f"Attempting to upload {DATA_FILE} to {REPO_ID}/{HF_DATA_FILE_PATH}...")
115
  api.upload_file(
116
  path_or_fileobj=DATA_FILE,
117
  path_in_repo=HF_DATA_FILE_PATH,
@@ -120,7 +106,6 @@ def upload_data_to_hf():
120
  token=HF_TOKEN_WRITE,
121
  commit_message=f"Update visitor data {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
122
  )
123
- logging.info("Visitor data successfully uploaded to Hugging Face.")
124
  except Exception as e:
125
  logging.error(f"Error uploading data to Hugging Face: {e}")
126
 
@@ -130,11 +115,9 @@ def upload_data_to_hf_async():
130
 
131
  def periodic_backup():
132
  if not HF_TOKEN_WRITE:
133
- logging.info("Periodic backup disabled: HF_TOKEN_WRITE not set.")
134
  return
135
  while True:
136
  time.sleep(3600)
137
- logging.info("Initiating periodic backup...")
138
  upload_data_to_hf()
139
 
140
  def verify_telegram_data(init_data_str):
@@ -157,7 +140,7 @@ def verify_telegram_data(init_data_str):
157
  auth_date = int(parsed_data.get('auth_date', [0])[0])
158
  current_time = int(time.time())
159
  if current_time - auth_date > 86400:
160
- logging.warning(f"Telegram InitData is older than 1 hour (Auth Date: {auth_date}, Current: {current_time}).")
161
  return parsed_data, True
162
  else:
163
  logging.warning(f"Data verification failed. Calculated: {calculated_hash}, Received: {received_hash}")
@@ -174,7 +157,6 @@ TEMPLATE = """
174
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no, viewport-fit=cover">
175
  <title>Morshen Group</title>
176
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
177
- <script src="https://unpkg.com/@tonconnect/ui@latest/dist/tonconnect-ui.min.js"></script>
178
  <link rel="preconnect" href="https://fonts.googleapis.com">
179
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
180
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
@@ -275,6 +257,14 @@ TEMPLATE = """
275
  background: rgba(44, 44, 46, 0.95);
276
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), var(--shadow-medium);
277
  }
 
 
 
 
 
 
 
 
278
  .tag {
279
  display: inline-block; background: var(--tag-bg); color: var(--text-secondary-color);
280
  padding: 6px 12px; border-radius: var(--border-radius-s); font-size: 0.85em;
@@ -366,6 +356,7 @@ TEMPLATE = """
366
  box-shadow: var(--shadow-medium), 0 0 0 4px rgba(var(--tg-theme-bg-color-rgb, 18, 18, 18), 0.3);
367
  }
368
  .save-card-button i { font-size: 1.2em; }
 
369
  .modal {
370
  display: none; position: fixed; z-index: 1001;
371
  left: 0; top: 0; width: 100%; height: 100%;
@@ -396,6 +387,7 @@ TEMPLATE = """
396
  .modal-text { font-size: 1.2em; line-height: 1.6; margin-bottom: var(--padding-s); word-wrap: break-word; }
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
  .icon { display: inline-block; width: 1.2em; text-align: center; margin-right: 8px; opacity: 0.9; }
400
  .icon-save::before { content: '💾'; }
401
  .icon-web::before { content: '🌐'; }
@@ -416,21 +408,9 @@ TEMPLATE = """
416
  .icon-link::before { content: '🔗'; }
417
  .icon-leader::before { content: '🏆'; }
418
  .icon-company::before { content: '🏢'; }
419
- .icon-wallet::before { content: '💎'; }
420
- .wallet-info-item {
421
- font-size: 1em; color: var(--text-secondary-color); word-break: break-all;
422
- margin-top: var(--padding-s);
423
- }
424
- .wallet-info-item strong {
425
- color: var(--text-color);
426
- }
427
- .wallet-balance {
428
- font-size: 1.8em; font-weight: 700; color: var(--tg-theme-link-color);
429
- margin-top: var(--padding-s);
430
- }
431
- #ton-connect-button > button {
432
- width: 100%;
433
- }
434
  @media (max-width: 480px) {
435
  .section-title { font-size: 1.8em; }
436
  .logo span { font-size: 1.4em; }
@@ -466,21 +446,22 @@ TEMPLATE = """
466
  </a>
467
  </section>
468
 
469
- <section class="section-card">
470
- <h2 class="section-title"><i class="icon icon-wallet"></i>TON Кошелек</h2>
471
- <p class="description">
472
- Подключите ваш TON кошелек, чтобы проверить баланс прямо в приложении.
473
- </p>
474
- <div id="wallet-info" style="display: none; margin-top: var(--padding-m);">
475
- <div class="wallet-info-item">
476
- <strong>Баланс:</strong>
477
- <div class="wallet-balance"><span id="wallet-balance">...</span> TON</div>
478
- </div>
479
- <div class="wallet-info-item">
480
- <strong>Адрес:</strong> <span id="wallet-address"></span>
481
- </div>
482
- </div>
483
- <div id="ton-connect-button" style="margin-top: var(--padding-m);"></div>
 
484
  </section>
485
 
486
  <section class="ecosystem-header">
@@ -588,8 +569,10 @@ TEMPLATE = """
588
  </div>
589
 
590
 
 
591
  <script>
592
  const tg = window.Telegram.WebApp;
 
593
 
594
  function applyTheme(themeParams) {
595
  const root = document.documentElement;
@@ -612,60 +595,36 @@ TEMPLATE = """
612
  }
613
  }
614
 
615
- async function fetchBalance(address) {
616
- const balanceEl = document.getElementById('wallet-balance');
617
- balanceEl.textContent = '...';
618
- try {
619
- const response = await fetch('/ton_balance', {
620
- method: 'POST',
621
- headers: {
622
- 'Content-Type': 'application/json',
623
- 'Accept': 'application/json'
624
- },
625
- body: JSON.stringify({ address: address }),
626
- });
627
-
628
- if (!response.ok) {
629
- throw new Error(`HTTP error! status: ${response.status}`);
630
- }
631
-
632
- const data = await response.json();
633
- if (data.status === 'ok') {
634
- balanceEl.textContent = data.balance;
635
- } else {
636
- throw new Error(data.message || 'Failed to fetch balance');
637
- }
638
- } catch (error) {
639
- console.error('Error fetching balance:', error);
640
- balanceEl.textContent = 'Ошибка';
641
- if (tg.HapticFeedback) tg.HapticFeedback.notificationOccurred('error');
642
- }
643
- }
644
-
645
- function setupTonConnect() {
646
- const tonConnectUI = new TonConnectUI.TonConnectUI({
647
- manifestUrl: window.location.origin + '/tonconnect-manifest.json',
648
- uiOptions: {
649
- buttonRootId: 'ton-connect-button'
650
- }
651
- });
652
-
653
- const walletInfoEl = document.getElementById('wallet-info');
654
- const walletAddressEl = document.getElementById('wallet-address');
655
-
656
- tonConnectUI.onStatusChange(wallet => {
657
- if (wallet) {
658
- const address = TonConnectUI.toUserFriendlyAddress(wallet.account.address);
659
- walletAddressEl.textContent = `${address.slice(0, 6)}...${address.slice(-4)}`;
660
- walletInfoEl.style.display = 'block';
661
- fetchBalance(wallet.account.address);
662
- } else {
663
- walletInfoEl.style.display = 'none';
664
- document.getElementById('wallet-balance').textContent = '...';
665
- walletAddressEl.textContent = '';
666
- }
667
- });
668
- }
669
 
670
 
671
  function setupTelegram() {
@@ -713,7 +672,6 @@ TEMPLATE = """
713
  greetingElement.textContent = `Добро пожаловать, ${name}! 👋`;
714
  } else {
715
  greetingElement.textContent = 'Добро пожаловать!';
716
- console.warn('Telegram User data not available (initDataUnsafe.user is empty).');
717
  }
718
 
719
  const contactButtons = document.querySelectorAll('.contact-link');
@@ -746,23 +704,85 @@ TEMPLATE = """
746
  modal.style.display = "none";
747
  }
748
  });
749
- } else {
750
- console.error("Modal elements not found!");
751
  }
752
-
753
- setupTonConnect();
754
 
755
  document.body.style.visibility = 'visible';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  }
757
 
758
  if (window.Telegram && window.Telegram.WebApp) {
759
  setupTelegram();
760
  } else {
761
- console.warn("Telegram WebApp script not immediately available, waiting for window.onload");
762
  window.addEventListener('load', setupTelegram);
763
  setTimeout(() => {
764
  if (document.body.style.visibility !== 'visible') {
765
- console.error("Telegram WebApp script fallback timeout triggered.");
766
  const greetingElement = document.getElementById('greeting');
767
  if(greetingElement) greetingElement.textContent = 'Ошибка загрузки интерфейса Telegram.';
768
  document.body.style.visibility = 'visible';
@@ -865,6 +885,7 @@ ADMIN_TEMPLATE = """
865
  transition: background-color 0.2s ease;
866
  }
867
  .refresh-btn:hover { background-color: #0b5ed7; }
 
868
  .admin-controls {
869
  background: var(--admin-card-bg);
870
  padding: var(--padding);
@@ -963,7 +984,6 @@ ADMIN_TEMPLATE = """
963
  } catch (error) {
964
  statusMessage.textContent = `Ошибка ${action}: ${error.message}`;
965
  statusMessage.style.color = 'var(--admin-danger)';
966
- console.error(`Error during ${action}:`, error);
967
  } finally {
968
  loader.style.display = 'none';
969
  }
@@ -1033,6 +1053,17 @@ def verify_data():
1033
  logging.exception("Error in /verify endpoint")
1034
  return jsonify({"status": "error", "message": "Internal server error"}), 500
1035
 
 
 
 
 
 
 
 
 
 
 
 
1036
  @app.route('/admin')
1037
  def admin_panel():
1038
  current_data = load_visitor_data()
@@ -1054,73 +1085,25 @@ def admin_trigger_upload():
1054
  upload_data_to_hf_async()
1055
  return jsonify({"status": "ok", "message": "Загрузка данных на Hugging Face запущена в фоновом режиме."})
1056
 
1057
- @app.route('/tonconnect-manifest.json')
1058
- def tonconnect_manifest():
1059
- return jsonify({
1060
- "url": request.host_url.rstrip('/'),
1061
- "name": "Morshen Group App",
1062
- "iconUrl": "https://huggingface.co/spaces/Aleksmorshen/Telemap8/resolve/main/morshengroup.jpg"
1063
- })
1064
-
1065
- @app.route('/ton_balance', methods=['POST'])
1066
- def get_ton_balance():
1067
- req_data = request.get_json()
1068
- address = req_data.get('address')
1069
- if not address:
1070
- return jsonify({"status": "error", "message": "Address is required"}), 400
1071
-
1072
- try:
1073
- api_url = f"https://toncenter.com/api/v2/getWalletInformation?address={address}"
1074
- response = requests.get(api_url)
1075
- response.raise_for_status()
1076
- data = response.json()
1077
-
1078
- if data.get("ok"):
1079
- balance_nanotons = int(data["result"]["balance"])
1080
- balance_ton = balance_nanotons / 1_000_000_000
1081
- return jsonify({"status": "ok", "balance": f"{balance_ton:.4f}"})
1082
- else:
1083
- return jsonify({"status": "error", "message": "Failed to get balance from API"}), 500
1084
- except requests.exceptions.RequestException as e:
1085
- logging.error(f"Error fetching TON balance from Toncenter: {e}")
1086
- return jsonify({"status": "error", "message": "Could not connect to TON API"}), 503
1087
- except (KeyError, ValueError) as e:
1088
- logging.error(f"Error parsing Toncenter response: {e}")
1089
- return jsonify({"status": "error", "message": "Invalid API response from Toncenter"}), 500
1090
 
1091
- if __name__ == '__main__':
1092
- print("---")
1093
- print("--- MORSHEN GROUP MINI APP SERVER ---")
1094
- print("---")
1095
- print(f"Flask server starting on http://{HOST}:{PORT}")
1096
- print(f"Using Bot Token ID: {BOT_TOKEN.split(':')[0]}")
1097
- print(f"Visitor data file: {DATA_FILE}")
1098
- print(f"Hugging Face Repo: {REPO_ID}")
1099
- print(f"HF Data Path: {HF_DATA_FILE_PATH}")
1100
- if not HF_TOKEN_READ or not HF_TOKEN_WRITE:
1101
- print("---")
1102
- print("--- WARNING: HUGGING FACE TOKEN(S) NOT SET ---")
1103
- print("--- Backup/restore functionality will be limited. Set HF_TOKEN_READ and HF_TOKEN_WRITE environment variables.")
1104
- print("---")
1105
- else:
1106
- print("--- Hugging Face tokens found.")
1107
- print("--- Attempting initial data download from Hugging Face...")
1108
- download_data_from_hf()
1109
 
 
 
1110
  load_visitor_data()
1111
 
1112
- print("---")
1113
- print("--- SECURITY WARNING ---")
1114
- print("--- The /admin route and its sub-routes are NOT protected.")
1115
- print("--- Implement proper authentication before deploying.")
1116
- print("---")
1117
-
1118
  if HF_TOKEN_WRITE:
1119
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
1120
  backup_thread.start()
1121
- print("--- Periodic backup thread started (every hour).")
1122
- else:
1123
- print("--- Periodic backup disabled (HF_TOKEN_WRITE missing).")
1124
 
1125
- print("--- Server Ready ---")
1126
  app.run(host=HOST, port=PORT, debug=False)
 
13
  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'
 
34
  def download_data_from_hf():
35
  global visitor_data_cache
36
  if not HF_TOKEN_READ:
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
  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
  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
  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
  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
  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
 
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
  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}")
 
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
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
 
160
  <link rel="preconnect" href="https://fonts.googleapis.com">
161
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
162
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
257
  background: rgba(44, 44, 46, 0.95);
258
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), var(--shadow-medium);
259
  }
260
+ .btn-red {
261
+ background: linear-gradient(95deg, #ff3b30, #ff453a);
262
+ color: var(--tg-theme-button-text-color);
263
+ }
264
+ .btn-red:hover {
265
+ background: linear-gradient(95deg, #ff453a, #ff3b30);
266
+ }
267
+
268
  .tag {
269
  display: inline-block; background: var(--tag-bg); color: var(--text-secondary-color);
270
  padding: 6px 12px; border-radius: var(--border-radius-s); font-size: 0.85em;
 
356
  box-shadow: var(--shadow-medium), 0 0 0 4px rgba(var(--tg-theme-bg-color-rgb, 18, 18, 18), 0.3);
357
  }
358
  .save-card-button i { font-size: 1.2em; }
359
+
360
  .modal {
361
  display: none; position: fixed; z-index: 1001;
362
  left: 0; top: 0; width: 100%; height: 100%;
 
387
  .modal-text { font-size: 1.2em; line-height: 1.6; margin-bottom: var(--padding-s); word-wrap: break-word; }
388
  .modal-text b { color: var(--tg-theme-link-color); font-weight: 600; }
389
  .modal-instruction { font-size: 1em; color: var(--text-secondary-color); margin-top: var(--padding-m); }
390
+
391
  .icon { display: inline-block; width: 1.2em; text-align: center; margin-right: 8px; opacity: 0.9; }
392
  .icon-save::before { content: '💾'; }
393
  .icon-web::before { content: '🌐'; }
 
408
  .icon-link::before { content: '🔗'; }
409
  .icon-leader::before { content: '🏆'; }
410
  .icon-company::before { content: '🏢'; }
411
+ .icon-ton::before { content: '💎'; }
412
+ .icon-wallet::before { content: '👛'; }
413
+
 
 
 
 
 
 
 
 
 
 
 
 
414
  @media (max-width: 480px) {
415
  .section-title { font-size: 1.8em; }
416
  .logo span { font-size: 1.4em; }
 
446
  </a>
447
  </section>
448
 
449
+ <section class="ton-wallet-section section-card">
450
+ <h2 class="section-title"><i class="icon icon-ton"></i>TON Кошелек</h2>
451
+ <p class="description" id="ton-wallet-status">Статус: Не подключен</p>
452
+ <div id="ton-info" style="display: none;">
453
+ <p class="modal-text" style="font-size: 1.1em; margin-bottom: var(--padding-s);">
454
+ <b>Адрес:</b> <span id="ton-wallet-address" style="word-break: break-all;"></span>
455
+ </p>
456
+ <p class="modal-text" style="font-size: 1.1em;">
457
+ <b>Баланс:</b> <span id="ton-balance"></span> TON
458
+ </p>
459
+ </div>
460
+ <div style="display: flex; gap: var(--padding-s); margin-top: var(--padding-m);">
461
+ <button id="connect-ton-btn" class="btn" style="flex-grow: 1;"><i class="icon icon-wallet"></i>Подключить кошелек</button>
462
+ <button id="disconnect-ton-btn" class="btn btn-red" style="flex-grow: 1; display: none;">Отключить</button>
463
+ </div>
464
+ <p id="ton-error" style="color: var(--admin-danger); font-size: 0.9em; margin-top: var(--padding-s); display: none;"></p>
465
  </section>
466
 
467
  <section class="ecosystem-header">
 
569
  </div>
570
 
571
 
572
+ <script src="https://unpkg.com/@tonconnect/sdk@2.0.0/dist/tonconnect-sdk.min.js"></script>
573
  <script>
574
  const tg = window.Telegram.WebApp;
575
+ let tonConnectSDK = null;
576
 
577
  function applyTheme(themeParams) {
578
  const root = document.documentElement;
 
595
  }
596
  }
597
 
598
+ async function fetchTonBalance(address) {
599
+ const rpcUrl = 'https://rpc.toncenter.com/api/v2/jsonRPC'; // Public RPC endpoint
600
+ try {
601
+ const response = await fetch(rpcUrl, {
602
+ method: 'POST',
603
+ headers: {
604
+ 'Content-Type': 'application/json',
605
+ },
606
+ body: JSON.stringify({
607
+ jsonrpc: '2.0',
608
+ id: 1,
609
+ method: 'getBalance',
610
+ params: {
611
+ address: address
612
+ }
613
+ })
614
+ });
615
+ if (!response.ok) throw new Error(`RPC error: ${response.status}`);
616
+ const data = await response.json();
617
+ if (data.error) throw new Error(`RPC error: ${data.error.message}`);
618
+ const balanceNanoTon = parseInt(data.result);
619
+ const balanceTon = balanceNanoTon / 1e9;
620
+ return balanceTon.toFixed(4);
621
+ } catch (error) {
622
+ console.error('Error fetching TON balance:', error);
623
+ document.getElementById('ton-error').textContent = `Ошибка получения баланса: ${error.message}`;
624
+ document.getElementById('ton-error').style.display = 'block';
625
+ return 'N/A';
626
+ }
627
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
 
629
 
630
  function setupTelegram() {
 
672
  greetingElement.textContent = `Добро пожаловать, ${name}! 👋`;
673
  } else {
674
  greetingElement.textContent = 'Добро пожаловать!';
 
675
  }
676
 
677
  const contactButtons = document.querySelectorAll('.contact-link');
 
704
  modal.style.display = "none";
705
  }
706
  });
 
 
707
  }
 
 
708
 
709
  document.body.style.visibility = 'visible';
710
+
711
+ // --- TON Connect Integration ---
712
+ const connectTonBtn = document.getElementById('connect-ton-btn');
713
+ const disconnectTonBtn = document.getElementById('disconnect-ton-btn');
714
+ const tonWalletStatus = document.getElementById('ton-wallet-status');
715
+ const tonWalletAddress = document.getElementById('ton-wallet-address');
716
+ const tonBalance = document.getElementById('ton-balance');
717
+ const tonInfoDiv = document.getElementById('ton-info');
718
+ const tonErrorDiv = document.getElementById('ton-error');
719
+
720
+ if (connectTonBtn && disconnectTonBtn && tonWalletStatus && tonWalletAddress && tonBalance && tonInfoDiv && tonErrorDiv) {
721
+ try {
722
+ tonConnectSDK = new TonConnectSDK.TonConnectSDK({
723
+ manifestUrl: window.location.origin + '/tonconnect-manifest.json',
724
+ actionsConfiguration: {
725
+ twaReturnUrl: 'https://t.me/YOUR_BOT_USERNAME/YOUR_MINIAPP_SHORTNAME' // Replace with your bot/app details
726
+ }
727
+ });
728
+
729
+ tonConnectSDK.onStatusChange(async wallet => {
730
+ tonErrorDiv.style.display = 'none';
731
+ if (wallet) {
732
+ tonWalletStatus.textContent = 'Статус: Подключен ✅';
733
+ tonWalletAddress.textContent = wallet.account.address;
734
+ tonInfoDiv.style.display = 'block';
735
+ connectTonBtn.style.display = 'none';
736
+ disconnectTonBtn.style.display = 'inline-flex';
737
+ tonBalance.textContent = 'Загрузка...';
738
+ const balance = await fetchTonBalance(wallet.account.address);
739
+ tonBalance.textContent = balance;
740
+ if (tg.HapticFeedback) {
741
+ tg.HapticFeedback.notificationOccurred('success');
742
+ }
743
+ } else {
744
+ tonWalletStatus.textContent = 'Статус: Не подключен';
745
+ tonWalletAddress.textContent = '';
746
+ tonBalance.textContent = '';
747
+ tonInfoDiv.style.display = 'none';
748
+ connectTonBtn.style.display = 'inline-flex';
749
+ disconnectTonBtn.style.display = 'none';
750
+ if (tg.HapticFeedback) {
751
+ tg.HapticFeedback.notificationOccurred('warning');
752
+ }
753
+ }
754
+ tg.MainButton.hide(); // Hide main button if shown by SDK
755
+ });
756
+
757
+ connectTonBtn.addEventListener('click', () => {
758
+ tonConnectSDK.connectWallet();
759
+ });
760
+
761
+ disconnectTonBtn.addEventListener('click', () => {
762
+ tonConnectSDK.disconnect();
763
+ });
764
+
765
+ tonConnectSDK.restoreConnection(); // Attempt to restore previous connection
766
+
767
+ } catch (e) {
768
+ console.error("Error initializing TON Connect SDK:", e);
769
+ tonErrorDiv.textContent = `Ошибка инициализации TON Connect: ${e.message}`;
770
+ tonErrorDiv.style.display = 'block';
771
+ connectTonBtn.disabled = true;
772
+ }
773
+ } else {
774
+ console.error("TON Wallet section elements not found!");
775
+ }
776
+ // --- End TON Connect Integration ---
777
+
778
  }
779
 
780
  if (window.Telegram && window.Telegram.WebApp) {
781
  setupTelegram();
782
  } else {
 
783
  window.addEventListener('load', setupTelegram);
784
  setTimeout(() => {
785
  if (document.body.style.visibility !== 'visible') {
 
786
  const greetingElement = document.getElementById('greeting');
787
  if(greetingElement) greetingElement.textContent = 'Ошибка загрузки интерфейса Telegram.';
788
  document.body.style.visibility = 'visible';
 
885
  transition: background-color 0.2s ease;
886
  }
887
  .refresh-btn:hover { background-color: #0b5ed7; }
888
+
889
  .admin-controls {
890
  background: var(--admin-card-bg);
891
  padding: var(--padding);
 
984
  } catch (error) {
985
  statusMessage.textContent = `Ошибка ${action}: ${error.message}`;
986
  statusMessage.style.color = 'var(--admin-danger)';
 
987
  } finally {
988
  loader.style.display = 'none';
989
  }
 
1053
  logging.exception("Error in /verify endpoint")
1054
  return jsonify({"status": "error", "message": "Internal server error"}), 500
1055
 
1056
+ @app.route('/tonconnect-manifest.json')
1057
+ def tonconnect_manifest():
1058
+ manifest = {
1059
+ "url": request.url_root.rstrip('/') + "/tonconnect-manifest.json",
1060
+ "name": "Morshen Group Mini App",
1061
+ "iconUrl": request.url_root.rstrip('/') + "/static/morshengroup.png", # You need to serve your icon
1062
+ "termsOfUseUrl": "https://example.com/terms", # Replace with your terms URL
1063
+ "privacyPolicyUrl": "https://example.com/privacy" # Replace with your privacy URL
1064
+ }
1065
+ return jsonify(manifest)
1066
+
1067
  @app.route('/admin')
1068
  def admin_panel():
1069
  current_data = load_visitor_data()
 
1085
  upload_data_to_hf_async()
1086
  return jsonify({"status": "ok", "message": "Загрузка данных на Hugging Face запущена в фоновом режиме."})
1087
 
1088
+ # Basic route to serve static files (like the icon)
1089
+ @app.route('/static/<path:filename>')
1090
+ def static_files(filename):
1091
+ # You would put your morshengroup.png in a 'static' folder
1092
+ # e.g., /static/morshengroup.png
1093
+ # For this example, we'll serve a placeholder or you'll need to add the static folder
1094
+ # and place your icon there.
1095
+ # A simple placeholder response:
1096
+ if filename == 'morshengroup.png':
1097
+ return Response(open('morshengroup.jpg', 'rb').read(), mimetype='image/jpeg') # Assuming morshengroup.jpg exists locally
1098
+ return Response("File not found", status=404)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
 
1101
+ if __name__ == '__main__':
1102
+ download_data_from_hf()
1103
  load_visitor_data()
1104
 
 
 
 
 
 
 
1105
  if HF_TOKEN_WRITE:
1106
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
1107
  backup_thread.start()
 
 
 
1108
 
 
1109
  app.run(host=HOST, port=PORT, debug=False)