Eluza133 commited on
Commit
ed181ab
·
verified ·
1 Parent(s): b4d81d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -124
app.py CHANGED
@@ -1,5 +1,3 @@
1
-
2
-
3
  import flask
4
  from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify, Response
5
  from flask_caching import Cache
@@ -15,6 +13,9 @@ import requests
15
  from io import BytesIO
16
  import uuid
17
  from functools import wraps
 
 
 
18
 
19
  app = Flask(__name__)
20
  app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey_folders_unique_tma")
@@ -22,9 +23,8 @@ DATA_FILE = 'cloudeng_data_tma.json'
22
  REPO_ID = "Eluza133/Z1e1u"
23
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
24
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE
25
- TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "6750208873:AAE2hvPlS99dBdhGa_Brre0IIpUdOvXxHt4")
26
  ADMIN_TELEGRAM_ID = os.getenv("ADMIN_TELEGRAM_ID", "YOUR_ADMIN_TELEGRAM_USER_ID_HERE")
27
- BOT_USERNAME = os.getenv("BOT_USERNAME", "ZeusCloudBot")
28
 
29
  ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin")
30
  ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "zeusadminpass")
@@ -37,6 +37,9 @@ logging.basicConfig(level=logging.INFO)
37
 
38
  DEFAULT_STORAGE_LIMIT_GB = 10
39
  BYTES_IN_GB = 1024**3
 
 
 
40
 
41
  BASE_STYLE = '''
42
  :root {
@@ -177,10 +180,8 @@ def calculate_current_storage_usage(filesystem_node):
177
  total_size = 0
178
  if not filesystem_node:
179
  return 0
180
-
181
  if filesystem_node.get('type') == 'file':
182
  return filesystem_node.get('size_bytes', 0)
183
-
184
  if filesystem_node.get('type') == 'folder' and 'children' in filesystem_node:
185
  for child in filesystem_node['children']:
186
  total_size += calculate_current_storage_usage(child)
@@ -191,11 +192,9 @@ def initialize_user_filesystem_tma(user_data, tma_user_id_str):
191
  user_data['filesystem'] = {
192
  "type": "folder", "id": "root", "name": "root", "children": []
193
  }
194
-
195
  user_data.setdefault('storage_limit_gb', DEFAULT_STORAGE_LIMIT_GB)
196
  user_data.setdefault('unlimited_storage', False)
197
  user_data.setdefault('storage_used_bytes', 0)
198
-
199
  if 'files' in user_data and isinstance(user_data['files'], list):
200
  total_migrated_size = 0
201
  for old_file in user_data['files']:
@@ -204,13 +203,10 @@ def initialize_user_filesystem_tma(user_data, tma_user_id_str):
204
  name_part, ext_part = os.path.splitext(original_filename)
205
  unique_suffix = uuid.uuid4().hex[:8]
206
  unique_filename = f"{name_part}_{unique_suffix}{ext_part}"
207
-
208
  estimated_size = 0
209
-
210
  existing_path = old_file.get('path')
211
  if not existing_path:
212
  existing_path = f"cloud_files/{tma_user_id_str}/root/{unique_filename}"
213
-
214
  file_node = {
215
  'type': 'file', 'id': file_id, 'original_filename': original_filename,
216
  'unique_filename': unique_filename, 'path': existing_path,
@@ -232,24 +228,25 @@ def load_data():
232
  if not isinstance(data, dict):
233
  data = {'users': {}}
234
  data.setdefault('users', {})
235
-
236
  for tma_user_id_str, user_data_item in data['users'].items():
237
  user_data_item.setdefault('storage_limit_gb', DEFAULT_STORAGE_LIMIT_GB)
238
  user_data_item.setdefault('unlimited_storage', False)
239
  initialize_user_filesystem_tma(user_data_item, tma_user_id_str)
240
  user_data_item['storage_used_bytes'] = calculate_current_storage_usage(user_data_item['filesystem'])
241
-
242
  return data
243
  except Exception as e:
 
244
  return {'users': {}}
245
 
246
  def save_data(data):
247
  try:
248
- with open(DATA_FILE, 'w', encoding='utf-8') as file:
249
- json.dump(data, file, ensure_ascii=False, indent=4)
250
- upload_db_to_hf()
251
- cache.clear()
 
252
  except Exception as e:
 
253
  raise
254
 
255
  def upload_db_to_hf():
@@ -262,7 +259,7 @@ def upload_db_to_hf():
262
  commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
263
  )
264
  except Exception as e:
265
- pass
266
 
267
  def download_db_from_hf():
268
  if not HF_TOKEN_READ:
@@ -278,6 +275,7 @@ def download_db_from_hf():
278
  if not os.path.exists(DATA_FILE):
279
  with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
280
  except Exception as e:
 
281
  if not os.path.exists(DATA_FILE):
282
  with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
283
 
@@ -328,7 +326,6 @@ TMA_ENTRY_HTML = '''
328
  window.Telegram.WebApp.ready();
329
  const initData = window.Telegram.WebApp.initData;
330
  const initDataUnsafe = window.Telegram.WebApp.initDataUnsafe;
331
-
332
  if (initDataUnsafe && initDataUnsafe.user) {
333
  fetch("{{ url_for('auth_via_telegram') }}", {
334
  method: 'POST',
@@ -363,19 +360,15 @@ def tma_entry_page():
363
  def root_redirect():
364
  return redirect(url_for('tma_entry_page'))
365
 
366
-
367
  @app.route('/auth_via_telegram', methods=['POST'])
368
  def auth_via_telegram():
369
  try:
370
  payload = request.json
371
  tg_user_data = payload.get('user')
372
-
373
  if not tg_user_data or not tg_user_data.get('id'):
374
  return jsonify({'status': 'error', 'message': 'Отсутствуют данные пользователя Telegram.'}), 400
375
-
376
  tma_user_id_str = str(tg_user_data['id'])
377
  data = load_data()
378
-
379
  if tma_user_id_str not in data['users']:
380
  data['users'][tma_user_id_str] = {
381
  'telegram_id': tg_user_data['id'],
@@ -389,15 +382,14 @@ def auth_via_telegram():
389
  try:
390
  save_data(data)
391
  except Exception as e:
 
392
  return jsonify({'status': 'error', 'message': 'Ошибка сохранения данных нового пользователя.'}), 500
393
-
394
  session['telegram_user_id'] = tma_user_id_str
395
  display_name = tg_user_data.get('first_name') or tg_user_data.get('username') or f"User {tma_user_id_str}"
396
  session['telegram_display_name'] = display_name
397
-
398
  return jsonify({'status': 'success', 'redirect_url': url_for('tma_dashboard')})
399
-
400
  except Exception as e:
 
401
  return jsonify({'status': 'error', 'message': 'Внутренняя ошибка сервера при авторизации.'}), 500
402
 
403
  TMA_DASHBOARD_HTML_TEMPLATE = '''
@@ -414,7 +406,7 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
414
  {% endif %}
415
  </p>
416
  {% if not user_data.unlimited_storage %}
417
- <button type="button" class="btn" style="background: var(--secondary); margin-left: 0; margin-bottom: 10px;" onclick="buyUnlimitedStorage('{{ tma_user_id }}', '{{ bot_username }}')">Купить безлимит за 2000 ⭐️</button>
418
  {% endif %}
419
  {% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}
420
  {% for category, message in messages %}<div class="flash {{ category }}">{{ message }}</div>{% endfor %}
@@ -515,8 +507,10 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
515
 
516
  function tmaDownloadFile(downloadUrl, filename) {
517
  if (window.Telegram && window.Telegram.WebApp && Telegram.WebApp.openLink) {
 
518
  Telegram.WebApp.openLink(downloadUrl);
519
  } else {
 
520
  const link = document.createElement('a');
521
  link.href = downloadUrl;
522
  link.setAttribute('download', filename);
@@ -551,14 +545,35 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
551
  }
552
  document.getElementById('logout-btn').addEventListener('click', function(e) { e.preventDefault(); window.location.href = "{{ url_for('tma_logout') }}"; });
553
 
554
- function buyUnlimitedStorage(userId, botUsername) {
555
- if (!Telegram.WebApp.openLink) {
556
- Telegram.WebApp.showAlert('Telegram Web App Link opening is not available.');
557
- return;
558
- }
559
- const deepLink = `https://t.me/${botUsername}?start=buy_unlimited_stars_${userId}`;
560
- Telegram.WebApp.openLink(deepLink);
561
- Telegram.WebApp.showAlert('Вас перенаправит в чат с ботом для оплаты. После оплаты свяжитесь с администратором для активации безлимитного хранилища.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  }
563
  </script></body></html>
564
  '''
@@ -568,22 +583,17 @@ def tma_dashboard():
568
  if 'telegram_user_id' not in session:
569
  flash('Пожалуйста, авторизуйтесь через Telegram.', 'error')
570
  return redirect(url_for('tma_entry_page'))
571
-
572
  tma_user_id = session['telegram_user_id']
573
  display_name = session.get('telegram_display_name', 'Пользователь')
574
  data = load_data()
575
-
576
  if tma_user_id not in data['users']:
577
  session.clear()
578
  flash('Пользователь не найден в системе. Пожалуйста, перезапустите приложение.', 'error')
579
  return redirect(url_for('tma_entry_page'))
580
-
581
  user_data = data['users'][tma_user_id]
582
  initialize_user_filesystem_tma(user_data, tma_user_id)
583
-
584
  current_folder_id = request.args.get('folder_id', 'root')
585
  current_folder, _ = find_node_by_id(user_data['filesystem'], current_folder_id)
586
-
587
  if not current_folder or current_folder.get('type') != 'folder':
588
  flash('Папка не найдена!', 'error')
589
  current_folder_id = 'root'
@@ -592,14 +602,11 @@ def tma_dashboard():
592
  flash('Критическая ошибка: корневая папка не найдена.', 'error')
593
  session.clear()
594
  return redirect(url_for('tma_entry_page'))
595
-
596
  items_in_folder = sorted(current_folder.get('children', []), key=lambda x: (x['type'] != 'folder', x.get('name', x.get('original_filename', '')).lower()))
597
-
598
  if request.method == 'POST':
599
  if not HF_TOKEN_WRITE:
600
  flash('Загрузка невозможна: токен для записи не настроен.', 'error')
601
  return redirect(url_for('tma_dashboard', folder_id=current_folder_id))
602
-
603
  files = request.files.getlist('files')
604
  if not files or all(not f.filename for f in files):
605
  flash('Файлы для загрузки не выбраны.', 'error')
@@ -607,13 +614,11 @@ def tma_dashboard():
607
  if len(files) > 20:
608
  flash('Максимум 20 файлов за раз!', 'error')
609
  return redirect(url_for('tma_dashboard', folder_id=current_folder_id))
610
-
611
  target_folder_id = request.form.get('current_folder_id', 'root')
612
  target_folder_node, _ = find_node_by_id(user_data['filesystem'], target_folder_id)
613
  if not target_folder_node or target_folder_node.get('type') != 'folder':
614
  flash('Целевая папка для загрузки не найдена!', 'error')
615
  return redirect(url_for('tma_dashboard'))
616
-
617
  if not user_data['unlimited_storage']:
618
  total_new_files_size = 0
619
  for file_obj in files:
@@ -621,12 +626,10 @@ def tma_dashboard():
621
  file_obj.seek(0, os.SEEK_END)
622
  total_new_files_size += file_obj.tell()
623
  file_obj.seek(0)
624
-
625
  if user_data['storage_used_bytes'] + total_new_files_size > user_data['storage_limit_gb'] * BYTES_IN_GB:
626
  flash(f'Недостаточно места! Ваш лимит {user_data["storage_limit_gb"]} ГБ, использовано {format_bytes(user_data["storage_used_bytes"])}. '
627
  f'Эти файлы слишком большие ({format_bytes(total_new_files_size)}).', 'error')
628
  return redirect(url_for('tma_dashboard', folder_id=current_folder_id))
629
-
630
  api = HfApi()
631
  uploaded_count = 0
632
  errors_list = []
@@ -639,10 +642,8 @@ def tma_dashboard():
639
  file_id = uuid.uuid4().hex
640
  hf_path = f"cloud_files/{tma_user_id}/{target_folder_id}/{unique_filename}"
641
  temp_path = os.path.join(UPLOAD_FOLDER, f"{file_id}_{unique_filename}")
642
-
643
  file_obj.save(temp_path)
644
  file_size = os.path.getsize(temp_path)
645
-
646
  try:
647
  api.upload_file(
648
  path_or_fileobj=temp_path, path_in_repo=hf_path, repo_id=REPO_ID,
@@ -662,7 +663,7 @@ def tma_dashboard():
662
  else:
663
  errors_list.append(f"Ошибка добавления метаданных для {original_filename}.")
664
  try: api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE)
665
- except Exception as del_err: pass
666
  except Exception as e:
667
  errors_list.append(f"Ошибка загрузки файла {original_filename}: {e}")
668
  finally:
@@ -687,38 +688,40 @@ def tma_dashboard():
687
  if not parent_node_bc: break
688
  temp_id = parent_node_bc.get('id')
689
  breadcrumbs.reverse()
690
-
691
  storage_used_formatted = format_bytes(user_data['storage_used_bytes'])
692
-
693
  return render_template_string(TMA_DASHBOARD_HTML_TEMPLATE,
694
  display_name=display_name, items=items_in_folder,
695
  current_folder_id=current_folder_id, current_folder=current_folder,
696
  breadcrumbs=breadcrumbs, repo_id_js=REPO_ID, HF_TOKEN_READ_js=HF_TOKEN_READ,
697
  hf_file_url_jinja=lambda path, download=False: f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{path}{'?download=true' if download else ''}",
698
  is_tma_user_admin_flag=is_admin_tma(),
699
- user_data=user_data, storage_used_formatted=storage_used_formatted,
700
- tma_user_id=tma_user_id, bot_username=BOT_USERNAME)
701
-
702
 
703
- @app.route('/tma_buy_unlimited', methods=['POST'])
704
- def tma_buy_unlimited():
705
  if 'telegram_user_id' not in session:
706
- flash('Пожалуйста, авторизуйтесь.', 'error')
707
- return redirect(url_for('tma_entry_page'))
708
-
709
  tma_user_id = session['telegram_user_id']
710
  data = load_data()
711
  user_data = data['users'].get(tma_user_id)
712
-
713
- if not user_data:
714
- session.clear()
715
- flash('Пользователь не найден.', 'error')
716
- return redirect(url_for('tma_entry_page'))
717
 
718
- flash('Для получения безлимитного хранилища оплатите 2000 Telegram Stars через бота. После оплаты свяжитесь с администратором для активации.', 'info')
719
-
720
- return redirect(url_for('tma_dashboard'))
721
-
 
 
 
 
 
 
 
 
 
 
 
722
 
723
  @app.route('/create_folder_tma', methods=['POST'])
724
  def create_folder_tma():
@@ -731,13 +734,11 @@ def create_folder_tma():
731
  if not user_data:
732
  flash('Пользователь не найден', 'error')
733
  return redirect(url_for('tma_entry_page'))
734
-
735
  parent_folder_id = request.form.get('parent_folder_id', 'root')
736
  folder_name = request.form.get('folder_name', '').strip()
737
  if not folder_name:
738
  flash('Имя папки не может быть пустым!', 'error')
739
  return redirect(url_for('tma_dashboard', folder_id=parent_folder_id))
740
-
741
  folder_id = uuid.uuid4().hex
742
  folder_data = {'type': 'folder', 'id': folder_id, 'name': folder_name, 'children': []}
743
  if add_node(user_data['filesystem'], parent_folder_id, folder_data):
@@ -753,10 +754,8 @@ def create_folder_tma():
753
  def download_tma(file_id):
754
  current_tma_user_id = session.get('telegram_user_id')
755
  is_browser_admin_session = session.get('admin_browser_logged_in', False)
756
-
757
  data = load_data()
758
  file_node = None
759
-
760
  if is_browser_admin_session:
761
  for uid_str_iter, udata_iter in data.get('users', {}).items():
762
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
@@ -767,7 +766,6 @@ def download_tma(file_id):
767
  user_data = data['users'].get(current_tma_user_id)
768
  if user_data:
769
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
770
-
771
  if not file_node and is_admin_tma():
772
  for uid_str_iter, udata_iter in data.get('users', {}).items():
773
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
@@ -780,37 +778,27 @@ def download_tma(file_id):
780
  return redirect(url_for('tma_entry_page'))
781
  else:
782
  return Response("Unauthorized", status=401, mimetype='text/plain')
783
-
784
-
785
  redirect_url_fallback = url_for('tma_dashboard')
786
  if is_browser_admin_session:
787
  redirect_url_fallback = url_for('admin_panel')
788
-
789
  redirect_url = request.referrer or redirect_url_fallback
790
-
791
-
792
  if not file_node or file_node.get('type') != 'file':
793
  flash('Файл не найден или доступ запрещен!', 'error')
794
  return redirect(redirect_url)
795
-
796
  hf_path = file_node.get('path')
797
  original_filename = file_node.get('original_filename', 'downloaded_file')
798
  if not hf_path:
799
  flash('Ошибка: Путь к файлу не найден.', 'error')
800
  return redirect(redirect_url)
801
-
802
  file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
803
  try:
804
  req_headers = {}
805
  if HF_TOKEN_READ: req_headers["authorization"] = f"Bearer {HF_TOKEN_READ}"
806
-
807
  r = requests.get(file_url, headers=req_headers, stream=True)
808
  r.raise_for_status()
809
-
810
  def generate_chunks():
811
  for chunk in r.iter_content(chunk_size=8192):
812
  yield chunk
813
-
814
  mimetype = 'application/octet-stream'
815
  if '.' in original_filename:
816
  ext = original_filename.rsplit('.', 1)[1].lower()
@@ -821,52 +809,45 @@ def download_tma(file_id):
821
  'zip': 'application/zip', 'rar': 'application/x-rar-compressed',
822
  }
823
  mimetype = content_types.get(ext, 'application/octet-stream')
824
-
825
  resp = Response(generate_chunks(), mimetype=mimetype)
826
  resp.headers['Content-Disposition'] = f'attachment; filename="{original_filename}"'
827
  resp.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
828
  resp.headers['Pragma'] = 'no-cache'
829
  resp.headers['Expires'] = '0'
830
  return resp
831
-
832
  except requests.exceptions.RequestException as e:
 
833
  flash(f'Ошибка скачивания файла: {e}', 'error')
834
  except Exception as e:
 
835
  flash(f'Внутренняя ошибка при скачивании: {e}', 'error')
836
  return redirect(redirect_url)
837
 
838
-
839
  @app.route('/delete_file_tma/<file_id>', methods=['POST'])
840
  def delete_file_tma():
841
  if 'telegram_user_id' not in session:
842
  flash('Пожалуйста, авторизуйтесь.', 'error')
843
  return redirect(url_for('tma_entry_page'))
844
-
845
  file_id = request.view_args.get('file_id')
846
-
847
  tma_user_id = session['telegram_user_id']
848
  data = load_data()
849
  user_data = data['users'].get(tma_user_id)
850
  if not user_data:
851
  session.clear(); flash('Пользователь не найден.', 'error'); return redirect(url_for('tma_entry_page'))
852
-
853
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
854
  current_view_folder_id = request.form.get('current_view_folder_id', 'root')
855
  if not file_node or file_node.get('type') != 'file':
856
  flash('Файл не найден.', 'error')
857
  return redirect(url_for('tma_dashboard', folder_id=current_view_folder_id))
858
-
859
  hf_path = file_node.get('path')
860
  original_filename = file_node.get('original_filename', 'файл')
861
  file_size_to_deduct = file_node.get('size_bytes', 0)
862
-
863
  if not hf_path:
864
  if remove_node(user_data['filesystem'], file_id):
865
  user_data['storage_used_bytes'] -= file_size_to_deduct
866
  try: save_data(data); flash(f'Метаданные файла {original_filename} удалены (путь отсутствовал).')
867
  except Exception as e: flash('Ошибка сохранения данных после удаления метаданных.', 'error')
868
  return redirect(url_for('tma_dashboard', folder_id=current_view_folder_id))
869
-
870
  if not HF_TOKEN_WRITE:
871
  flash('Удаление невозможно: токен для записи не настроен.', 'error')
872
  return redirect(url_for('tma_dashboard', folder_id=current_view_folder_id))
@@ -898,7 +879,6 @@ def delete_folder_tma(folder_id):
898
  user_data = data['users'].get(tma_user_id)
899
  if not user_data:
900
  session.clear(); flash('Пользователь не найден.', 'error'); return redirect(url_for('tma_entry_page'))
901
-
902
  folder_node, parent_node = find_node_by_id(user_data['filesystem'], folder_id)
903
  current_view_folder_id = request.form.get('current_view_folder_id', 'root')
904
  if not folder_node or folder_node.get('type') != 'folder' or not parent_node:
@@ -917,10 +897,8 @@ def delete_folder_tma(folder_id):
917
  def get_text_content_tma(file_id):
918
  current_tma_user_id = session.get('telegram_user_id')
919
  is_browser_admin_session = session.get('admin_browser_logged_in', False)
920
-
921
  data = load_data()
922
  file_node = None
923
-
924
  if is_browser_admin_session:
925
  for uid_str, udata_iter in data.get('users', {}).items():
926
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
@@ -930,7 +908,6 @@ def get_text_content_tma(file_id):
930
  user_data = data['users'].get(current_tma_user_id)
931
  if user_data:
932
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
933
-
934
  if not file_node and is_admin_tma():
935
  for uid_str, udata_iter in data.get('users', {}).items():
936
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
@@ -938,7 +915,6 @@ def get_text_content_tma(file_id):
938
  file_node = node; break
939
  else:
940
  return Response("Не авторизован", status=401)
941
-
942
  if not file_node or file_node.get('type') != 'file' or file_node.get('file_type') != 'text':
943
  return Response("Текстовый файл не найден", status=404)
944
  hf_path = file_node.get('path')
@@ -962,7 +938,6 @@ def tma_logout():
962
  flash('Вы вышли из сессии приложения.')
963
  return redirect(url_for('tma_entry_page'))
964
 
965
-
966
  ADMIN_LOGIN_HTML_TEMPLATE = '''
967
  <!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
968
  <title>Admin Login</title><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
@@ -1000,7 +975,6 @@ def admin_login():
1000
  username = request.form.get('username')
1001
  password = request.form.get('password')
1002
  next_url = request.form.get('next')
1003
-
1004
  if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
1005
  session['admin_browser_logged_in'] = True
1006
  flash('Успешный вход в админ-панель.', 'success')
@@ -1138,7 +1112,6 @@ def admin_user_files(tma_user_id_str):
1138
  user_data = data.get('users', {}).get(tma_user_id_str)
1139
  if not user_data:
1140
  flash(f'Пользователь ID {tma_user_id_str} не найден.', 'error'); return redirect(url_for('admin_panel'))
1141
-
1142
  all_files = []
1143
  def collect_files_admin(folder, current_path_id='root'):
1144
  parent_path_str = get_node_path_string(user_data['filesystem'], current_path_id)
@@ -1148,13 +1121,10 @@ def admin_user_files(tma_user_id_str):
1148
  item['size_formatted'] = format_bytes(item.get('size_bytes'))
1149
  all_files.append(item)
1150
  elif item.get('type') == 'folder': collect_files_admin(item, item.get('id'))
1151
-
1152
  collect_files_admin(user_data.get('filesystem', {}))
1153
  all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
1154
-
1155
  display_name_admin_view = user_data.get('first_name', user_data.get('telegram_username', f"User {tma_user_id_str}"))
1156
  storage_used_formatted_admin_view = format_bytes(user_data['storage_used_bytes'])
1157
-
1158
  return render_template_string(ADMIN_USER_FILES_HTML_TEMPLATE,
1159
  tma_user_id_str_admin_view=tma_user_id_str, display_name_admin_view=display_name_admin_view, files=all_files,
1160
  repo_id_js_admin=REPO_ID,
@@ -1166,11 +1136,9 @@ def admin_user_files(tma_user_id_str):
1166
  def admin_toggle_unlimited_storage(tma_user_id_str):
1167
  data = load_data()
1168
  user_data = data.get('users', {}).get(tma_user_id_str)
1169
-
1170
  if not user_data:
1171
  flash(f'Пользователь {tma_user_id_str} не найден.', 'error')
1172
  return redirect(url_for('admin_panel'))
1173
-
1174
  user_data['unlimited_storage'] = not user_data.get('unlimited_storage', False)
1175
  try:
1176
  save_data(data)
@@ -1178,7 +1146,6 @@ def admin_toggle_unlimited_storage(tma_user_id_str):
1178
  flash(f'Безлимитное хранилище для пользователя {tma_user_id_str} {status}.', 'success')
1179
  except Exception as e:
1180
  flash(f'Ошибка при изменении статуса безлимитного хранилища: {e}', 'error')
1181
-
1182
  return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str))
1183
 
1184
  @app.route('/admhosto/delete_user/<tma_user_id_str>', methods=['POST'])
@@ -1196,9 +1163,9 @@ def admin_delete_user(tma_user_id_str):
1196
  except hf_utils.HfHubHTTPError as e:
1197
  if e.response.status_code != 404:
1198
  flash(f'Ошибка удаления файлов пользователя {tma_user_id_str} с сервера: {e}. Пользователь из базы не удален.', 'error'); return redirect(url_for('admin_panel'))
 
1199
  except Exception as e:
1200
  flash(f'Ошибка удаления файлов пользователя {tma_user_id_str} с сервера: {e}. Пользователь из базы не ��дален.', 'error'); return redirect(url_for('admin_panel'))
1201
-
1202
  try:
1203
  del data['users'][tma_user_id_str]
1204
  save_data(data)
@@ -1207,7 +1174,6 @@ def admin_delete_user(tma_user_id_str):
1207
  flash(f'Файлы на сервере могли быть удалены, но произошла ошибка удаления пользователя из базы данных: {e}', 'error')
1208
  return redirect(url_for('admin_panel'))
1209
 
1210
-
1211
  @app.route('/admhosto/delete_file/<tma_user_id_str_form>/<file_id>', methods=['POST'])
1212
  @admin_browser_login_required
1213
  def admin_delete_file(tma_user_id_str_form, file_id):
@@ -1217,22 +1183,18 @@ def admin_delete_file(tma_user_id_str_form, file_id):
1217
  user_data = data.get('users', {}).get(tma_user_id_str_form)
1218
  if not user_data:
1219
  flash(f'Пользователь {tma_user_id_str_form} не найден.', 'error'); return redirect(url_for('admin_panel'))
1220
-
1221
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
1222
  if not file_node or file_node.get('type') != 'file':
1223
  flash('Файл не найден в базе данных этого пользователя.', 'error'); return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str_form))
1224
-
1225
  hf_path = file_node.get('path')
1226
  original_filename = file_node.get('original_filename', 'файл')
1227
  file_size_to_deduct = file_node.get('size_bytes', 0)
1228
-
1229
  if not hf_path:
1230
  if remove_node(user_data['filesystem'], file_id):
1231
  user_data['storage_used_bytes'] -= file_size_to_deduct
1232
  try: save_data(data); flash(f'Метаданные файла {original_filename} удалены из базы (путь к файлу на сервере отсутствовал).')
1233
  except Exception as e: flash('Ошибка сохранения данных после удаления метаданных (путь отсутствовал).', 'error')
1234
  return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str_form))
1235
-
1236
  try:
1237
  api = HfApi()
1238
  api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE)
@@ -1253,19 +1215,64 @@ def admin_delete_file(tma_user_id_str_form, file_id):
1253
  return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str_form))
1254
 
1255
 
1256
- if __name__ == '__main__':
1257
- if os.getenv("HF_TOKEN") is None:
1258
- pass
1259
- if os.getenv("HF_TOKEN_READ") is None:
1260
- pass
1261
- if os.getenv("ADMIN_TELEGRAM_ID") == "YOUR_ADMIN_TELEGRAM_USER_ID_HERE":
1262
- pass
1263
- if os.getenv("ADMIN_USERNAME") == "admin" and os.getenv("ADMIN_PASSWORD") == "zeusadminpass":
1264
- pass
1265
- if os.getenv("BOT_USERNAME") == "ZeusCloudBot":
1266
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1267
 
1268
 
 
 
 
 
 
 
 
1269
  if HF_TOKEN_WRITE:
1270
  download_db_from_hf()
1271
  threading.Thread(target=periodic_backup, daemon=True).start()
@@ -1274,5 +1281,8 @@ if __name__ == '__main__':
1274
  else:
1275
  if not os.path.exists(DATA_FILE):
1276
  with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
 
 
 
1277
 
1278
  app.run(debug=False, host='0.0.0.0', port=7860)
 
 
 
1
  import flask
2
  from flask import Flask, render_template_string, request, redirect, url_for, session, flash, send_file, jsonify, Response
3
  from flask_caching import Cache
 
13
  from io import BytesIO
14
  import uuid
15
  from functools import wraps
16
+ import telegram
17
+ from telegram import Update
18
+ from telegram.ext import Application, CommandHandler, MessageHandler, filters, PreCheckoutQueryHandler, ContextTypes
19
 
20
  app = Flask(__name__)
21
  app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey_folders_unique_tma")
 
23
  REPO_ID = "Eluza133/Z1e1u"
24
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
25
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE
26
+ TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "6750208873:AAE2hvPlJ99dBdhGa_Brre0IIpUdOvXxHt4")
27
  ADMIN_TELEGRAM_ID = os.getenv("ADMIN_TELEGRAM_ID", "YOUR_ADMIN_TELEGRAM_USER_ID_HERE")
 
28
 
29
  ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin")
30
  ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "zeusadminpass")
 
37
 
38
  DEFAULT_STORAGE_LIMIT_GB = 10
39
  BYTES_IN_GB = 1024**3
40
+ UNLIMITED_STORAGE_PRICE_STARS = 2000
41
+
42
+ data_lock = threading.Lock()
43
 
44
  BASE_STYLE = '''
45
  :root {
 
180
  total_size = 0
181
  if not filesystem_node:
182
  return 0
 
183
  if filesystem_node.get('type') == 'file':
184
  return filesystem_node.get('size_bytes', 0)
 
185
  if filesystem_node.get('type') == 'folder' and 'children' in filesystem_node:
186
  for child in filesystem_node['children']:
187
  total_size += calculate_current_storage_usage(child)
 
192
  user_data['filesystem'] = {
193
  "type": "folder", "id": "root", "name": "root", "children": []
194
  }
 
195
  user_data.setdefault('storage_limit_gb', DEFAULT_STORAGE_LIMIT_GB)
196
  user_data.setdefault('unlimited_storage', False)
197
  user_data.setdefault('storage_used_bytes', 0)
 
198
  if 'files' in user_data and isinstance(user_data['files'], list):
199
  total_migrated_size = 0
200
  for old_file in user_data['files']:
 
203
  name_part, ext_part = os.path.splitext(original_filename)
204
  unique_suffix = uuid.uuid4().hex[:8]
205
  unique_filename = f"{name_part}_{unique_suffix}{ext_part}"
 
206
  estimated_size = 0
 
207
  existing_path = old_file.get('path')
208
  if not existing_path:
209
  existing_path = f"cloud_files/{tma_user_id_str}/root/{unique_filename}"
 
210
  file_node = {
211
  'type': 'file', 'id': file_id, 'original_filename': original_filename,
212
  'unique_filename': unique_filename, 'path': existing_path,
 
228
  if not isinstance(data, dict):
229
  data = {'users': {}}
230
  data.setdefault('users', {})
 
231
  for tma_user_id_str, user_data_item in data['users'].items():
232
  user_data_item.setdefault('storage_limit_gb', DEFAULT_STORAGE_LIMIT_GB)
233
  user_data_item.setdefault('unlimited_storage', False)
234
  initialize_user_filesystem_tma(user_data_item, tma_user_id_str)
235
  user_data_item['storage_used_bytes'] = calculate_current_storage_usage(user_data_item['filesystem'])
 
236
  return data
237
  except Exception as e:
238
+ logging.error(f"Error loading data: {e}")
239
  return {'users': {}}
240
 
241
  def save_data(data):
242
  try:
243
+ with data_lock:
244
+ with open(DATA_FILE, 'w', encoding='utf-8') as file:
245
+ json.dump(data, file, ensure_ascii=False, indent=4)
246
+ upload_db_to_hf()
247
+ cache.clear()
248
  except Exception as e:
249
+ logging.error(f"Error saving data: {e}")
250
  raise
251
 
252
  def upload_db_to_hf():
 
259
  commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
260
  )
261
  except Exception as e:
262
+ logging.error(f"Error uploading database: {e}")
263
 
264
  def download_db_from_hf():
265
  if not HF_TOKEN_READ:
 
275
  if not os.path.exists(DATA_FILE):
276
  with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
277
  except Exception as e:
278
+ logging.error(f"Error downloading database: {e}")
279
  if not os.path.exists(DATA_FILE):
280
  with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
281
 
 
326
  window.Telegram.WebApp.ready();
327
  const initData = window.Telegram.WebApp.initData;
328
  const initDataUnsafe = window.Telegram.WebApp.initDataUnsafe;
 
329
  if (initDataUnsafe && initDataUnsafe.user) {
330
  fetch("{{ url_for('auth_via_telegram') }}", {
331
  method: 'POST',
 
360
  def root_redirect():
361
  return redirect(url_for('tma_entry_page'))
362
 
 
363
  @app.route('/auth_via_telegram', methods=['POST'])
364
  def auth_via_telegram():
365
  try:
366
  payload = request.json
367
  tg_user_data = payload.get('user')
 
368
  if not tg_user_data or not tg_user_data.get('id'):
369
  return jsonify({'status': 'error', 'message': 'Отсутствуют данные пользователя Telegram.'}), 400
 
370
  tma_user_id_str = str(tg_user_data['id'])
371
  data = load_data()
 
372
  if tma_user_id_str not in data['users']:
373
  data['users'][tma_user_id_str] = {
374
  'telegram_id': tg_user_data['id'],
 
382
  try:
383
  save_data(data)
384
  except Exception as e:
385
+ logging.error(f"Save data error for new TMA user {tma_user_id_str}: {e}")
386
  return jsonify({'status': 'error', 'message': 'Ошибка сохранения данных нового пользователя.'}), 500
 
387
  session['telegram_user_id'] = tma_user_id_str
388
  display_name = tg_user_data.get('first_name') or tg_user_data.get('username') or f"User {tma_user_id_str}"
389
  session['telegram_display_name'] = display_name
 
390
  return jsonify({'status': 'success', 'redirect_url': url_for('tma_dashboard')})
 
391
  except Exception as e:
392
+ logging.error(f"Error in auth_via_telegram: {e}")
393
  return jsonify({'status': 'error', 'message': 'Внутренняя ошибка сервера при авторизации.'}), 500
394
 
395
  TMA_DASHBOARD_HTML_TEMPLATE = '''
 
406
  {% endif %}
407
  </p>
408
  {% if not user_data.unlimited_storage %}
409
+ <button type="button" id="buy-unlimited-btn" class="btn" style="background: var(--secondary); margin-left: 0; margin-bottom: 10px;">Купить безлимит за {{ UNLIMITED_STORAGE_PRICE_STARS }} ⭐️</button>
410
  {% endif %}
411
  {% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}
412
  {% for category, message in messages %}<div class="flash {{ category }}">{{ message }}</div>{% endfor %}
 
507
 
508
  function tmaDownloadFile(downloadUrl, filename) {
509
  if (window.Telegram && window.Telegram.WebApp && Telegram.WebApp.openLink) {
510
+ console.log(`Attempting TMA download for: ${filename} via URL: ${downloadUrl}`);
511
  Telegram.WebApp.openLink(downloadUrl);
512
  } else {
513
+ console.warn("Telegram.WebApp.openLink not available, or not in TMA environment. Attempting fallback download.");
514
  const link = document.createElement('a');
515
  link.href = downloadUrl;
516
  link.setAttribute('download', filename);
 
545
  }
546
  document.getElementById('logout-btn').addEventListener('click', function(e) { e.preventDefault(); window.location.href = "{{ url_for('tma_logout') }}"; });
547
 
548
+ const buyButton = document.getElementById('buy-unlimited-btn');
549
+ if (buyButton) {
550
+ buyButton.addEventListener('click', () => {
551
+ buyButton.disabled = true;
552
+ buyButton.textContent = 'Создаем счет...';
553
+ fetch("{{ url_for('create_invoice') }}")
554
+ .then(response => response.json())
555
+ .then(data => {
556
+ if (data.ok) {
557
+ Telegram.WebApp.openInvoice(data.invoice_slug, (status) => {
558
+ if (status === 'paid') {
559
+ Telegram.WebApp.showAlert('Оплата прошла успешно! Хранилище обновлено.');
560
+ window.location.reload();
561
+ } else if (status === 'failed') {
562
+ Telegram.WebApp.showAlert('Не удалось совершить оплату. Попробуйте снова.');
563
+ }
564
+ });
565
+ } else {
566
+ Telegram.WebApp.showAlert(data.error || 'Не удалось создать счет.');
567
+ }
568
+ })
569
+ .catch(error => {
570
+ Telegram.WebApp.showAlert('Ошибка сети при создании счета.');
571
+ })
572
+ .finally(() => {
573
+ buyButton.disabled = false;
574
+ buyButton.textContent = 'Купить безлимит за {{ UNLIMITED_STORAGE_PRICE_STARS }} ⭐️';
575
+ });
576
+ });
577
  }
578
  </script></body></html>
579
  '''
 
583
  if 'telegram_user_id' not in session:
584
  flash('Пожалуйста, авторизуйтесь через Telegram.', 'error')
585
  return redirect(url_for('tma_entry_page'))
 
586
  tma_user_id = session['telegram_user_id']
587
  display_name = session.get('telegram_display_name', 'Пользователь')
588
  data = load_data()
 
589
  if tma_user_id not in data['users']:
590
  session.clear()
591
  flash('Пользователь не найден в системе. Пожалуйста, перезапустите приложение.', 'error')
592
  return redirect(url_for('tma_entry_page'))
 
593
  user_data = data['users'][tma_user_id]
594
  initialize_user_filesystem_tma(user_data, tma_user_id)
 
595
  current_folder_id = request.args.get('folder_id', 'root')
596
  current_folder, _ = find_node_by_id(user_data['filesystem'], current_folder_id)
 
597
  if not current_folder or current_folder.get('type') != 'folder':
598
  flash('Папка не найдена!', 'error')
599
  current_folder_id = 'root'
 
602
  flash('Критическая ошибка: корневая папка не найдена.', 'error')
603
  session.clear()
604
  return redirect(url_for('tma_entry_page'))
 
605
  items_in_folder = sorted(current_folder.get('children', []), key=lambda x: (x['type'] != 'folder', x.get('name', x.get('original_filename', '')).lower()))
 
606
  if request.method == 'POST':
607
  if not HF_TOKEN_WRITE:
608
  flash('Загрузка невозможна: токен для записи не настроен.', 'error')
609
  return redirect(url_for('tma_dashboard', folder_id=current_folder_id))
 
610
  files = request.files.getlist('files')
611
  if not files or all(not f.filename for f in files):
612
  flash('Файлы для загрузки не выбраны.', 'error')
 
614
  if len(files) > 20:
615
  flash('Максимум 20 файлов за раз!', 'error')
616
  return redirect(url_for('tma_dashboard', folder_id=current_folder_id))
 
617
  target_folder_id = request.form.get('current_folder_id', 'root')
618
  target_folder_node, _ = find_node_by_id(user_data['filesystem'], target_folder_id)
619
  if not target_folder_node or target_folder_node.get('type') != 'folder':
620
  flash('Целевая папка для загрузки не найдена!', 'error')
621
  return redirect(url_for('tma_dashboard'))
 
622
  if not user_data['unlimited_storage']:
623
  total_new_files_size = 0
624
  for file_obj in files:
 
626
  file_obj.seek(0, os.SEEK_END)
627
  total_new_files_size += file_obj.tell()
628
  file_obj.seek(0)
 
629
  if user_data['storage_used_bytes'] + total_new_files_size > user_data['storage_limit_gb'] * BYTES_IN_GB:
630
  flash(f'Недостаточно места! Ваш лимит {user_data["storage_limit_gb"]} ГБ, использовано {format_bytes(user_data["storage_used_bytes"])}. '
631
  f'Эти файлы слишком большие ({format_bytes(total_new_files_size)}).', 'error')
632
  return redirect(url_for('tma_dashboard', folder_id=current_folder_id))
 
633
  api = HfApi()
634
  uploaded_count = 0
635
  errors_list = []
 
642
  file_id = uuid.uuid4().hex
643
  hf_path = f"cloud_files/{tma_user_id}/{target_folder_id}/{unique_filename}"
644
  temp_path = os.path.join(UPLOAD_FOLDER, f"{file_id}_{unique_filename}")
 
645
  file_obj.save(temp_path)
646
  file_size = os.path.getsize(temp_path)
 
647
  try:
648
  api.upload_file(
649
  path_or_fileobj=temp_path, path_in_repo=hf_path, repo_id=REPO_ID,
 
663
  else:
664
  errors_list.append(f"Ошибка добавления метаданных для {original_filename}.")
665
  try: api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE)
666
+ except Exception as del_err: logging.error(f"Failed to delete orphaned file {hf_path} from HF Hub: {del_err}")
667
  except Exception as e:
668
  errors_list.append(f"Ошибка загрузки файла {original_filename}: {e}")
669
  finally:
 
688
  if not parent_node_bc: break
689
  temp_id = parent_node_bc.get('id')
690
  breadcrumbs.reverse()
 
691
  storage_used_formatted = format_bytes(user_data['storage_used_bytes'])
 
692
  return render_template_string(TMA_DASHBOARD_HTML_TEMPLATE,
693
  display_name=display_name, items=items_in_folder,
694
  current_folder_id=current_folder_id, current_folder=current_folder,
695
  breadcrumbs=breadcrumbs, repo_id_js=REPO_ID, HF_TOKEN_READ_js=HF_TOKEN_READ,
696
  hf_file_url_jinja=lambda path, download=False: f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{path}{'?download=true' if download else ''}",
697
  is_tma_user_admin_flag=is_admin_tma(),
698
+ user_data=user_data, storage_used_formatted=storage_used_formatted, UNLIMITED_STORAGE_PRICE_STARS=UNLIMITED_STORAGE_PRICE_STARS)
 
 
699
 
700
+ @app.route('/create_invoice')
701
+ async def create_invoice():
702
  if 'telegram_user_id' not in session:
703
+ return jsonify({'ok': False, 'error': 'Not authorized'})
 
 
704
  tma_user_id = session['telegram_user_id']
705
  data = load_data()
706
  user_data = data['users'].get(tma_user_id)
707
+ if not user_data or user_data.get('unlimited_storage'):
708
+ return jsonify({'ok': False, 'error': 'User not found or already has unlimited storage'})
 
 
 
709
 
710
+ payload = f"unlimited_{tma_user_id}_{uuid.uuid4().hex}"
711
+ bot = telegram.Bot(token=TELEGRAM_BOT_TOKEN)
712
+ try:
713
+ link = await bot.create_invoice_link(
714
+ title='Безлимитное хранилище',
715
+ description='Постоянный доступ к безлимитному облачному хранилищу Zeus Cloud.',
716
+ payload=payload,
717
+ currency='XTR',
718
+ prices=[telegram.LabeledPrice('Безлимит', UNLIMITED_STORAGE_PRICE_STARS)]
719
+ )
720
+ invoice_slug = link.split('/')[4]
721
+ return jsonify({'ok': True, 'invoice_slug': invoice_slug})
722
+ except Exception as e:
723
+ logging.error(f"Error creating invoice for user {tma_user_id}: {e}")
724
+ return jsonify({'ok': False, 'error': 'Could not create invoice from Telegram API'})
725
 
726
  @app.route('/create_folder_tma', methods=['POST'])
727
  def create_folder_tma():
 
734
  if not user_data:
735
  flash('Пользователь не найден', 'error')
736
  return redirect(url_for('tma_entry_page'))
 
737
  parent_folder_id = request.form.get('parent_folder_id', 'root')
738
  folder_name = request.form.get('folder_name', '').strip()
739
  if not folder_name:
740
  flash('Имя папки не может быть пустым!', 'error')
741
  return redirect(url_for('tma_dashboard', folder_id=parent_folder_id))
 
742
  folder_id = uuid.uuid4().hex
743
  folder_data = {'type': 'folder', 'id': folder_id, 'name': folder_name, 'children': []}
744
  if add_node(user_data['filesystem'], parent_folder_id, folder_data):
 
754
  def download_tma(file_id):
755
  current_tma_user_id = session.get('telegram_user_id')
756
  is_browser_admin_session = session.get('admin_browser_logged_in', False)
 
757
  data = load_data()
758
  file_node = None
 
759
  if is_browser_admin_session:
760
  for uid_str_iter, udata_iter in data.get('users', {}).items():
761
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
 
766
  user_data = data['users'].get(current_tma_user_id)
767
  if user_data:
768
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
 
769
  if not file_node and is_admin_tma():
770
  for uid_str_iter, udata_iter in data.get('users', {}).items():
771
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
 
778
  return redirect(url_for('tma_entry_page'))
779
  else:
780
  return Response("Unauthorized", status=401, mimetype='text/plain')
 
 
781
  redirect_url_fallback = url_for('tma_dashboard')
782
  if is_browser_admin_session:
783
  redirect_url_fallback = url_for('admin_panel')
 
784
  redirect_url = request.referrer or redirect_url_fallback
 
 
785
  if not file_node or file_node.get('type') != 'file':
786
  flash('Файл не найден или доступ запрещен!', 'error')
787
  return redirect(redirect_url)
 
788
  hf_path = file_node.get('path')
789
  original_filename = file_node.get('original_filename', 'downloaded_file')
790
  if not hf_path:
791
  flash('Ошибка: Путь к файлу не найден.', 'error')
792
  return redirect(redirect_url)
 
793
  file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
794
  try:
795
  req_headers = {}
796
  if HF_TOKEN_READ: req_headers["authorization"] = f"Bearer {HF_TOKEN_READ}"
 
797
  r = requests.get(file_url, headers=req_headers, stream=True)
798
  r.raise_for_status()
 
799
  def generate_chunks():
800
  for chunk in r.iter_content(chunk_size=8192):
801
  yield chunk
 
802
  mimetype = 'application/octet-stream'
803
  if '.' in original_filename:
804
  ext = original_filename.rsplit('.', 1)[1].lower()
 
809
  'zip': 'application/zip', 'rar': 'application/x-rar-compressed',
810
  }
811
  mimetype = content_types.get(ext, 'application/octet-stream')
 
812
  resp = Response(generate_chunks(), mimetype=mimetype)
813
  resp.headers['Content-Disposition'] = f'attachment; filename="{original_filename}"'
814
  resp.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
815
  resp.headers['Pragma'] = 'no-cache'
816
  resp.headers['Expires'] = '0'
817
  return resp
 
818
  except requests.exceptions.RequestException as e:
819
+ logging.error(f"Error downloading file {original_filename} (ID: {file_id}) from HF: {e}")
820
  flash(f'Ошибка скачивания файла: {e}', 'error')
821
  except Exception as e:
822
+ logging.error(f"Internal error during download {original_filename} (ID: {file_id}): {e}")
823
  flash(f'Внутренняя ошибка при скачивании: {e}', 'error')
824
  return redirect(redirect_url)
825
 
 
826
  @app.route('/delete_file_tma/<file_id>', methods=['POST'])
827
  def delete_file_tma():
828
  if 'telegram_user_id' not in session:
829
  flash('Пожалуйста, авторизуйтесь.', 'error')
830
  return redirect(url_for('tma_entry_page'))
 
831
  file_id = request.view_args.get('file_id')
 
832
  tma_user_id = session['telegram_user_id']
833
  data = load_data()
834
  user_data = data['users'].get(tma_user_id)
835
  if not user_data:
836
  session.clear(); flash('Пользователь не найден.', 'error'); return redirect(url_for('tma_entry_page'))
 
837
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
838
  current_view_folder_id = request.form.get('current_view_folder_id', 'root')
839
  if not file_node or file_node.get('type') != 'file':
840
  flash('Файл не найден.', 'error')
841
  return redirect(url_for('tma_dashboard', folder_id=current_view_folder_id))
 
842
  hf_path = file_node.get('path')
843
  original_filename = file_node.get('original_filename', 'файл')
844
  file_size_to_deduct = file_node.get('size_bytes', 0)
 
845
  if not hf_path:
846
  if remove_node(user_data['filesystem'], file_id):
847
  user_data['storage_used_bytes'] -= file_size_to_deduct
848
  try: save_data(data); flash(f'Метаданные файла {original_filename} удалены (путь отсутствовал).')
849
  except Exception as e: flash('Ошибка сохранения данных после удаления метаданных.', 'error')
850
  return redirect(url_for('tma_dashboard', folder_id=current_view_folder_id))
 
851
  if not HF_TOKEN_WRITE:
852
  flash('Удаление невозможно: токен для записи не настроен.', 'error')
853
  return redirect(url_for('tma_dashboard', folder_id=current_view_folder_id))
 
879
  user_data = data['users'].get(tma_user_id)
880
  if not user_data:
881
  session.clear(); flash('Пользователь не найден.', 'error'); return redirect(url_for('tma_entry_page'))
 
882
  folder_node, parent_node = find_node_by_id(user_data['filesystem'], folder_id)
883
  current_view_folder_id = request.form.get('current_view_folder_id', 'root')
884
  if not folder_node or folder_node.get('type') != 'folder' or not parent_node:
 
897
  def get_text_content_tma(file_id):
898
  current_tma_user_id = session.get('telegram_user_id')
899
  is_browser_admin_session = session.get('admin_browser_logged_in', False)
 
900
  data = load_data()
901
  file_node = None
 
902
  if is_browser_admin_session:
903
  for uid_str, udata_iter in data.get('users', {}).items():
904
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
 
908
  user_data = data['users'].get(current_tma_user_id)
909
  if user_data:
910
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
 
911
  if not file_node and is_admin_tma():
912
  for uid_str, udata_iter in data.get('users', {}).items():
913
  node, _ = find_node_by_id(udata_iter.get('filesystem', {}), file_id)
 
915
  file_node = node; break
916
  else:
917
  return Response("Не авторизован", status=401)
 
918
  if not file_node or file_node.get('type') != 'file' or file_node.get('file_type') != 'text':
919
  return Response("Текстовый файл не найден", status=404)
920
  hf_path = file_node.get('path')
 
938
  flash('Вы вышли из сессии приложения.')
939
  return redirect(url_for('tma_entry_page'))
940
 
 
941
  ADMIN_LOGIN_HTML_TEMPLATE = '''
942
  <!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
943
  <title>Admin Login</title><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
 
975
  username = request.form.get('username')
976
  password = request.form.get('password')
977
  next_url = request.form.get('next')
 
978
  if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
979
  session['admin_browser_logged_in'] = True
980
  flash('Успешный вход в админ-панель.', 'success')
 
1112
  user_data = data.get('users', {}).get(tma_user_id_str)
1113
  if not user_data:
1114
  flash(f'Пользователь ID {tma_user_id_str} не найден.', 'error'); return redirect(url_for('admin_panel'))
 
1115
  all_files = []
1116
  def collect_files_admin(folder, current_path_id='root'):
1117
  parent_path_str = get_node_path_string(user_data['filesystem'], current_path_id)
 
1121
  item['size_formatted'] = format_bytes(item.get('size_bytes'))
1122
  all_files.append(item)
1123
  elif item.get('type') == 'folder': collect_files_admin(item, item.get('id'))
 
1124
  collect_files_admin(user_data.get('filesystem', {}))
1125
  all_files.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
 
1126
  display_name_admin_view = user_data.get('first_name', user_data.get('telegram_username', f"User {tma_user_id_str}"))
1127
  storage_used_formatted_admin_view = format_bytes(user_data['storage_used_bytes'])
 
1128
  return render_template_string(ADMIN_USER_FILES_HTML_TEMPLATE,
1129
  tma_user_id_str_admin_view=tma_user_id_str, display_name_admin_view=display_name_admin_view, files=all_files,
1130
  repo_id_js_admin=REPO_ID,
 
1136
  def admin_toggle_unlimited_storage(tma_user_id_str):
1137
  data = load_data()
1138
  user_data = data.get('users', {}).get(tma_user_id_str)
 
1139
  if not user_data:
1140
  flash(f'Пользователь {tma_user_id_str} не найден.', 'error')
1141
  return redirect(url_for('admin_panel'))
 
1142
  user_data['unlimited_storage'] = not user_data.get('unlimited_storage', False)
1143
  try:
1144
  save_data(data)
 
1146
  flash(f'Безлимитное хранилище для пользователя {tma_user_id_str} {status}.', 'success')
1147
  except Exception as e:
1148
  flash(f'Ошибка при изменении статуса безлимитного хранилища: {e}', 'error')
 
1149
  return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str))
1150
 
1151
  @app.route('/admhosto/delete_user/<tma_user_id_str>', methods=['POST'])
 
1163
  except hf_utils.HfHubHTTPError as e:
1164
  if e.response.status_code != 404:
1165
  flash(f'Ошибка удаления файлов пользователя {tma_user_id_str} с сервера: {e}. Пользователь из базы не удален.', 'error'); return redirect(url_for('admin_panel'))
1166
+ logging.info(f"Folder {user_folder_path_on_hf} not found on HF Hub for user {tma_user_id_str} or was already empty, proceeding with DB deletion.")
1167
  except Exception as e:
1168
  flash(f'Ошибка удаления файлов пользователя {tma_user_id_str} с сервера: {e}. Пользователь из базы не ��дален.', 'error'); return redirect(url_for('admin_panel'))
 
1169
  try:
1170
  del data['users'][tma_user_id_str]
1171
  save_data(data)
 
1174
  flash(f'Файлы на сервере могли быть удалены, но произошла ошибка удаления пользователя из базы данных: {e}', 'error')
1175
  return redirect(url_for('admin_panel'))
1176
 
 
1177
  @app.route('/admhosto/delete_file/<tma_user_id_str_form>/<file_id>', methods=['POST'])
1178
  @admin_browser_login_required
1179
  def admin_delete_file(tma_user_id_str_form, file_id):
 
1183
  user_data = data.get('users', {}).get(tma_user_id_str_form)
1184
  if not user_data:
1185
  flash(f'Пользователь {tma_user_id_str_form} не найден.', 'error'); return redirect(url_for('admin_panel'))
 
1186
  file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
1187
  if not file_node or file_node.get('type') != 'file':
1188
  flash('Файл не найден в базе данных этого пользователя.', 'error'); return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str_form))
 
1189
  hf_path = file_node.get('path')
1190
  original_filename = file_node.get('original_filename', 'файл')
1191
  file_size_to_deduct = file_node.get('size_bytes', 0)
 
1192
  if not hf_path:
1193
  if remove_node(user_data['filesystem'], file_id):
1194
  user_data['storage_used_bytes'] -= file_size_to_deduct
1195
  try: save_data(data); flash(f'Метаданные файла {original_filename} удалены из базы (путь к файлу на сервере отсутствовал).')
1196
  except Exception as e: flash('Ошибка сохранения данных после удаления метаданных (путь отсутствовал).', 'error')
1197
  return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str_form))
 
1198
  try:
1199
  api = HfApi()
1200
  api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE)
 
1215
  return redirect(url_for('admin_user_files', tma_user_id_str=tma_user_id_str_form))
1216
 
1217
 
1218
+ async def precheckout_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
1219
+ query = update.pre_checkout_query
1220
+ if query.invoice_payload.startswith('unlimited_'):
1221
+ await query.answer(ok=True)
1222
+ else:
1223
+ await query.answer(ok=False, error_message="Что-то пошло не так...")
1224
+
1225
+ async def successful_payment_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
1226
+ payment_info = update.message.successful_payment
1227
+ payload = payment_info.invoice_payload
1228
+ logging.info(f"Successful payment! Payload: {payload}, Currency: {payment_info.currency}, Total Amount: {payment_info.total_amount}")
1229
+
1230
+ if payload.startswith('unlimited_'):
1231
+ try:
1232
+ parts = payload.split('_')
1233
+ user_id_str = parts[1]
1234
+
1235
+ with data_lock:
1236
+ data = load_data()
1237
+ if user_id_str in data['users']:
1238
+ data['users'][user_id_str]['unlimited_storage'] = True
1239
+ logging.info(f"Upgrading user {user_id_str} to unlimited storage.")
1240
+ save_data(data)
1241
+ logging.info(f"User {user_id_str} data saved successfully.")
1242
+
1243
+ try:
1244
+ await context.bot.send_message(
1245
+ chat_id=user_id_str,
1246
+ text="🎉 Поздравляем! Вы успешно приобрели безлимитное хранилище. Перезапустите приложение, чтобы увидеть изменения."
1247
+ )
1248
+ except Exception as e:
1249
+ logging.error(f"Failed to send confirmation message to user {user_id_str}: {e}")
1250
+ else:
1251
+ logging.warning(f"User {user_id_str} not found in database for payload {payload}")
1252
+ except Exception as e:
1253
+ logging.error(f"Error processing successful payment with payload {payload}: {e}")
1254
+
1255
+ def run_bot():
1256
+ if not TELEGRAM_BOT_TOKEN:
1257
+ logging.error("TELEGRAM_BOT_TOKEN is not set. Payment processing bot will not start.")
1258
+ return
1259
+
1260
+ application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
1261
+ application.add_handler(PreCheckoutQueryHandler(precheckout_callback))
1262
+ application.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment_callback))
1263
+
1264
+ logging.info("Telegram Bot for payments is starting...")
1265
+ application.run_polling()
1266
+ logging.info("Telegram Bot for payments has stopped.")
1267
 
1268
 
1269
+ if __name__ == '__main__':
1270
+ if not HF_TOKEN_WRITE: logging.warning("HF_TOKEN (write access) is not set. Uploads/deletions/backups will fail.")
1271
+ if not HF_TOKEN_READ: logging.warning("HF_TOKEN_READ is not set. Using HF_TOKEN. Downloads/previews might fail for private repos if HF_TOKEN also lacks read.")
1272
+ if ADMIN_TELEGRAM_ID == "YOUR_ADMIN_TELEGRAM_USER_ID_HERE": logging.warning("ADMIN_TELEGRAM_ID is not set. TMA admin features might not work as expected for specific users.")
1273
+ if ADMIN_USERNAME == "admin" and ADMIN_PASSWORD == "zeusadminpass":
1274
+ logging.warning("Using default admin username/password for browser admin panel. Please change ADMIN_USERNAME and ADMIN_PASSWORD environment variables.")
1275
+
1276
  if HF_TOKEN_WRITE:
1277
  download_db_from_hf()
1278
  threading.Thread(target=periodic_backup, daemon=True).start()
 
1281
  else:
1282
  if not os.path.exists(DATA_FILE):
1283
  with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
1284
+
1285
+ bot_thread = threading.Thread(target=run_bot, daemon=True)
1286
+ bot_thread.start()
1287
 
1288
  app.run(debug=False, host='0.0.0.0', port=7860)