Update app.py
Browse files
app.py
CHANGED
|
@@ -6,16 +6,21 @@ import asyncio
|
|
| 6 |
from typing import Dict
|
| 7 |
import os
|
| 8 |
import mimetypes
|
|
|
|
|
|
|
| 9 |
|
| 10 |
app = FastAPI()
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
HTML_CONTENT = """
|
| 13 |
<!DOCTYPE html>
|
| 14 |
<html lang="en">
|
| 15 |
<head>
|
| 16 |
<meta charset="UTF-8">
|
| 17 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 18 |
-
<title>
|
| 19 |
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
|
| 20 |
<style>
|
| 21 |
body {
|
|
@@ -445,7 +450,7 @@ HTML_CONTENT = """
|
|
| 445 |
text-align: center;
|
| 446 |
}
|
| 447 |
|
| 448 |
-
|
| 449 |
.quick-open-content video,
|
| 450 |
.quick-open-content audio {
|
| 451 |
max-width: 100%;
|
|
@@ -534,7 +539,7 @@ HTML_CONTENT = """
|
|
| 534 |
</head>
|
| 535 |
<body>
|
| 536 |
<div class="container">
|
| 537 |
-
<h1>
|
| 538 |
<form id="uploadForm">
|
| 539 |
<div id="dropZone" class="drop-zone">
|
| 540 |
<input type="file" name="file" id="file" class="file-input" accept="*" required>
|
|
@@ -583,6 +588,18 @@ HTML_CONTENT = """
|
|
| 583 |
</div>
|
| 584 |
</div>
|
| 585 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
<script>
|
| 587 |
const fileInput = document.getElementById('file');
|
| 588 |
const fileName = document.getElementById('fileName');
|
|
@@ -592,10 +609,12 @@ HTML_CONTENT = """
|
|
| 592 |
const resultContainer = document.getElementById('resultContainer');
|
| 593 |
const dropZone = document.getElementById('dropZone');
|
| 594 |
const modal = document.getElementById('embedModal');
|
|
|
|
| 595 |
const historyModal = document.getElementById('historyModal');
|
| 596 |
const quickOpenModal = document.getElementById('quickOpenModal');
|
| 597 |
const span = document.getElementsByClassName("close");
|
| 598 |
const embedLinkInput = document.getElementById('embedLink');
|
|
|
|
| 599 |
const uploadBtn = document.getElementById('uploadBtn');
|
| 600 |
const historyBtn = document.getElementById('historyBtn');
|
| 601 |
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
|
@@ -637,16 +656,14 @@ HTML_CONTENT = """
|
|
| 637 |
}
|
| 638 |
});
|
| 639 |
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
span[2].onclick = function() {
|
| 649 |
-
quickOpenModal.style.display = "none";
|
| 650 |
}
|
| 651 |
|
| 652 |
window.onclick = function(event) {
|
|
@@ -659,6 +676,9 @@ HTML_CONTENT = """
|
|
| 659 |
if (event.target == quickOpenModal) {
|
| 660 |
quickOpenModal.style.display = "none";
|
| 661 |
}
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
| 663 |
|
| 664 |
historyBtn.onclick = function() {
|
|
@@ -796,6 +816,14 @@ HTML_CONTENT = """
|
|
| 796 |
};
|
| 797 |
buttonsContainer.appendChild(copyBtn);
|
| 798 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 799 |
const openBtn = document.createElement('button');
|
| 800 |
openBtn.textContent = 'Open';
|
| 801 |
openBtn.className = 'small-btn';
|
|
@@ -832,6 +860,33 @@ HTML_CONTENT = """
|
|
| 832 |
alert('Embed link copied to clipboard!');
|
| 833 |
}
|
| 834 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 835 |
function saveToHistory(fileName, url, originalExtension) {
|
| 836 |
let history = JSON.parse(localStorage.getItem('uploadHistory')) || [];
|
| 837 |
history.unshift({ fileName, url, originalExtension, timestamp: new Date().toISOString() });
|
|
@@ -864,6 +919,15 @@ HTML_CONTENT = """
|
|
| 864 |
};
|
| 865 |
actionsContainer.appendChild(copyBtn);
|
| 866 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
const openBtn = document.createElement('button');
|
| 868 |
openBtn.textContent = 'Open';
|
| 869 |
openBtn.className = 'small-btn';
|
|
@@ -889,7 +953,7 @@ HTML_CONTENT = """
|
|
| 889 |
historyModal.style.display = "block";
|
| 890 |
}
|
| 891 |
|
| 892 |
-
|
| 893 |
quickOpenContent.innerHTML = '';
|
| 894 |
const fullUrl = window.location.origin + url;
|
| 895 |
|
|
@@ -976,6 +1040,60 @@ async def handle_upload(file: UploadFile = File(...)):
|
|
| 976 |
|
| 977 |
return JSONResponse(content={"url": mirrored_url, "originalExtension": original_extension})
|
| 978 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 979 |
@app.get("/rbxg/{path:path}")
|
| 980 |
async def handle_file_stream(path: str, request: Request):
|
| 981 |
original_url = f'https://replicate.delivery/pbxt/{path}'
|
|
@@ -1099,4 +1217,4 @@ async def retry_upload(upload_url: str, file_content: bytes, content_type: str,
|
|
| 1099 |
await asyncio.sleep(delay)
|
| 1100 |
delay = min(delay * 2, 60)
|
| 1101 |
|
| 1102 |
-
return False
|
|
|
|
| 6 |
from typing import Dict
|
| 7 |
import os
|
| 8 |
import mimetypes
|
| 9 |
+
import secrets
|
| 10 |
+
import json
|
| 11 |
|
| 12 |
app = FastAPI()
|
| 13 |
|
| 14 |
+
# Store shortened URLs
|
| 15 |
+
SHORTENED_URLS = {}
|
| 16 |
+
|
| 17 |
HTML_CONTENT = """
|
| 18 |
<!DOCTYPE html>
|
| 19 |
<html lang="en">
|
| 20 |
<head>
|
| 21 |
<meta charset="UTF-8">
|
| 22 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 23 |
+
<title>File Uploader</title>
|
| 24 |
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
|
| 25 |
<style>
|
| 26 |
body {
|
|
|
|
| 450 |
text-align: center;
|
| 451 |
}
|
| 452 |
|
| 453 |
+
.quick-open-content img,
|
| 454 |
.quick-open-content video,
|
| 455 |
.quick-open-content audio {
|
| 456 |
max-width: 100%;
|
|
|
|
| 539 |
</head>
|
| 540 |
<body>
|
| 541 |
<div class="container">
|
| 542 |
+
<h1>File Uploader</h1>
|
| 543 |
<form id="uploadForm">
|
| 544 |
<div id="dropZone" class="drop-zone">
|
| 545 |
<input type="file" name="file" id="file" class="file-input" accept="*" required>
|
|
|
|
| 588 |
</div>
|
| 589 |
</div>
|
| 590 |
|
| 591 |
+
<div id="shortenModal" class="modal">
|
| 592 |
+
<div class="modal-content">
|
| 593 |
+
<span class="close">×</span>
|
| 594 |
+
<h2>Short Link</h2>
|
| 595 |
+
<p>Share this shortened link:</p>
|
| 596 |
+
<div class="embed-container">
|
| 597 |
+
<input type="text" id="shortenedLink" readonly>
|
| 598 |
+
<button onclick="copyShortenedLink()" class="small-btn">Copy</button>
|
| 599 |
+
</div>
|
| 600 |
+
</div>
|
| 601 |
+
</div>
|
| 602 |
+
|
| 603 |
<script>
|
| 604 |
const fileInput = document.getElementById('file');
|
| 605 |
const fileName = document.getElementById('fileName');
|
|
|
|
| 609 |
const resultContainer = document.getElementById('resultContainer');
|
| 610 |
const dropZone = document.getElementById('dropZone');
|
| 611 |
const modal = document.getElementById('embedModal');
|
| 612 |
+
const shortenModal = document.getElementById('shortenModal');
|
| 613 |
const historyModal = document.getElementById('historyModal');
|
| 614 |
const quickOpenModal = document.getElementById('quickOpenModal');
|
| 615 |
const span = document.getElementsByClassName("close");
|
| 616 |
const embedLinkInput = document.getElementById('embedLink');
|
| 617 |
+
const shortenedLinkInput = document.getElementById('shortenedLink');
|
| 618 |
const uploadBtn = document.getElementById('uploadBtn');
|
| 619 |
const historyBtn = document.getElementById('historyBtn');
|
| 620 |
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
|
|
|
| 656 |
}
|
| 657 |
});
|
| 658 |
|
| 659 |
+
// Close modals when clicking X
|
| 660 |
+
for (let i = 0; i < span.length; i++) {
|
| 661 |
+
span[i].onclick = function() {
|
| 662 |
+
modal.style.display = "none";
|
| 663 |
+
historyModal.style.display = "none";
|
| 664 |
+
quickOpenModal.style.display = "none";
|
| 665 |
+
shortenModal.style.display = "none";
|
| 666 |
+
}
|
|
|
|
|
|
|
| 667 |
}
|
| 668 |
|
| 669 |
window.onclick = function(event) {
|
|
|
|
| 676 |
if (event.target == quickOpenModal) {
|
| 677 |
quickOpenModal.style.display = "none";
|
| 678 |
}
|
| 679 |
+
if (event.target == shortenModal) {
|
| 680 |
+
shortenModal.style.display = "none";
|
| 681 |
+
}
|
| 682 |
}
|
| 683 |
|
| 684 |
historyBtn.onclick = function() {
|
|
|
|
| 816 |
};
|
| 817 |
buttonsContainer.appendChild(copyBtn);
|
| 818 |
|
| 819 |
+
const shortenBtn = document.createElement('button');
|
| 820 |
+
shortenBtn.textContent = 'Create Short Link';
|
| 821 |
+
shortenBtn.className = 'small-btn';
|
| 822 |
+
shortenBtn.onclick = () => {
|
| 823 |
+
createShortLink(url);
|
| 824 |
+
};
|
| 825 |
+
buttonsContainer.appendChild(shortenBtn);
|
| 826 |
+
|
| 827 |
const openBtn = document.createElement('button');
|
| 828 |
openBtn.textContent = 'Open';
|
| 829 |
openBtn.className = 'small-btn';
|
|
|
|
| 860 |
alert('Embed link copied to clipboard!');
|
| 861 |
}
|
| 862 |
|
| 863 |
+
function copyShortenedLink() {
|
| 864 |
+
shortenedLinkInput.select();
|
| 865 |
+
document.execCommand('copy');
|
| 866 |
+
alert('Short link copied to clipboard!');
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
function createShortLink(url) {
|
| 870 |
+
fetch('/shorten', {
|
| 871 |
+
method: 'POST',
|
| 872 |
+
headers: {
|
| 873 |
+
'Content-Type': 'application/json',
|
| 874 |
+
},
|
| 875 |
+
body: JSON.stringify({ url: url })
|
| 876 |
+
})
|
| 877 |
+
.then(response => response.json())
|
| 878 |
+
.then(data => {
|
| 879 |
+
if (data.shortUrl) {
|
| 880 |
+
shortenedLinkInput.value = window.location.origin + data.shortUrl;
|
| 881 |
+
shortenModal.style.display = "block";
|
| 882 |
+
}
|
| 883 |
+
})
|
| 884 |
+
.catch(error => {
|
| 885 |
+
console.error('Error creating short link:', error);
|
| 886 |
+
alert('Failed to create short link');
|
| 887 |
+
});
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
function saveToHistory(fileName, url, originalExtension) {
|
| 891 |
let history = JSON.parse(localStorage.getItem('uploadHistory')) || [];
|
| 892 |
history.unshift({ fileName, url, originalExtension, timestamp: new Date().toISOString() });
|
|
|
|
| 919 |
};
|
| 920 |
actionsContainer.appendChild(copyBtn);
|
| 921 |
|
| 922 |
+
const shortenBtn = document.createElement('button');
|
| 923 |
+
shortenBtn.textContent = 'Short Link';
|
| 924 |
+
shortenBtn.className = 'small-btn';
|
| 925 |
+
shortenBtn.onclick = () => {
|
| 926 |
+
createShortLink(item.url);
|
| 927 |
+
historyModal.style.display = "none";
|
| 928 |
+
};
|
| 929 |
+
actionsContainer.appendChild(shortenBtn);
|
| 930 |
+
|
| 931 |
const openBtn = document.createElement('button');
|
| 932 |
openBtn.textContent = 'Open';
|
| 933 |
openBtn.className = 'small-btn';
|
|
|
|
| 953 |
historyModal.style.display = "block";
|
| 954 |
}
|
| 955 |
|
| 956 |
+
function quickOpen(url, fileName, originalExtension) {
|
| 957 |
quickOpenContent.innerHTML = '';
|
| 958 |
const fullUrl = window.location.origin + url;
|
| 959 |
|
|
|
|
| 1040 |
|
| 1041 |
return JSONResponse(content={"url": mirrored_url, "originalExtension": original_extension})
|
| 1042 |
|
| 1043 |
+
@app.post("/shorten")
|
| 1044 |
+
async def shorten_url(request: Request):
|
| 1045 |
+
data = await request.json()
|
| 1046 |
+
original_url = data.get("url")
|
| 1047 |
+
|
| 1048 |
+
if not original_url:
|
| 1049 |
+
return JSONResponse(content={"error": "No URL provided"}, status_code=400)
|
| 1050 |
+
|
| 1051 |
+
# Generate a short code
|
| 1052 |
+
short_code = ''.join(secrets.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for _ in range(6))
|
| 1053 |
+
|
| 1054 |
+
# Store the mapping
|
| 1055 |
+
SHORTENED_URLS[short_code] = original_url
|
| 1056 |
+
|
| 1057 |
+
# Save to file for persistence
|
| 1058 |
+
try:
|
| 1059 |
+
with open("shortened_urls.json", "w") as f:
|
| 1060 |
+
json.dump(SHORTENED_URLS, f)
|
| 1061 |
+
except:
|
| 1062 |
+
# Not critical if it fails to save
|
| 1063 |
+
pass
|
| 1064 |
+
|
| 1065 |
+
return JSONResponse(content={"shortUrl": f"/s/{short_code}"})
|
| 1066 |
+
|
| 1067 |
+
@app.get("/s/{short_code}")
|
| 1068 |
+
async def redirect_short_url(short_code: str):
|
| 1069 |
+
# Try to load from file first (for persistence)
|
| 1070 |
+
try:
|
| 1071 |
+
if not SHORTENED_URLS and os.path.exists("shortened_urls.json"):
|
| 1072 |
+
with open("shortened_urls.json", "r") as f:
|
| 1073 |
+
shortened_urls_data = json.load(f)
|
| 1074 |
+
SHORTENED_URLS.update(shortened_urls_data)
|
| 1075 |
+
except:
|
| 1076 |
+
pass
|
| 1077 |
+
|
| 1078 |
+
# Get the original URL
|
| 1079 |
+
original_url = SHORTENED_URLS.get(short_code)
|
| 1080 |
+
if not original_url:
|
| 1081 |
+
return JSONResponse(content={"error": "Short URL not found"}, status_code=404)
|
| 1082 |
+
|
| 1083 |
+
# HTML for redirecting
|
| 1084 |
+
html_content = f"""
|
| 1085 |
+
<!DOCTYPE html>
|
| 1086 |
+
<html>
|
| 1087 |
+
<head>
|
| 1088 |
+
<meta http-equiv="refresh" content="0;url={original_url}">
|
| 1089 |
+
</head>
|
| 1090 |
+
<body>
|
| 1091 |
+
<p>Redirecting...</p>
|
| 1092 |
+
</body>
|
| 1093 |
+
</html>
|
| 1094 |
+
"""
|
| 1095 |
+
return HTMLResponse(content=html_content)
|
| 1096 |
+
|
| 1097 |
@app.get("/rbxg/{path:path}")
|
| 1098 |
async def handle_file_stream(path: str, request: Request):
|
| 1099 |
original_url = f'https://replicate.delivery/pbxt/{path}'
|
|
|
|
| 1217 |
await asyncio.sleep(delay)
|
| 1218 |
delay = min(delay * 2, 60)
|
| 1219 |
|
| 1220 |
+
return False
|