+'''
+ template_context = {
+ 'user_display_name': user_display_name, 'items': items_in_folder, 'current_folder_id': current_folder_id,
+ 'current_folder': current_folder, 'breadcrumbs': breadcrumbs, 'repo_id': REPO_ID,
+ 'hf_file_url': lambda path, download=False: f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{path}{'?download=true' if download else ''}",
+ }
+ return render_template_string(html, **template_context)
@app.route('/create_folder', methods=['POST'])
def create_folder():
if 'telegram_user_id' not in session: return jsonify({'status': 'error', 'message': 'Не авторизован'}), 401
- tg_user_id = session['telegram_user_id']
- data = load_data_tg(); user_data = data['users'].get(tg_user_id)
+ tg_user_id_str = str(session['telegram_user_id'])
+ data = load_data()
+ user_data = data['users'].get(tg_user_id_str)
if not user_data: return jsonify({'status': 'error', 'message': 'Пользователь не найден'}), 404
parent_folder_id = request.form.get('parent_folder_id', 'root')
folder_name = request.form.get('folder_name', '').strip()
- if not folder_name: flash('Имя папки не может быть пустым!', 'error'); return redirect(url_for('dashboard', folder_id=parent_folder_id))
- # Allow more characters in folder names
- # if not all(c.isalnum() or c in [' ', '_', '-'] for c in folder_name):
- # flash('Имя папки может содержать буквы, цифры, пробелы, дефисы и подчерки��ания.', 'error')
- # return redirect(url_for('dashboard', folder_id=parent_folder_id))
-
- folder_id = uuid.uuid4().hex
- folder_data = {'type': 'folder', 'id': folder_id, 'name': folder_name, 'children': []}
- if add_node(user_data['filesystem'], parent_folder_id, folder_data):
- try: save_data_tg(data); flash(f'Папка "{folder_name}" успешно создана.')
- except Exception as e: flash('Ошибка сохранения данных при создании папки.', 'error'); logging.error(f"Create folder save error: {e}")
- else: flash('Не удалось найти родительскую папку.', 'error')
+
+ if not folder_name:
+ flash('Имя папки не может быть пустым!', 'error')
+ elif not all(c.isalnum() or c.isspace() or c in '_-.' for c in folder_name) or len(folder_name) > 50 : # Basic validation
+ flash('Имя папки содержит недопустимые символы или слишком длинное.', 'error')
+ else:
+ folder_id = uuid.uuid4().hex
+ folder_data = {'type': 'folder', 'id': folder_id, 'name': folder_name, 'children': []}
+ if add_node(user_data['filesystem'], parent_folder_id, folder_data):
+ try: save_data(data); flash(f'Папка "{folder_name}" успешно создана.')
+ except Exception as e: flash('Ошибка сохранения данных.', 'error'); logging.error(f"Create folder save error: {e}")
+ else: flash('Не удалось найти родительскую папку.', 'error')
return redirect(url_for('dashboard', folder_id=parent_folder_id))
+
@app.route('/download/')
def download_file(file_id):
- is_admin_access = is_current_user_admin()
- if 'telegram_user_id' not in session and not is_admin_access:
- flash('Пожалуйста, авторизуйтесь.')
- return redirect(url_for('launch_webapp'))
+ allow_access = False
+ current_user_tg_id_str = str(session.get('telegram_user_id')) if 'telegram_user_id' in session else None
+
+ data = load_data()
+ file_node = None
+ # file_owner_tg_id_str = None # Not strictly needed here, but good for logging if implemented
- data = load_data_tg()
- file_node = None; owner_tg_user_id = None
+ # Check if current user is admin
+ is_current_user_admin = is_admin()
- if 'telegram_user_id' in session:
- current_tg_user_id = session['telegram_user_id']
- user_data = data['users'].get(current_tg_user_id)
+ if current_user_tg_id_str:
+ user_data = data['users'].get(current_user_tg_id_str)
if user_data:
- file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
- if file_node: owner_tg_user_id = current_tg_user_id
-
- if not file_node and is_admin_access: # Admin search across users
- logging.info(f"Admin download: searching for file ID {file_id}")
- for tg_id, u_data in data.get('users', {}).items():
- node, _ = find_node_by_id(u_data.get('filesystem', {}), file_id)
+ _file_node, _ = find_node_by_id(user_data.get('filesystem', {}), file_id)
+ if _file_node and _file_node.get('type') == 'file':
+ file_node = _file_node
+ allow_access = True # Owner has access
+
+ if not file_node and is_current_user_admin: # If admin and file not found under their own ID (or they are searching)
+ for tg_id, udata in data.get('users', {}).items():
+ if tg_id == current_user_tg_id_str: continue # Already checked
+ node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
if node and node.get('type') == 'file':
- file_node = node; owner_tg_user_id = tg_id
- logging.info(f"Admin found file ID {file_id} belonging to user {owner_tg_user_id}")
- break
+ file_node = node; allow_access = True; break
- if not file_node or file_node.get('type') != 'file':
- flash('Файл не найден!', 'error')
- return redirect(request.referrer or url_for('dashboard'))
+ if not allow_access or not file_node:
+ flash('Файл не найден или доступ запрещен!', 'error')
+ return redirect(url_for('dashboard') if current_user_tg_id_str else url_for('mini_app_entry'))
hf_path = file_node.get('path')
original_filename = file_node.get('original_filename', 'downloaded_file')
if not hf_path:
- flash('Ошибка: Путь к файлу не найден в метаданных.', 'error')
- return redirect(request.referrer or url_for('dashboard'))
+ flash('Ошибка: Путь к файлу не найден.', 'error')
+ return redirect(url_for('dashboard', folder_id=request.args.get('folder_id', 'root'))) # Stay in current folder
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
try:
- headers = {};
+ headers = {}
if HF_TOKEN_READ: headers["authorization"] = f"Bearer {HF_TOKEN_READ}"
response = requests.get(file_url, headers=headers, stream=True)
response.raise_for_status()
- file_content = BytesIO(response.content)
- return send_file(file_content, as_attachment=True, download_name=original_filename, mimetype='application/octet-stream')
+ return send_file(BytesIO(response.content), as_attachment=True, download_name=original_filename, mimetype='application/octet-stream')
except requests.exceptions.RequestException as e:
logging.error(f"Error downloading file from HF ({hf_path}): {e}")
- flash(f'Ошибка скачивания файла {original_filename}! ({e})', 'error')
+ flash(f'Ошибка скачивания файла: {e}', 'error')
except Exception as e:
logging.error(f"Unexpected error during download ({hf_path}): {e}")
- flash('Произошла непредвиденная ошибка при скачивании файла.', 'error')
- return redirect(request.referrer or url_for('dashboard'))
+ flash('Непредвиденная ошибка при скачивании.', 'error')
+ return redirect(url_for('dashboard', folder_id=request.args.get('folder_id', 'root')))
@app.route('/delete_file/', methods=['POST'])
def delete_file(file_id):
- if 'telegram_user_id' not in session: flash('Пожалуйста, авторизуйтесь.'); return redirect(url_for('launch_webapp'))
- tg_user_id = session['telegram_user_id']
- data = load_data_tg(); user_data = data['users'].get(tg_user_id)
- if not user_data: flash('Пользователь не найден!', 'error'); session.clear(); return redirect(url_for('launch_webapp'))
+ if 'telegram_user_id' not in session: flash('Пожалуйста, авторизуйтесь.'); return redirect(url_for('mini_app_entry'))
+ tg_user_id_str = str(session['telegram_user_id'])
+ data = load_data()
+ user_data = data['users'].get(tg_user_id_str)
+ if not user_data: flash('Пользователь не найден!'); session.clear(); return redirect(url_for('mini_app_entry'))
file_node, parent_node = find_node_by_id(user_data['filesystem'], file_id)
current_view_folder_id = request.form.get('current_view_folder_id', parent_node.get('id', 'root') if parent_node else 'root')
- if not file_node or file_node.get('type') != 'file' or not parent_node: # parent_node must exist for a non-root file
- flash('Файл не найден или не может быть удален.', 'error')
- return redirect(url_for('dashboard', folder_id=current_view_folder_id))
- hf_path = file_node.get('path')
- original_filename = file_node.get('original_filename', 'файл')
-
- if not hf_path:
- flash(f'Ошибка: Путь к файлу {original_filename} не найден. Удаление только из базы.', 'error')
- if remove_node(user_data['filesystem'], file_id):
- try: save_data_tg(data); flash(f'Метаданные файла {original_filename} удалены.')
- except Exception as e: flash('Ошибка сохранения данных.', 'error'); logging.error(f"Delete file metadata save error: {e}")
- return redirect(url_for('dashboard', folder_id=current_view_folder_id))
-
- if not HF_TOKEN_WRITE: flash('Удаление невозможно: токен для записи не настроен.', 'error'); return redirect(url_for('dashboard', folder_id=current_view_folder_id))
-
- try:
- api = HfApi()
- api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, commit_message=f"User {tg_user_id} deleted file {original_filename}")
- logging.info(f"Deleted file {hf_path} from HF Hub for user {tg_user_id}")
- if remove_node(user_data['filesystem'], file_id):
- try: save_data_tg(data); flash(f'Файл {original_filename} успешно удален!')
- except Exception as e: flash('Файл удален с сервера, но ошибка обновления БД.', 'error'); logging.error(f"Delete file DB update error: {e}")
- else: flash('Файл удален с сервера, но не найден в БД.', 'error')
- except hf_utils.EntryNotFoundError:
- logging.warning(f"File {hf_path} not found on HF Hub for user {tg_user_id}. Removing from DB.")
- if remove_node(user_data['filesystem'], file_id):
- try: save_data_tg(data); flash(f'Файл {original_filename} не найден на сервере, удален из базы.')
- except Exception as e: flash('Ошибка сохранения данных.', 'error'); logging.error(f"Delete file metadata save error (HF not found): {e}")
- else: flash('Файл не найден ни на сервере, ни в базе данных.', 'error')
- except Exception as e:
- logging.error(f"Error deleting file {hf_path} for {tg_user_id}: {e}")
- flash(f'Ошибка удаления файла {original_filename}: {e}', 'error')
+ if not file_node or file_node.get('type') != 'file' or not parent_node:
+ flash('Файл не найден или не может быть удален.', 'error')
+ elif not HF_TOKEN_WRITE:
+ flash('Удаление невозможно: токен для записи не настроен.', 'error')
+ else:
+ hf_path = file_node.get('path')
+ original_filename = file_node.get('original_filename', 'файл')
+ try:
+ if hf_path:
+ api = HfApi()
+ api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE,
+ commit_message=f"User TGID:{tg_user_id_str} deleted file {original_filename}")
+ if remove_node(user_data['filesystem'], file_id): save_data(data)
+ flash(f'Файл {original_filename} успешно удален!')
+ except hf_utils.EntryNotFoundError: # File not on HF, remove from DB
+ if remove_node(user_data['filesystem'], file_id): save_data(data)
+ flash(f'Файл {original_filename} не найден на сервере, удален из базы.')
+ except Exception as e:
+ logging.error(f"Error deleting file {hf_path} for TGID {tg_user_id_str}: {e}")
+ flash(f'Ошибка удаления файла {original_filename}: {e}', 'error')
return redirect(url_for('dashboard', folder_id=current_view_folder_id))
@app.route('/delete_folder/', methods=['POST'])
def delete_folder(folder_id):
- if 'telegram_user_id' not in session: flash('Пожалуйста, авторизуйтесь.'); return redirect(url_for('launch_webapp'))
+ if 'telegram_user_id' not in session: flash('Пожалуйста, авторизуйтесь.'); return redirect(url_for('mini_app_entry'))
if folder_id == 'root': flash('Нельзя удалить корневую папку!', 'error'); return redirect(url_for('dashboard'))
- tg_user_id = session['telegram_user_id']
- data = load_data_tg(); user_data = data['users'].get(tg_user_id)
- if not user_data: flash('Пользователь не найден!', 'error'); session.clear(); return redirect(url_for('launch_webapp'))
+ tg_user_id_str = str(session['telegram_user_id'])
+ data = load_data()
+ user_data = data['users'].get(tg_user_id_str)
+ if not user_data: flash('Пользователь не найден!'); session.clear(); return redirect(url_for('mini_app_entry'))
folder_node, parent_node = find_node_by_id(user_data['filesystem'], folder_id)
current_view_folder_id = request.form.get('current_view_folder_id', parent_node.get('id', 'root') if parent_node else 'root')
-
if not folder_node or folder_node.get('type') != 'folder' or not parent_node:
flash('Папка не найдена или не может быть удалена.', 'error')
- return redirect(url_for('dashboard', folder_id=current_view_folder_id))
-
- folder_name = folder_node.get('name', 'папка')
- if folder_node.get('children'):
- flash(f'Папку "{folder_name}" можно удалить только если она пуста.', 'error')
- return redirect(url_for('dashboard', folder_id=current_view_folder_id)) # Redirect back to the folder containing the one we tried to delete
-
- if remove_node(user_data['filesystem'], folder_id):
- try: save_data_tg(data); flash(f'Пустая папка "{folder_name}" успешно удалена.')
- except Exception as e: flash('Ошибка сохранения данных после удаления папки.', 'error'); logging.error(f"Delete empty folder save error: {e}")
- else: flash('Не удалось удалить папку из базы данных.', 'error')
-
- # Redirect to the parent of the deleted folder
- redirect_to_folder_id = parent_node.get('id', 'root')
- return redirect(url_for('dashboard', folder_id=redirect_to_folder_id))
+ elif folder_node.get('children'):
+ flash(f'Папку "{folder_node.get("name", "папка")}" можно удалить только если она пуста.', 'error')
+ else:
+ if remove_node(user_data['filesystem'], folder_id):
+ try: save_data(data); flash(f'Папка "{folder_node.get("name", "папка")}" удалена.')
+ except Exception as e: flash('Ошибка сохранения данных.', 'error'); logging.error(f"Delete folder save error: {e}")
+ else: flash('Не удалось удалить папку.', 'error')
+ return redirect(url_for('dashboard', folder_id=current_view_folder_id))
@app.route('/get_text_content/')
def get_text_content(file_id):
- is_admin_access = is_current_user_admin()
- if 'telegram_user_id' not in session and not is_admin_access:
- return Response("Не авторизован", status=401)
-
- data = load_data_tg()
- file_node = None; owner_tg_user_id = None
+ allow_access = False
+ current_user_tg_id_str = str(session.get('telegram_user_id')) if 'telegram_user_id' in session else None
+
+ data = load_data()
+ file_node = None
+ is_current_user_admin = is_admin()
- if 'telegram_user_id' in session:
- current_tg_user_id = session['telegram_user_id']
- user_data = data['users'].get(current_tg_user_id)
+ if current_user_tg_id_str:
+ user_data = data['users'].get(current_user_tg_id_str)
if user_data:
- file_node, _ = find_node_by_id(user_data['filesystem'], file_id)
- if file_node and file_node.get('file_type') == 'text': owner_tg_user_id = current_tg_user_id
-
- if not file_node and is_admin_access:
- logging.info(f"Admin text content: searching for file ID {file_id}")
- for tg_id, u_data in data.get('users', {}).items():
- node, _ = find_node_by_id(u_data.get('filesystem', {}), file_id)
+ _file_node, _ = find_node_by_id(user_data.get('filesystem', {}), file_id)
+ if _file_node and _file_node.get('type') == 'file' and _file_node.get('file_type') == 'text':
+ file_node = _file_node; allow_access = True
+
+ if not file_node and is_current_user_admin:
+ for tg_id, udata in data.get('users', {}).items():
+ if tg_id == current_user_tg_id_str: continue
+ node, _ = find_node_by_id(udata.get('filesystem', {}), file_id)
if node and node.get('type') == 'file' and node.get('file_type') == 'text':
- file_node = node; owner_tg_user_id = tg_id
- logging.info(f"Admin found text file ID {file_id} belonging to user {owner_tg_user_id}")
- break
-
- if not file_node or file_node.get('type') != 'file' or file_node.get('file_type') != 'text':
- return Response("Текстовый файл не найден", status=404)
+ file_node = node; allow_access = True; break
+ if not allow_access or not file_node: return Response("Текстовый файл не найден или доступ запрещен", status=404)
+
hf_path = file_node.get('path')
if not hf_path: return Response("Ошибка: путь к файлу отсутствует", status=500)
+
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{hf_path}?download=true"
try:
headers = {};
if HF_TOKEN_READ: headers["authorization"] = f"Bearer {HF_TOKEN_READ}"
- response = requests.get(file_url, headers=headers)
+ response = requests.get(file_url, headers=headers, timeout=10)
response.raise_for_status()
if len(response.content) > 1 * 1024 * 1024: return Response("Файл слишком большой для предпросмотра.", status=413)
try: text_content = response.content.decode('utf-8')
except UnicodeDecodeError: text_content = response.content.decode('latin-1', errors='replace')
return Response(text_content, mimetype='text/plain')
- except requests.exceptions.RequestException as e: logging.error(f"Error fetching text content from HF ({hf_path}): {e}"); return Response(f"Ошибка загрузки содержимого: {e}", status=502)
- except Exception as e: logging.error(f"Unexpected error fetching text content ({hf_path}): {e}"); return Response("Внутренняя ошибка сервера", status=500)
-
-@app.route('/logout')
-def logout():
- session.clear()
- flash('Вы успешно вышли из сессии. Перезапустите приложение из Telegram.')
- # Redirect to launch page, which will re-auth or show message
- return redirect(url_for('launch_webapp'))
-
+ except requests.exceptions.RequestException as e: return Response(f"Ошибка загрузки: {e}", status=502)
+ except Exception as e: return Response("Внутренняя ошибка", status=500)
-# --- Admin Panel Routes ---
+# --- Admin Routes ---
@app.route('/admhosto')
def admin_panel():
- if not is_current_user_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('dashboard'))
- data = load_data_tg(); users = data.get('users', {})
+ if not is_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('mini_app_entry'))
+ data = load_data()
+ users = data.get('users', {})
user_details = []
- for tg_id_str, u_data in users.items():
- file_count = 0
- q = [u_data.get('filesystem', {}).get('children', [])]
+ for tg_id_str, udata in users.items():
+ file_count = 0; q = [udata.get('filesystem', {}).get('children', [])]
while q:
current_level = q.pop(0)
for item in current_level:
if item.get('type') == 'file': file_count += 1
elif item.get('type') == 'folder' and 'children' in item: q.append(item.get('children', []))
-
- display_name = u_data.get('telegram_raw', {}).get('first_name', f"User {tg_id_str}")
- if u_data.get('telegram_raw', {}).get('last_name'):
- display_name += f" {u_data.get('telegram_raw', {}).get('last_name')}"
- if not display_name.strip() and u_data.get('telegram_raw', {}).get('username'):
- display_name = u_data.get('telegram_raw', {}).get('username')
-
user_details.append({
- 'telegram_id_str': tg_id_str,
- 'display_name': display_name,
- 'created_at': u_data.get('created_at', 'N/A'),
- 'file_count': file_count
+ 'telegram_user_id': tg_id_str,
+ 'display_name': udata.get('tg_first_name', udata.get('tg_username', f"TGID: {tg_id_str}")),
+ 'created_at': udata.get('created_at', 'N/A'), 'file_count': file_count
})
- return render_template_string(HTML_ADMIN_PANEL, user_details=user_details)
-
-@app.route('/admhosto/user/')
-def admin_user_files(telegram_user_id_str):
- if not is_current_user_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('dashboard'))
- data = load_data_tg(); user_data = data.get('users', {}).get(telegram_user_id_str)
- if not user_data: flash(f'Пользователь {telegram_user_id_str} не найден.', 'error'); return redirect(url_for('admin_panel'))
-
- display_name = user_data.get('telegram_raw', {}).get('first_name', f"User {telegram_user_id_str}")
- if user_data.get('telegram_raw', {}).get('last_name'): display_name += f" {user_data.get('telegram_raw', {}).get('last_name')}"
+ html = '''
+Админ-панель
+
Админ-панель
+
+{% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}
+ Скачать
+ {% set previewable = file.file_type in ['image', 'video', 'pdf', 'text'] %}
+ {% if previewable %}{% endif %}
+
+{% else %}
У пользователя нет файлов.
{% endfor %}
+
+ ×
+'''
+ return render_template_string(html, telegram_user_id=telegram_user_id, user_display_name=user_display_name, files=all_files, repo_id=REPO_ID,
+ hf_file_url=lambda path, download=False: f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{path}{'?download=true' if download else ''}")
-@app.route('/admhosto/delete_user/', methods=['POST'])
-def admin_delete_user(telegram_user_id_str):
- if not is_current_user_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('dashboard'))
- if not HF_TOKEN_WRITE: flash('Удаление невозможно: токен для записи не настроен.', 'error'); return redirect(url_for('admin_panel'))
+@app.route('/admhosto/delete_user/', methods=['POST'])
+def admin_delete_user(telegram_user_id):
+ if not is_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('mini_app_entry'))
+ if not HF_TOKEN_WRITE: flash('Удаление невозможно: токен не настроен.', 'error'); return redirect(url_for('admin_panel'))
- data = load_data_tg()
- if telegram_user_id_str not in data['users']: flash('Пользователь не найден!', 'error'); return redirect(url_for('admin_panel'))
+ data = load_data()
+ if telegram_user_id not in data['users']: flash('Пользователь не найден!', 'error'); return redirect(url_for('admin_panel'))
- logging.warning(f"ADMIN ACTION: Attempting to delete user {telegram_user_id_str} and all their data.")
+ logging.warning(f"ADMIN ACTION: Attempting to delete user TGID {telegram_user_id} and all their data.")
try:
- api = HfApi()
- user_folder_path_on_hf = f"cloud_files/{telegram_user_id_str}" # Path on HF based on TG ID
- logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user {telegram_user_id_str}")
- api.delete_folder(folder_path=user_folder_path_on_hf, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, commit_message=f"ADMIN ACTION: Deleted all files/folders for user {telegram_user_id_str}")
+ api = HfApi(); user_folder_path_on_hf = f"cloud_files/{telegram_user_id}"
+ logging.info(f"Attempting to delete HF Hub folder: {user_folder_path_on_hf} for user TGID {telegram_user_id}")
+ api.delete_folder(folder_path=user_folder_path_on_hf, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE,
+ commit_message=f"ADMIN ACTION: Deleted all files/folders for user TGID {telegram_user_id}")
logging.info(f"Successfully initiated deletion of folder {user_folder_path_on_hf} on HF Hub.")
except hf_utils.HfHubHTTPError as e:
- if e.response.status_code == 404: logging.warning(f"User folder {user_folder_path_on_hf} not found on HF Hub for user {telegram_user_id_str}. Skipping HF deletion.")
- else: logging.error(f"Error deleting user folder {user_folder_path_on_hf} from HF Hub for {telegram_user_id_str}: {e}"); flash(f'Ошибка при удалении файлов пользователя {telegram_user_id_str} с сервера: {e}. Пользователь НЕ удален из базы.', 'error'); return redirect(url_for('admin_panel'))
- except Exception as e: logging.error(f"Unexpected error during HF Hub folder deletion for {telegram_user_id_str}: {e}"); flash(f'Неожиданная ошибка при удалении файлов {telegram_user_id_str} с сервера: {e}. Пользователь НЕ удален из базы.', 'error'); return redirect(url_for('admin_panel'))
+ if e.response.status_code == 404: logging.warning(f"User folder {user_folder_path_on_hf} not found on HF Hub.")
+ else: logging.error(f"Error deleting user folder {user_folder_path_on_hf} from HF Hub: {e}"); flash(f'Ошибка удаления файлов с сервера: {e}. Пользователь НЕ удален из базы.', 'error'); return redirect(url_for('admin_panel'))
+ except Exception as e: logging.error(f"Unexpected error during HF folder deletion: {e}"); flash(f'Неожиданная ошибка удаления файлов с сервера: {e}. Пользователь НЕ удален из базы.', 'error'); return redirect(url_for('admin_panel'))
try:
- del data['users'][telegram_user_id_str]
- save_data_tg(data)
- flash(f'Пользователь {telegram_user_id_str} и его файлы (запрос на удаление отправлен) успешно удалены из базы данных!')
- logging.info(f"ADMIN ACTION: Successfully deleted user {telegram_user_id_str} from database.")
- except Exception as e: logging.error(f"Error saving data after deleting user {telegram_user_id_str}: {e}"); flash(f'Файлы пользователя {telegram_user_id_str} удалены с сервера, но произошла ошибка при удалении пользователя из базы данных: {e}', 'error')
+ del data['users'][telegram_user_id]; save_data(data)
+ flash(f'Пользователь TGID {telegram_user_id} и его файлы удалены/запрошены к удалению!')
+ logging.info(f"ADMIN ACTION: Successfully deleted user TGID {telegram_user_id} from database.")
+ except Exception as e: logging.error(f"Error saving data after deleting user {telegram_user_id}: {e}"); flash(f'Файлы удалены с сервера, но ошибка удаления из базы: {e}', 'error')
return redirect(url_for('admin_panel'))
-@app.route('/admhosto/delete_file//', methods=['POST'])
-def admin_delete_file(telegram_user_id_str, file_id):
- if not is_current_user_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('dashboard'))
- if not HF_TOKEN_WRITE: flash('Удаление невозможно: токен для записи не настроен.', 'error'); return redirect(url_for('admin_user_files', telegram_user_id_str=telegram_user_id_str))
+@app.route('/admhosto/delete_file//', methods=['POST'])
+def admin_delete_file(telegram_user_id, file_id):
+ if not is_admin(): flash('Доступ запрещен.', 'error'); return redirect(url_for('mini_app_entry'))
+ if not HF_TOKEN_WRITE: flash('Удаление невозможно: токен не настроен.', 'error'); return redirect(url_for('admin_user_files', telegram_user_id=telegram_user_id))
- data = load_data_tg(); user_data = data.get('users', {}).get(telegram_user_id_str)
- if not user_data: flash(f'Пользователь {telegram_user_id_str} не найден.', 'error'); return redirect(url_for('admin_panel'))
+ data = load_data()
+ user_data = data.get('users', {}).get(telegram_user_id)
+ if not user_data: flash(f'Пользователь {telegram_user_id} не найден.', 'error'); return redirect(url_for('admin_panel'))
+
+ file_node, _ = find_node_by_id(user_data.get('filesystem',{}), file_id)
+ if not file_node or file_node.get('type') != 'file': flash('Файл не найден.', 'error'); return redirect(url_for('admin_user_files', telegram_user_id=telegram_user_id))
- file_node, parent_node = find_node_by_id(user_data['filesystem'], file_id)
- if not file_node or file_node.get('type') != 'file' or not parent_node: flash('Файл не найден в структуре пользователя.', 'error'); return redirect(url_for('admin_user_files', telegram_user_id_str=telegram_user_id_str))
-
hf_path = file_node.get('path')
original_filename = file_node.get('original_filename', 'файл')
- if not hf_path:
- flash(f'Ошибка: Путь к файлу {original_filename} не найден. Удаление только из базы.', 'error')
- if remove_node(user_data['filesystem'], file_id):
- try: save_data_tg(data); flash(f'Метаданные файла {original_filename} удалены (путь отсутствовал).')
- except Exception as e: flash('Ошибка сохранения данных.', 'error'); logging.error(f"Admin delete file metadata save error (no path): {e}")
- return redirect(url_for('admin_user_files', telegram_user_id_str=telegram_user_id_str))
-
try:
- api = HfApi()
- api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, commit_message=f"ADMIN ACTION: Deleted file {original_filename} for user {telegram_user_id_str}")
- logging.info(f"ADMIN ACTION: Deleted file {hf_path} from HF Hub for user {telegram_user_id_str}")
- if remove_node(user_data['filesystem'], file_id):
- try: save_data_tg(data); flash(f'Файл {original_filename} успешно удален!')
- except Exception as e: flash('Файл удален с сервера, но ошибка обновления БД.', 'error'); logging.error(f"Admin delete file DB update error: {e}")
- else: flash('Файл удален с сервера, но не найден в БД.', 'error')
+ if hf_path:
+ api = HfApi()
+ api.delete_file(path_in_repo=hf_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE,
+ commit_message=f"ADMIN ACTION: Deleted file {original_filename} for user TGID {telegram_user_id}")
+ if remove_node(user_data['filesystem'], file_id): save_data(data)
+ flash(f'Файл {original_filename} удален!')
except hf_utils.EntryNotFoundError:
- logging.warning(f"ADMIN ACTION: File {hf_path} not found on HF Hub for user {telegram_user_id_str}. Removing from DB.")
- if remove_node(user_data['filesystem'], file_id):
- try: save_data_tg(data); flash(f'Файл {original_filename} не найден на сервере, удален из базы.')
- except Exception as e: flash('Ошибка сохранения данных.', 'error'); logging.error(f"Admin delete file metadata save error (HF not found): {e}")
- else: flash('Файл не найден ни на сервере, ни в базе данных.', 'error')
- except Exception as e: logging.error(f"ADMIN ACTION: Error deleting file {hf_path} for {telegram_user_id_str}: {e}"); flash(f'Ошибка удаления файла {original_filename}: {e}', 'error')
- return redirect(url_for('admin_user_files', telegram_user_id_str=telegram_user_id_str))
-
-
-# --- Main Execution & Bot Runner ---
-async def run_bot(application_bot):
- logging.info("Initializing Telegram bot...")
- await application_bot.initialize()
- logging.info("Starting Telegram bot polling...")
- await application_bot.start()
- await application_bot.updater.start_polling()
- logging.info("Telegram bot is running.")
-
-def bot_thread_target(bot_app_instance):
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
- try:
- loop.run_until_complete(run_bot(bot_app_instance))
- except KeyboardInterrupt:
- logging.info("Bot polling interrupted by user.")
+ if remove_node(user_data['filesystem'], file_id): save_data(data)
+ flash(f'Файл {original_filename} не найден на сервере, удален из базы.')
except Exception as e:
- logging.error(f"Exception in bot thread: {e}", exc_info=True)
- finally:
- # Cleanly stop the bot if polling was started
- if bot_app_instance.updater and bot_app_instance.updater.running:
- loop.run_until_complete(bot_app_instance.updater.stop())
- loop.run_until_complete(bot_app_instance.stop()) # Ensure bot stops
- loop.close()
- logging.info("Bot thread finished.")
-
+ logging.error(f"ADMIN ACTION: Error deleting file {hf_path} for TGID {telegram_user_id}: {e}")
+ flash(f'Ошибка удаления файла {original_filename}: {e}', 'error')
+ return redirect(url_for('admin_user_files', telegram_user_id=telegram_user_id))
if __name__ == '__main__':
- if ADMIN_TELEGRAM_ID == "YOUR_ADMIN_TELEGRAM_ID_REPLACE_ME":
- logging.warning("ADMIN_TELEGRAM_ID is not set. Admin panel functionality will not work correctly for any user.")
- if WEBAPP_URL == "https://example.com/launch_webapp":
- logging.warning("WEBAPP_URL is not set to your actual deployment URL. Telegram bot button might not work.")
+ if not BOT_TOKEN: logging.critical("BOT_TOKEN is not set. Telegram authentication will FAIL.")
+ if not ADMIN_TELEGRAM_USER_ID: logging.warning("ADMIN_TELEGRAM_USER_ID is not set. Admin panel will be inaccessible.")
if not HF_TOKEN_WRITE: logging.warning("HF_TOKEN (write access) is not set. File uploads, deletions, and backups will fail.")
- if not HF_TOKEN_READ: logging.warning("HF_TOKEN_READ is not set. Falling back to HF_TOKEN. File downloads/previews might fail for private repos if HF_TOKEN is also not set.")
-
- # Initial DB download
- if HF_TOKEN_WRITE or HF_TOKEN_READ:
- logging.info("Performing initial database download before starting.")
- download_db_from_hf()
- else: # No tokens, create local empty if not exists
- if not os.path.exists(DATA_FILE):
- with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
- logging.info(f"Created empty local database file: {DATA_FILE} (no HF tokens provided).")
+ if not HF_TOKEN_READ: logging.warning("HF_TOKEN_READ is not set (or HF_TOKEN). Downloads/previews might fail for private repos.")
- # Start periodic backup if write token exists
if HF_TOKEN_WRITE:
- backup_thread = threading.Thread(target=periodic_backup, daemon=True)
- backup_thread.start()
+ logging.info("Performing initial database download before starting background backup.")
+ download_db_from_hf() # Download once at start
+ threading.Thread(target=periodic_backup, daemon=True).start()
logging.info("Periodic backup thread started.")
else:
- logging.warning("Periodic backup disabled because HF_TOKEN (write access) is not set.")
-
- # Setup and start Telegram bot
- ptb_application = Application.builder().token(BOT_TOKEN).build()
- ptb_application.add_handler(CommandHandler("start", start_command_handler))
- ptb_application.add_handler(MessageHandler(filters.COMMAND, unknown_command_handler)) # Handles any other command
-
- bot_main_thread = threading.Thread(target=bot_thread_target, args=(ptb_application,), daemon=True)
- bot_main_thread.start()
-
- logging.info("Starting Flask application...")
- # Use a production-ready WSGI server like Gunicorn or Waitress instead of app.run() for production
- app.run(debug=False, host='0.0.0.0', port=7860)
\ No newline at end of file
+ logging.warning("Periodic backup disabled (HF_TOKEN_WRITE not set).")
+ if HF_TOKEN_READ: download_db_from_hf()
+ elif not os.path.exists(DATA_FILE): # No tokens and no local file
+ with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'users': {}}, f)
+ logging.info(f"Created empty local database file: {DATA_FILE}")
+
+ app.run(debug=False, host='0.0.0.0', port=int(os.getenv("PORT", 7860)))
\ No newline at end of file