Update app.py
Browse files
app.py
CHANGED
|
@@ -450,8 +450,7 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
|
|
| 450 |
modal.style.display = 'flex';
|
| 451 |
|
| 452 |
if (type !== 'folder' && itemId) {
|
| 453 |
-
|
| 454 |
-
downloadBtn.onclick = () => { tmaDownloadFile(downloadUrl); };
|
| 455 |
downloadBtn.style.display = 'inline-block';
|
| 456 |
} else {
|
| 457 |
downloadBtn.style.display = 'none';
|
|
@@ -465,7 +464,7 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
|
|
| 465 |
const response = await fetch(srcOrUrl); if (!response.ok) throw new Error(`Ошибка: ${response.statusText}`);
|
| 466 |
const text = await response.text();
|
| 467 |
modalContent.innerHTML = `<pre>${text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`;
|
| 468 |
-
} else
|
| 469 |
} catch (error) { modalContent.innerHTML = `<p>Ошибка предпросмотра: ${error.message}</p>`; }
|
| 470 |
}
|
| 471 |
|
|
@@ -478,8 +477,23 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
|
|
| 478 |
document.getElementById('modal-download-btn').style.display = 'none';
|
| 479 |
}
|
| 480 |
|
| 481 |
-
function
|
| 482 |
haptic.impactOccurred('medium');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
if (window.Telegram && window.Telegram.WebApp && Telegram.WebApp.openLink) { Telegram.WebApp.openLink(downloadUrl, {try_instant_view: false}); }
|
| 484 |
else { window.open(downloadUrl, '_blank'); }
|
| 485 |
}
|
|
@@ -569,8 +583,7 @@ TMA_DASHBOARD_HTML_TEMPLATE = '''
|
|
| 569 |
function downloadSingleSelected() {
|
| 570 |
if (selectedItems.size !== 1) return;
|
| 571 |
const fileId = selectedItems.values().next().value;
|
| 572 |
-
|
| 573 |
-
tmaDownloadFile(url);
|
| 574 |
toggleSelectionMode(false);
|
| 575 |
}
|
| 576 |
|
|
@@ -755,47 +768,45 @@ def get_file_node_for_admin(tma_user_id_str, file_id):
|
|
| 755 |
def download_tma(file_id):
|
| 756 |
file_node = get_file_node_for_user(file_id)
|
| 757 |
if not file_node:
|
| 758 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
|
| 760 |
hf_path = file_node.get('path')
|
| 761 |
original_filename = file_node.get('original_filename', 'downloaded_file')
|
| 762 |
if not hf_path:
|
| 763 |
return Response("Ошибка: Путь к файлу не найден.", status=500)
|
| 764 |
|
| 765 |
-
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{quote(hf_path)}?download=true"
|
| 766 |
-
|
| 767 |
try:
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
if 'content-length' in r.headers:
|
| 784 |
-
response_headers['Content-Length'] = r.headers['content-length']
|
| 785 |
-
|
| 786 |
-
return Response(generate(), headers=response_headers)
|
| 787 |
-
|
| 788 |
-
except requests.exceptions.HTTPError as e:
|
| 789 |
-
logging.error(f"File not found on Hugging Face (HTTPError): {hf_path} - {e}")
|
| 790 |
-
if e.response.status_code == 404:
|
| 791 |
-
return Response("Файл не найден на удаленн��м хранилище.", status=404)
|
| 792 |
-
else:
|
| 793 |
-
return Response(f"Ошибка при доступе к файлу: {e}", status=e.response.status_code)
|
| 794 |
except Exception as e:
|
| 795 |
-
logging.error(f"Error
|
| 796 |
return Response(f'Ошибка скачивания файла: {e}', status=502)
|
| 797 |
|
| 798 |
-
|
| 799 |
@app.route('/batch_download_tma')
|
| 800 |
def batch_download_tma():
|
| 801 |
if 'telegram_user_id' not in session: return Response("Unauthorized", 401)
|
|
@@ -1127,13 +1138,33 @@ ADMIN_USER_FILES_HTML = '''
|
|
| 1127 |
<span onclick="closeModalManual()" class="modal-close-btn">×</span>
|
| 1128 |
<div class="modal-main-content" id="modalContent"></div>
|
| 1129 |
<div class="modal-actions">
|
| 1130 |
-
<a id="modal-download-btn" class="btn download-btn" style="display: none; width: 80%;">
|
| 1131 |
<i class="fa-solid fa-download"></i> Download
|
| 1132 |
</a>
|
| 1133 |
</div>
|
| 1134 |
</div>
|
| 1135 |
</div>
|
| 1136 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1137 |
async function openModal(srcOrUrl, type, itemId) {
|
| 1138 |
if (!srcOrUrl) return;
|
| 1139 |
const modal = document.getElementById('mediaModal');
|
|
@@ -1143,8 +1174,7 @@ ADMIN_USER_FILES_HTML = '''
|
|
| 1143 |
modal.style.display = 'flex';
|
| 1144 |
|
| 1145 |
if (type !== 'folder' && itemId) {
|
| 1146 |
-
|
| 1147 |
-
downloadBtn.href = downloadUrl;
|
| 1148 |
downloadBtn.style.display = 'inline-block';
|
| 1149 |
} else {
|
| 1150 |
downloadBtn.style.display = 'none';
|
|
@@ -1158,7 +1188,7 @@ ADMIN_USER_FILES_HTML = '''
|
|
| 1158 |
const response = await fetch(srcOrUrl); if (!response.ok) throw new Error(`Error: ${response.statusText}`);
|
| 1159 |
const text = await response.text();
|
| 1160 |
modalContent.innerHTML = `<pre>${text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`;
|
| 1161 |
-
} else
|
| 1162 |
} catch (error) { modalContent.innerHTML = `<p>Preview Error: ${error.message}</p>`; }
|
| 1163 |
}
|
| 1164 |
function closeModal(event) { if (event.target.id === 'mediaModal') closeModalManual(); }
|
|
@@ -1285,46 +1315,12 @@ def admin_user_files(tma_user_id_str):
|
|
| 1285 |
def admin_download_file(tma_user_id_str, file_id):
|
| 1286 |
file_node = get_file_node_for_admin(tma_user_id_str, file_id)
|
| 1287 |
if not file_node:
|
| 1288 |
-
return
|
| 1289 |
-
|
| 1290 |
-
hf_path = file_node.get('path')
|
| 1291 |
-
original_filename = file_node.get('original_filename', 'downloaded_file')
|
| 1292 |
-
if not hf_path:
|
| 1293 |
-
return Response("Error: File path not found.", status=500)
|
| 1294 |
-
|
| 1295 |
-
file_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{quote(hf_path)}?download=true"
|
| 1296 |
-
|
| 1297 |
-
try:
|
| 1298 |
-
req_headers = {}
|
| 1299 |
-
if HF_TOKEN_READ:
|
| 1300 |
-
req_headers["Authorization"] = f"Bearer {HF_TOKEN_READ}"
|
| 1301 |
-
|
| 1302 |
-
r = requests.get(file_url, headers=req_headers, stream=True)
|
| 1303 |
-
r.raise_for_status()
|
| 1304 |
-
|
| 1305 |
-
def generate():
|
| 1306 |
-
for chunk in r.iter_content(chunk_size=8192):
|
| 1307 |
-
yield chunk
|
| 1308 |
-
|
| 1309 |
-
response_headers = {
|
| 1310 |
-
'Content-Disposition': f'attachment; filename="{quote(original_filename)}"',
|
| 1311 |
-
'Content-Type': r.headers.get('content-type', 'application/octet-stream')
|
| 1312 |
-
}
|
| 1313 |
-
if 'content-length' in r.headers:
|
| 1314 |
-
response_headers['Content-Length'] = r.headers['content-length']
|
| 1315 |
-
|
| 1316 |
-
return Response(generate(), headers=response_headers)
|
| 1317 |
-
|
| 1318 |
-
except requests.exceptions.HTTPError as e:
|
| 1319 |
-
logging.error(f"Admin: File not found on Hugging Face (HTTPError): {hf_path} - {e}")
|
| 1320 |
-
if e.response.status_code == 404:
|
| 1321 |
-
return Response("File not found in remote storage.", status=404)
|
| 1322 |
-
else:
|
| 1323 |
-
return Response(f"Error accessing file: {e}", status=e.response.status_code)
|
| 1324 |
-
except Exception as e:
|
| 1325 |
-
logging.error(f"Admin: Error streaming download for {file_id}: {e}")
|
| 1326 |
-
return Response(f'Error downloading file: {e}', status=502)
|
| 1327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1328 |
|
| 1329 |
@app.route('/admhosto/text/<tma_user_id_str>/<file_id>')
|
| 1330 |
@admin_browser_login_required
|
|
|
|
| 450 |
modal.style.display = 'flex';
|
| 451 |
|
| 452 |
if (type !== 'folder' && itemId) {
|
| 453 |
+
downloadBtn.onclick = () => { initiateDownload(itemId); };
|
|
|
|
| 454 |
downloadBtn.style.display = 'inline-block';
|
| 455 |
} else {
|
| 456 |
downloadBtn.style.display = 'none';
|
|
|
|
| 464 |
const response = await fetch(srcOrUrl); if (!response.ok) throw new Error(`Ошибка: ${response.statusText}`);
|
| 465 |
const text = await response.text();
|
| 466 |
modalContent.innerHTML = `<pre>${text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`;
|
| 467 |
+
} else initiateDownload(itemId);
|
| 468 |
} catch (error) { modalContent.innerHTML = `<p>Ошибка предпросмотра: ${error.message}</p>`; }
|
| 469 |
}
|
| 470 |
|
|
|
|
| 477 |
document.getElementById('modal-download-btn').style.display = 'none';
|
| 478 |
}
|
| 479 |
|
| 480 |
+
async function initiateDownload(fileId) {
|
| 481 |
haptic.impactOccurred('medium');
|
| 482 |
+
try {
|
| 483 |
+
const response = await fetch(`{{ url_for('download_tma', file_id='__FILE_ID__') }}`.replace('__FILE_ID__', fileId));
|
| 484 |
+
const data = await response.json();
|
| 485 |
+
if (data.status === 'success' && data.url) {
|
| 486 |
+
tmaDownloadFile(data.url);
|
| 487 |
+
closeModalManual();
|
| 488 |
+
} else {
|
| 489 |
+
Telegram.WebApp.showAlert(data.message || 'Не удалось создать ссылку для скачивания.');
|
| 490 |
+
}
|
| 491 |
+
} catch (error) {
|
| 492 |
+
Telegram.WebApp.showAlert('Сетевая ошибка при создании ссылки для скачивания.');
|
| 493 |
+
}
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
function tmaDownloadFile(downloadUrl) {
|
| 497 |
if (window.Telegram && window.Telegram.WebApp && Telegram.WebApp.openLink) { Telegram.WebApp.openLink(downloadUrl, {try_instant_view: false}); }
|
| 498 |
else { window.open(downloadUrl, '_blank'); }
|
| 499 |
}
|
|
|
|
| 583 |
function downloadSingleSelected() {
|
| 584 |
if (selectedItems.size !== 1) return;
|
| 585 |
const fileId = selectedItems.values().next().value;
|
| 586 |
+
initiateDownload(fileId);
|
|
|
|
| 587 |
toggleSelectionMode(false);
|
| 588 |
}
|
| 589 |
|
|
|
|
| 768 |
def download_tma(file_id):
|
| 769 |
file_node = get_file_node_for_user(file_id)
|
| 770 |
if not file_node:
|
| 771 |
+
return jsonify({'status': 'error', 'message': 'Файл не найден или доступ запрещен'}), 404
|
| 772 |
+
|
| 773 |
+
token = uuid.uuid4().hex
|
| 774 |
+
cache.set(f"download_token_{token}", file_node, timeout=60)
|
| 775 |
+
public_url = url_for('public_download', token=token, _external=True)
|
| 776 |
+
return jsonify({'status': 'success', 'url': public_url})
|
| 777 |
+
|
| 778 |
+
@app.route('/public_download/<token>')
|
| 779 |
+
def public_download(token):
|
| 780 |
+
file_node = cache.get(f"download_token_{token}")
|
| 781 |
+
cache.delete(f"download_token_{token}")
|
| 782 |
+
if not file_node:
|
| 783 |
+
return Response("Ссылка для скачивания недействительна или истекла.", status=404)
|
| 784 |
|
| 785 |
hf_path = file_node.get('path')
|
| 786 |
original_filename = file_node.get('original_filename', 'downloaded_file')
|
| 787 |
if not hf_path:
|
| 788 |
return Response("Ошибка: Путь к файлу не найден.", status=500)
|
| 789 |
|
|
|
|
|
|
|
| 790 |
try:
|
| 791 |
+
local_file_path = hf_hub_download(
|
| 792 |
+
repo_id=REPO_ID,
|
| 793 |
+
filename=hf_path,
|
| 794 |
+
repo_type="dataset",
|
| 795 |
+
token=HF_TOKEN_READ,
|
| 796 |
+
cache_dir=os.path.join(UPLOAD_FOLDER, 'hf_download_cache')
|
| 797 |
+
)
|
| 798 |
+
return send_file(
|
| 799 |
+
local_file_path,
|
| 800 |
+
as_attachment=True,
|
| 801 |
+
download_name=original_filename
|
| 802 |
+
)
|
| 803 |
+
except hf_utils.EntryNotFoundError:
|
| 804 |
+
logging.error(f"File not found on Hugging Face: {hf_path}")
|
| 805 |
+
return Response("Файл не найден на удаленном хранилище.", status=404)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 806 |
except Exception as e:
|
| 807 |
+
logging.error(f"Error downloading with token {token} from HF: {e}")
|
| 808 |
return Response(f'Ошибка скачивания файла: {e}', status=502)
|
| 809 |
|
|
|
|
| 810 |
@app.route('/batch_download_tma')
|
| 811 |
def batch_download_tma():
|
| 812 |
if 'telegram_user_id' not in session: return Response("Unauthorized", 401)
|
|
|
|
| 1138 |
<span onclick="closeModalManual()" class="modal-close-btn">×</span>
|
| 1139 |
<div class="modal-main-content" id="modalContent"></div>
|
| 1140 |
<div class="modal-actions">
|
| 1141 |
+
<a id="modal-download-btn" class="btn download-btn" href="javascript:void(0);" style="display: none; width: 80%;">
|
| 1142 |
<i class="fa-solid fa-download"></i> Download
|
| 1143 |
</a>
|
| 1144 |
</div>
|
| 1145 |
</div>
|
| 1146 |
</div>
|
| 1147 |
<script>
|
| 1148 |
+
async function initiateDownload(fileId) {
|
| 1149 |
+
const downloadBtn = document.getElementById('modal-download-btn');
|
| 1150 |
+
const originalHTML = downloadBtn.innerHTML;
|
| 1151 |
+
downloadBtn.innerHTML = '<div class="loading-spinner" style="width:20px; height:20px; border-width:2px;"></div>';
|
| 1152 |
+
try {
|
| 1153 |
+
const response = await fetch(`{{ url_for('admin_download_file', tma_user_id_str=user_id, file_id='__FILE_ID__') }}`.replace('__FILE_ID__', fileId));
|
| 1154 |
+
const data = await response.json();
|
| 1155 |
+
if (data.status === 'success' && data.url) {
|
| 1156 |
+
window.open(data.url, '_blank');
|
| 1157 |
+
} else {
|
| 1158 |
+
alert(data.message || 'Failed to create download link.');
|
| 1159 |
+
}
|
| 1160 |
+
} catch (error) {
|
| 1161 |
+
alert('Network error while creating download link.');
|
| 1162 |
+
} finally {
|
| 1163 |
+
downloadBtn.innerHTML = originalHTML;
|
| 1164 |
+
closeModalManual();
|
| 1165 |
+
}
|
| 1166 |
+
}
|
| 1167 |
+
|
| 1168 |
async function openModal(srcOrUrl, type, itemId) {
|
| 1169 |
if (!srcOrUrl) return;
|
| 1170 |
const modal = document.getElementById('mediaModal');
|
|
|
|
| 1174 |
modal.style.display = 'flex';
|
| 1175 |
|
| 1176 |
if (type !== 'folder' && itemId) {
|
| 1177 |
+
downloadBtn.onclick = () => initiateDownload(itemId);
|
|
|
|
| 1178 |
downloadBtn.style.display = 'inline-block';
|
| 1179 |
} else {
|
| 1180 |
downloadBtn.style.display = 'none';
|
|
|
|
| 1188 |
const response = await fetch(srcOrUrl); if (!response.ok) throw new Error(`Error: ${response.statusText}`);
|
| 1189 |
const text = await response.text();
|
| 1190 |
modalContent.innerHTML = `<pre>${text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`;
|
| 1191 |
+
} else initiateDownload(itemId);
|
| 1192 |
} catch (error) { modalContent.innerHTML = `<p>Preview Error: ${error.message}</p>`; }
|
| 1193 |
}
|
| 1194 |
function closeModal(event) { if (event.target.id === 'mediaModal') closeModalManual(); }
|
|
|
|
| 1315 |
def admin_download_file(tma_user_id_str, file_id):
|
| 1316 |
file_node = get_file_node_for_admin(tma_user_id_str, file_id)
|
| 1317 |
if not file_node:
|
| 1318 |
+
return jsonify({'status': 'error', 'message': 'File not found or access denied!'}), 404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1319 |
|
| 1320 |
+
token = uuid.uuid4().hex
|
| 1321 |
+
cache.set(f"download_token_{token}", file_node, timeout=60)
|
| 1322 |
+
public_url = url_for('public_download', token=token, _external=True)
|
| 1323 |
+
return jsonify({'status': 'success', 'url': public_url})
|
| 1324 |
|
| 1325 |
@app.route('/admhosto/text/<tma_user_id_str>/<file_id>')
|
| 1326 |
@admin_browser_login_required
|