Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -3,7 +3,9 @@ from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
|
|
3 |
import requests
|
4 |
import time
|
5 |
import asyncio
|
6 |
-
from typing import Dict
|
|
|
|
|
7 |
|
8 |
app = FastAPI()
|
9 |
|
@@ -418,7 +420,7 @@ HTML_CONTENT = """
|
|
418 |
overflow: auto;
|
419 |
}
|
420 |
|
421 |
-
|
422 |
margin: 5% auto;
|
423 |
padding: 20px;
|
424 |
width: 90%;
|
@@ -440,7 +442,7 @@ HTML_CONTENT = """
|
|
440 |
border: none;
|
441 |
}
|
442 |
|
443 |
-
|
444 |
.container {
|
445 |
padding: 1.5rem;
|
446 |
}
|
@@ -518,12 +520,12 @@ HTML_CONTENT = """
|
|
518 |
<h1>Radd PRO Uploader</h1>
|
519 |
<form id="uploadForm">
|
520 |
<div id="dropZone" class="drop-zone">
|
521 |
-
<input type="file" name="
|
522 |
-
<label for="
|
523 |
-
<p>or drag and drop
|
524 |
</div>
|
525 |
<div class="file-name" id="fileName"></div>
|
526 |
-
<button type="submit" id="uploadBtn" class="btn" style="display: none; margin-top: 1rem;">Upload
|
527 |
<div class="progress-container" id="progressContainer"></div>
|
528 |
<div class="loading-spinner" id="loadingSpinner"></div>
|
529 |
</form>
|
@@ -562,7 +564,7 @@ HTML_CONTENT = """
|
|
562 |
</div>
|
563 |
|
564 |
<script>
|
565 |
-
const fileInput = document.getElementById('
|
566 |
const fileName = document.getElementById('fileName');
|
567 |
const uploadForm = document.getElementById('uploadForm');
|
568 |
const progressContainer = document.getElementById('progressContainer');
|
@@ -584,7 +586,7 @@ HTML_CONTENT = """
|
|
584 |
uploadForm.addEventListener('submit', (e) => {
|
585 |
e.preventDefault();
|
586 |
if (fileInput.files.length > 0) {
|
587 |
-
|
588 |
}
|
589 |
});
|
590 |
|
@@ -644,17 +646,13 @@ HTML_CONTENT = """
|
|
644 |
|
645 |
function handleFileSelect(e) {
|
646 |
if (e.target.files && e.target.files.length > 0) {
|
647 |
-
const
|
648 |
-
fileName.textContent =
|
649 |
uploadBtn.style.display = 'inline-block';
|
650 |
-
|
651 |
-
const dataTransfer = new DataTransfer();
|
652 |
-
dataTransfer.items.add(file);
|
653 |
-
fileInput.files = dataTransfer.files;
|
654 |
}
|
655 |
}
|
656 |
|
657 |
-
async function
|
658 |
progressContainer.innerHTML = '';
|
659 |
progressContainer.style.display = 'block';
|
660 |
loadingSpinner.style.display = 'block';
|
@@ -662,73 +660,32 @@ HTML_CONTENT = """
|
|
662 |
resultContainer.innerHTML = '';
|
663 |
resultContainer.style.display = 'none';
|
664 |
|
665 |
-
const progressBar = createProgressBar(file.name);
|
666 |
-
progressContainer.appendChild(progressBar);
|
667 |
-
|
668 |
const formData = new FormData();
|
669 |
-
|
670 |
-
|
671 |
-
while (true) {
|
672 |
-
try {
|
673 |
-
const xhr = new XMLHttpRequest();
|
674 |
-
xhr.open('POST', '/upload', true);
|
675 |
-
xhr.upload.onprogress = (event) => updateProgress(event, progressBar.querySelector('.progress'));
|
676 |
-
|
677 |
-
xhr.onload = function() {
|
678 |
-
if (xhr.status === 200) {
|
679 |
-
const response = JSON.parse(xhr.responseText);
|
680 |
-
if (response.url) {
|
681 |
-
addResultLink(response.url, file.name);
|
682 |
-
saveToHistory(file.name, response.url);
|
683 |
-
resetUploadState();
|
684 |
-
return;
|
685 |
-
} else {
|
686 |
-
throw new Error('Upload failed: ' + response.error);
|
687 |
-
}
|
688 |
-
} else {
|
689 |
-
throw new Error(`HTTP error! status: ${xhr.status}`);
|
690 |
-
}
|
691 |
-
};
|
692 |
-
|
693 |
-
xhr.onerror = function() {
|
694 |
-
throw new Error('Network error occurred');
|
695 |
-
};
|
696 |
-
|
697 |
-
xhr.send(formData);
|
698 |
-
|
699 |
-
await new Promise((resolve, reject) => {
|
700 |
-
xhr.onloadend = resolve;
|
701 |
-
xhr.onerror = reject;
|
702 |
-
});
|
703 |
-
|
704 |
-
break;
|
705 |
-
} catch (error) {
|
706 |
-
console.error('Upload error:', error);
|
707 |
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
708 |
-
}
|
709 |
}
|
710 |
-
}
|
711 |
|
712 |
-
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
|
|
732 |
}
|
733 |
}
|
734 |
|
@@ -738,47 +695,46 @@ HTML_CONTENT = """
|
|
738 |
uploadBtn.style.display = 'none';
|
739 |
uploadBtn.disabled = false;
|
740 |
loadingSpinner.style.display = 'none';
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
const
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
copyBtn.onclick = () => {
|
762 |
-
navigator.clipboard.writeText(window.location.origin + url).then(() => {
|
763 |
-
alert('Link copied to clipboard!');
|
764 |
});
|
765 |
};
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
|
780 |
-
resultContainer.appendChild(
|
781 |
resultContainer.style.display = 'block';
|
|
|
|
|
782 |
}
|
783 |
|
784 |
function showEmbedModal(url) {
|
@@ -793,9 +749,9 @@ HTML_CONTENT = """
|
|
793 |
alert('Embed link copied to clipboard!');
|
794 |
}
|
795 |
|
796 |
-
function saveToHistory(
|
797 |
let history = JSON.parse(localStorage.getItem('uploadHistory')) || [];
|
798 |
-
history.unshift({
|
799 |
if (history.length > 500) history = history.slice(0, 500);
|
800 |
localStorage.setItem('uploadHistory', JSON.stringify(history));
|
801 |
}
|
@@ -809,56 +765,37 @@ HTML_CONTENT = """
|
|
809 |
|
810 |
const itemName = document.createElement('span');
|
811 |
itemName.className = 'history-item-name';
|
812 |
-
itemName.textContent = item.
|
813 |
historyItem.appendChild(itemName);
|
814 |
|
815 |
const actionsContainer = document.createElement('div');
|
816 |
actionsContainer.className = 'history-item-actions';
|
817 |
|
818 |
const copyBtn = document.createElement('button');
|
819 |
-
copyBtn.textContent = 'Copy Link';
|
820 |
copyBtn.className = 'small-btn';
|
821 |
copyBtn.onclick = () => {
|
822 |
-
navigator.clipboard.writeText(window.location.origin + item.
|
823 |
-
alert('
|
824 |
});
|
825 |
};
|
826 |
actionsContainer.appendChild(copyBtn);
|
827 |
|
828 |
const openBtn = document.createElement('button');
|
829 |
-
openBtn.textContent = 'Open';
|
830 |
openBtn.className = 'small-btn';
|
831 |
openBtn.onclick = () => {
|
832 |
-
window.open(window.location.origin + item.
|
833 |
};
|
834 |
actionsContainer.appendChild(openBtn);
|
835 |
|
836 |
-
const quickOpenBtn = document.createElement('button');
|
837 |
-
quickOpenBtn.textContent = 'Quick Open';
|
838 |
-
quickOpenBtn.className = 'small-btn';
|
839 |
-
quickOpenBtn.onclick = () => {
|
840 |
-
quickOpen(item.url, item.fileName);
|
841 |
-
};
|
842 |
-
actionsContainer.appendChild(quickOpenBtn);
|
843 |
-
|
844 |
-
if (item.fileName.toLowerCase().endsWith('.mp4')) {
|
845 |
-
const embedBtn = document.createElement('button');
|
846 |
-
embedBtn.textContent = 'Embed';
|
847 |
-
embedBtn.className = 'small-btn';
|
848 |
-
embedBtn.onclick = () => {
|
849 |
-
showEmbedModal(item.url);
|
850 |
-
historyModal.style.display = "none";
|
851 |
-
};
|
852 |
-
actionsContainer.appendChild(embedBtn);
|
853 |
-
}
|
854 |
-
|
855 |
historyItem.appendChild(actionsContainer);
|
856 |
historyList.appendChild(historyItem);
|
857 |
});
|
858 |
historyModal.style.display = "block";
|
859 |
}
|
860 |
|
861 |
-
|
862 |
quickOpenContent.innerHTML = '';
|
863 |
const fullUrl = window.location.origin + url;
|
864 |
|
@@ -909,31 +846,38 @@ async def index():
|
|
909 |
return HTML_CONTENT
|
910 |
|
911 |
@app.post("/upload")
|
912 |
-
async def handle_upload(
|
913 |
-
if not
|
914 |
-
return JSONResponse(content={"error": "No
|
915 |
|
916 |
cookies = await get_cookies()
|
917 |
if 'csrftoken' not in cookies or 'sessionid' not in cookies:
|
918 |
return JSONResponse(content={"error": "Failed to obtain necessary cookies"}, status_code=500)
|
919 |
|
920 |
-
|
921 |
-
|
922 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
923 |
|
924 |
-
|
925 |
-
|
926 |
-
|
927 |
-
|
928 |
|
929 |
-
|
930 |
-
|
|
|
931 |
|
932 |
-
return JSONResponse(content={"
|
933 |
|
934 |
-
@app.get("/rbxg/{
|
935 |
-
async def
|
936 |
-
original_url = f'https://replicate.delivery/pbxt/{
|
937 |
range_header = request.headers.get('Range')
|
938 |
|
939 |
headers = {'Range': range_header} if range_header else {}
|
@@ -1046,4 +990,4 @@ async def retry_upload(upload_url: str, file_content: bytes, content_type: str,
|
|
1046 |
await asyncio.sleep(delay)
|
1047 |
delay = min(delay * 2, 60) # Exponential backoff, capped at 60 seconds
|
1048 |
|
1049 |
-
return False
|
|
|
3 |
import requests
|
4 |
import time
|
5 |
import asyncio
|
6 |
+
from typing import Dict, List
|
7 |
+
import uuid
|
8 |
+
import os
|
9 |
|
10 |
app = FastAPI()
|
11 |
|
|
|
420 |
overflow: auto;
|
421 |
}
|
422 |
|
423 |
+
.quick-open-content {
|
424 |
margin: 5% auto;
|
425 |
padding: 20px;
|
426 |
width: 90%;
|
|
|
442 |
border: none;
|
443 |
}
|
444 |
|
445 |
+
@media (max-width: 480px) {
|
446 |
.container {
|
447 |
padding: 1.5rem;
|
448 |
}
|
|
|
520 |
<h1>Radd PRO Uploader</h1>
|
521 |
<form id="uploadForm">
|
522 |
<div id="dropZone" class="drop-zone">
|
523 |
+
<input type="file" name="files" id="files" class="file-input" accept=".zip,.mp4,.txt,.mp3,image/*,.pdf" required multiple>
|
524 |
+
<label for="files" class="btn">Choose Files</label>
|
525 |
+
<p>or drag and drop files here/paste images</p>
|
526 |
</div>
|
527 |
<div class="file-name" id="fileName"></div>
|
528 |
+
<button type="submit" id="uploadBtn" class="btn" style="display: none; margin-top: 1rem;">Upload Files</button>
|
529 |
<div class="progress-container" id="progressContainer"></div>
|
530 |
<div class="loading-spinner" id="loadingSpinner"></div>
|
531 |
</form>
|
|
|
564 |
</div>
|
565 |
|
566 |
<script>
|
567 |
+
const fileInput = document.getElementById('files');
|
568 |
const fileName = document.getElementById('fileName');
|
569 |
const uploadForm = document.getElementById('uploadForm');
|
570 |
const progressContainer = document.getElementById('progressContainer');
|
|
|
586 |
uploadForm.addEventListener('submit', (e) => {
|
587 |
e.preventDefault();
|
588 |
if (fileInput.files.length > 0) {
|
589 |
+
uploadFiles(fileInput.files);
|
590 |
}
|
591 |
});
|
592 |
|
|
|
646 |
|
647 |
function handleFileSelect(e) {
|
648 |
if (e.target.files && e.target.files.length > 0) {
|
649 |
+
const fileNames = Array.from(e.target.files).map(file => file.name).join(', ');
|
650 |
+
fileName.textContent = fileNames;
|
651 |
uploadBtn.style.display = 'inline-block';
|
|
|
|
|
|
|
|
|
652 |
}
|
653 |
}
|
654 |
|
655 |
+
async function uploadFiles(files) {
|
656 |
progressContainer.innerHTML = '';
|
657 |
progressContainer.style.display = 'block';
|
658 |
loadingSpinner.style.display = 'block';
|
|
|
660 |
resultContainer.innerHTML = '';
|
661 |
resultContainer.style.display = 'none';
|
662 |
|
|
|
|
|
|
|
663 |
const formData = new FormData();
|
664 |
+
for (let i = 0; i < files.length; i++) {
|
665 |
+
formData.append('files', files[i]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
666 |
}
|
|
|
667 |
|
668 |
+
try {
|
669 |
+
const response = await fetch('/upload', {
|
670 |
+
method: 'POST',
|
671 |
+
body: formData
|
672 |
+
});
|
673 |
+
|
674 |
+
if (response.ok) {
|
675 |
+
const result = await response.json();
|
676 |
+
if (result.folder_url) {
|
677 |
+
addFolderResult(result.folder_url, result.files);
|
678 |
+
} else {
|
679 |
+
throw new Error('Upload failed: No folder URL received');
|
680 |
+
}
|
681 |
+
} else {
|
682 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
683 |
+
}
|
684 |
+
} catch (error) {
|
685 |
+
console.error('Upload error:', error);
|
686 |
+
alert('Upload failed. Please try again.');
|
687 |
+
} finally {
|
688 |
+
resetUploadState();
|
689 |
}
|
690 |
}
|
691 |
|
|
|
695 |
uploadBtn.style.display = 'none';
|
696 |
uploadBtn.disabled = false;
|
697 |
loadingSpinner.style.display = 'none';
|
698 |
+
progressContainer.style.display = 'none';
|
699 |
+
}
|
700 |
+
|
701 |
+
function addFolderResult(folderUrl, files) {
|
702 |
+
const folderContainer = document.createElement('div');
|
703 |
+
folderContainer.className = 'folder-result';
|
704 |
+
|
705 |
+
const folderLink = document.createElement('a');
|
706 |
+
folderLink.href = folderUrl;
|
707 |
+
folderLink.textContent = 'View Folder';
|
708 |
+
folderLink.className = 'result-link';
|
709 |
+
folderLink.target = '_blank';
|
710 |
+
folderContainer.appendChild(folderLink);
|
711 |
+
|
712 |
+
const copyFolderBtn = document.createElement('button');
|
713 |
+
copyFolderBtn.textContent = 'Copy Folder Link';
|
714 |
+
copyFolderBtn.className = 'small-btn';
|
715 |
+
copyFolderBtn.onclick = () => {
|
716 |
+
navigator.clipboard.writeText(window.location.origin + folderUrl).then(() => {
|
717 |
+
alert('Folder link copied to clipboard!');
|
|
|
|
|
|
|
718 |
});
|
719 |
};
|
720 |
+
folderContainer.appendChild(copyFolderBtn);
|
721 |
+
|
722 |
+
const fileList = document.createElement('ul');
|
723 |
+
files.forEach(file => {
|
724 |
+
const listItem = document.createElement('li');
|
725 |
+
const fileLink = document.createElement('a');
|
726 |
+
fileLink.href = file.url;
|
727 |
+
fileLink.textContent = file.name;
|
728 |
+
fileLink.target = '_blank';
|
729 |
+
listItem.appendChild(fileLink);
|
730 |
+
fileList.appendChild(listItem);
|
731 |
+
});
|
732 |
+
folderContainer.appendChild(fileList);
|
733 |
|
734 |
+
resultContainer.appendChild(folderContainer);
|
735 |
resultContainer.style.display = 'block';
|
736 |
+
|
737 |
+
saveToHistory(folderUrl, files);
|
738 |
}
|
739 |
|
740 |
function showEmbedModal(url) {
|
|
|
749 |
alert('Embed link copied to clipboard!');
|
750 |
}
|
751 |
|
752 |
+
function saveToHistory(folderUrl, files) {
|
753 |
let history = JSON.parse(localStorage.getItem('uploadHistory')) || [];
|
754 |
+
history.unshift({ folderUrl, files, timestamp: new Date().toISOString() });
|
755 |
if (history.length > 500) history = history.slice(0, 500);
|
756 |
localStorage.setItem('uploadHistory', JSON.stringify(history));
|
757 |
}
|
|
|
765 |
|
766 |
const itemName = document.createElement('span');
|
767 |
itemName.className = 'history-item-name';
|
768 |
+
itemName.textContent = `Folder: ${new Date(item.timestamp).toLocaleString()}`;
|
769 |
historyItem.appendChild(itemName);
|
770 |
|
771 |
const actionsContainer = document.createElement('div');
|
772 |
actionsContainer.className = 'history-item-actions';
|
773 |
|
774 |
const copyBtn = document.createElement('button');
|
775 |
+
copyBtn.textContent = 'Copy Folder Link';
|
776 |
copyBtn.className = 'small-btn';
|
777 |
copyBtn.onclick = () => {
|
778 |
+
navigator.clipboard.writeText(window.location.origin + item.folderUrl).then(() => {
|
779 |
+
alert('Folder link copied to clipboard!');
|
780 |
});
|
781 |
};
|
782 |
actionsContainer.appendChild(copyBtn);
|
783 |
|
784 |
const openBtn = document.createElement('button');
|
785 |
+
openBtn.textContent = 'Open Folder';
|
786 |
openBtn.className = 'small-btn';
|
787 |
openBtn.onclick = () => {
|
788 |
+
window.open(window.location.origin + item.folderUrl, '_blank');
|
789 |
};
|
790 |
actionsContainer.appendChild(openBtn);
|
791 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
792 |
historyItem.appendChild(actionsContainer);
|
793 |
historyList.appendChild(historyItem);
|
794 |
});
|
795 |
historyModal.style.display = "block";
|
796 |
}
|
797 |
|
798 |
+
function quickOpen(url, fileName) {
|
799 |
quickOpenContent.innerHTML = '';
|
800 |
const fullUrl = window.location.origin + url;
|
801 |
|
|
|
846 |
return HTML_CONTENT
|
847 |
|
848 |
@app.post("/upload")
|
849 |
+
async def handle_upload(files: List[UploadFile] = File(...)):
|
850 |
+
if not files:
|
851 |
+
return JSONResponse(content={"error": "No files selected."}, status_code=400)
|
852 |
|
853 |
cookies = await get_cookies()
|
854 |
if 'csrftoken' not in cookies or 'sessionid' not in cookies:
|
855 |
return JSONResponse(content={"error": "Failed to obtain necessary cookies"}, status_code=500)
|
856 |
|
857 |
+
folder_id = str(uuid.uuid4())
|
858 |
+
folder_path = f"/rbxg/{folder_id}"
|
859 |
+
os.makedirs(folder_path, exist_ok=True)
|
860 |
+
|
861 |
+
uploaded_files = []
|
862 |
+
for file in files:
|
863 |
+
upload_result = await initiate_upload(cookies, file.filename, file.content_type)
|
864 |
+
if not upload_result or 'upload_url' not in upload_result:
|
865 |
+
return JSONResponse(content={"error": f"Failed to initiate upload for {file.filename}"}, status_code=500)
|
866 |
|
867 |
+
file_content = await file.read()
|
868 |
+
upload_success = await retry_upload(upload_result['upload_url'], file_content, file.content_type)
|
869 |
+
if not upload_success:
|
870 |
+
return JSONResponse(content={"error": f"File upload failed for {file.filename} after multiple attempts"}, status_code=500)
|
871 |
|
872 |
+
original_url = upload_result['serving_url']
|
873 |
+
mirrored_url = f"{folder_path}/{file.filename}"
|
874 |
+
uploaded_files.append({"name": file.filename, "url": mirrored_url})
|
875 |
|
876 |
+
return JSONResponse(content={"folder_url": folder_path, "files": uploaded_files})
|
877 |
|
878 |
+
@app.get("/rbxg/{folder_id}/{file_name}")
|
879 |
+
async def handle_file_stream(folder_id: str, file_name: str, request: Request):
|
880 |
+
original_url = f'https://replicate.delivery/pbxt/{folder_id}/{file_name}'
|
881 |
range_header = request.headers.get('Range')
|
882 |
|
883 |
headers = {'Range': range_header} if range_header else {}
|
|
|
990 |
await asyncio.sleep(delay)
|
991 |
delay = min(delay * 2, 60) # Exponential backoff, capped at 60 seconds
|
992 |
|
993 |
+
return False
|