This commit is contained in:
parent
b7f1c74e38
commit
dfd619a1d9
|
@ -0,0 +1,242 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Download App</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.2/dist/tailwind.min.css" rel="stylesheet">
|
||||
<script>
|
||||
var socket = null;
|
||||
var init_count = 0;
|
||||
|
||||
async function init() {
|
||||
if (init_count) {
|
||||
alert("Already connected.");
|
||||
return;
|
||||
}
|
||||
alert("Connected to server.");
|
||||
var username = document.getElementById('username').value;
|
||||
setInterval(fetchDownloads, 1000);
|
||||
fetchDownloads();
|
||||
socket = new WebSocket("wss://" + window.location.host + "/ws/" + username);
|
||||
|
||||
socket.onmessage = function (event) {
|
||||
var data = JSON.parse(event.data);
|
||||
var card = document.getElementById('card-' + data.url);
|
||||
var progressBar = document.getElementById('progress-' + data.url);
|
||||
var percentageElement = document.getElementById('percentage-' + data.url);
|
||||
progressBar.style.width = data.progress + '%';
|
||||
percentageElement.innerText = data.progress + '%';
|
||||
|
||||
if (data.progress == '100') {
|
||||
percentageElement.style.display = 'none';
|
||||
progressBar.parentElement.style.display = 'none';
|
||||
card.closeButton.style.display = 'block';
|
||||
card.cancelButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
init_count += 1;
|
||||
}
|
||||
|
||||
async function fetchDownloads() {
|
||||
var username = document.getElementById('username').value;
|
||||
var password = document.getElementById('password').value;
|
||||
var auth = "Basic " + btoa(username + ":" + password);
|
||||
|
||||
var response = await fetch('/downloads/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': auth
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
var data = await response.json();
|
||||
alert('Could not authenticate: ' + data.detail);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = await response.json();
|
||||
updateCards(data);
|
||||
}
|
||||
|
||||
function updateCards(data) {
|
||||
var cards = document.getElementsByClassName("downloadCard")
|
||||
var cardsArray = Array.from(cards);
|
||||
cardsArray.forEach(function (card) {
|
||||
var url = card.id.replace('card-', '');
|
||||
if (data.in_progress.includes(url)){
|
||||
} else if (data.completed.includes(url)) {
|
||||
console.log("Client: Completed: " + url);
|
||||
createDownloadCard(url, true);
|
||||
} else {
|
||||
console.log("Client: Remove: " + url);
|
||||
removeCard(url);
|
||||
}
|
||||
});
|
||||
data.in_progress.forEach(function (url) {
|
||||
if ( document.getElementById('card-' + url) == null ) {
|
||||
console.log("Server: New: " + url);
|
||||
createDownloadCard(url);
|
||||
}
|
||||
});
|
||||
data.completed.forEach(function (url) {
|
||||
if ( document.getElementById('card-' + url) == null ) {
|
||||
console.log("Server: Completed: " + url);
|
||||
createDownloadCard(url, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeCard(url) {
|
||||
var card = document.getElementById('card-' + url);
|
||||
if ( card ) {
|
||||
card.parentNode.removeChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelDownload(url) {
|
||||
var username = document.getElementById('username').value;
|
||||
var password = document.getElementById('password').value;
|
||||
var auth = "Basic " + btoa(username + ":" + password);
|
||||
|
||||
var response = await fetch('/cancel/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': auth
|
||||
},
|
||||
body: JSON.stringify({ 'url': url })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
var data = await response.json();
|
||||
alert('Could not cancel download: ' + data.detail);
|
||||
} else {
|
||||
var card = document.getElementById('card-' + url);
|
||||
card.parentNode.removeChild(card);
|
||||
}
|
||||
console.log("Client: canceled: " + url);
|
||||
}
|
||||
|
||||
async function startDownload() {
|
||||
var url = document.getElementById('url').value;
|
||||
var username = document.getElementById('username').value;
|
||||
var password = document.getElementById('password').value;
|
||||
var auth = "Basic " + btoa(username + ":" + password);
|
||||
|
||||
if (document.getElementById('card-' + url)) {
|
||||
alert('A download for this URL already exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = url.split("/").pop();
|
||||
var response = await fetch('/file_exists/?filename=' + filename, {
|
||||
headers: {
|
||||
'Authorization': auth
|
||||
}
|
||||
});
|
||||
var data = await response.json();
|
||||
|
||||
if (data.exists) {
|
||||
alert('A file with this name already exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
createDownloadCard(url);
|
||||
|
||||
fetch('/download/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': auth
|
||||
},
|
||||
body: JSON.stringify({ url: url })
|
||||
});
|
||||
console.log("Client: started: " + url);
|
||||
}
|
||||
|
||||
function createDownloadCard(url, completed = false) {
|
||||
if (document.getElementById('card-' + url)) {
|
||||
return;
|
||||
}
|
||||
var card = document.createElement('div');
|
||||
card.id = 'card-' + url;
|
||||
card.className = 'downloadCard bg-white shadow rounded-lg p-5 mb-4 relative ml-auto mr-auto';
|
||||
|
||||
|
||||
var closeButton = document.createElement('button');
|
||||
closeButton.className = 'absolute top-1 right-1 p-2';
|
||||
closeButton.style.display = 'none';
|
||||
closeButton.innerHTML = '×';
|
||||
closeButton.addEventListener('click', function () {
|
||||
cancelDownload(url);
|
||||
});
|
||||
card.appendChild(closeButton);
|
||||
card.closeButton = closeButton;
|
||||
|
||||
var cancelButton = document.createElement('button');
|
||||
cancelButton.className = 'text-red-500 absolute top-1 right-1 p-2';
|
||||
cancelButton.innerHTML = '×';
|
||||
cancelButton.addEventListener('click', function () {
|
||||
cancelDownload(url);
|
||||
});
|
||||
card.appendChild(cancelButton);
|
||||
card.cancelButton = cancelButton;
|
||||
|
||||
var urlElement = document.createElement('a');
|
||||
urlElement.className = 'text-black font-bold mb-2 overflow-auto';
|
||||
urlElement.href = url;
|
||||
urlElement.innerText = url;
|
||||
card.appendChild(urlElement);
|
||||
|
||||
var flexContainer = document.createElement('div');
|
||||
flexContainer.className = 'flex items-center';
|
||||
|
||||
var percentageElement = document.createElement('p');
|
||||
percentageElement.id = 'percentage-' + url;
|
||||
percentageElement.innerText = '0%';
|
||||
percentageElement.className = 'mr-2';
|
||||
flexContainer.appendChild(percentageElement);
|
||||
|
||||
var progressBarContainer = document.createElement('div');
|
||||
progressBarContainer.className = 'h-2 w-full bg-gray-200 rounded-full';
|
||||
var progressBar = document.createElement('div');
|
||||
progressBar.id = 'progress-' + url;
|
||||
progressBar.className = 'h-2 bg-blue-500 rounded-full';
|
||||
progressBar.style.width = '0%';
|
||||
progressBarContainer.appendChild(progressBar);
|
||||
flexContainer.appendChild(progressBarContainer);
|
||||
|
||||
card.appendChild(flexContainer);
|
||||
|
||||
if (completed) {
|
||||
percentageElement.style.display = 'none';
|
||||
progressBar.parentElement.style.display = 'none';
|
||||
closeButton.style.display = 'block';
|
||||
cancelButton.style.display = 'none';
|
||||
}
|
||||
document.getElementById('downloads').appendChild(card);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-200 py-10">
|
||||
<div class="container mx-auto max-w-5xl px-20">
|
||||
<div class="mb-5 flex space-x-2">
|
||||
<input id="username" class="flex-grow p-2 rounded shadow h-10 w-full sm:w-auto" type="text"
|
||||
placeholder="Enter Username">
|
||||
<input id="password" class="flex-grow p-2 rounded shadow h-10 w-full sm:w-auto" type="password"
|
||||
placeholder="Enter Password">
|
||||
<button onclick="init()"
|
||||
class="bg-blue-300 hover:bg-blue-400 text-white font-bold py-2 px-4 rounded h-10 min-w-max sm:w-auto sm:ml-2">Login</button>
|
||||
</div>
|
||||
<div class="mb-5 flex space-x-2">
|
||||
<input id="url" class="flex-grow p-2 rounded shadow h-10 w-full sm:w-auto" type="text"
|
||||
placeholder="Enter URL">
|
||||
<button onclick="startDownload()"
|
||||
class="bg-blue-300 hover:bg-blue-400 text-white font-bold py-2 px-4 rounded h-10 min-w-max sm:w-auto sm:ml-2">⬇</button>
|
||||
</div>
|
||||
<div id="downloads"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,136 @@
|
|||
from fastapi import FastAPI, WebSocket, HTTPException, Request, Depends, status
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
import aiohttp, asyncio
|
||||
import os, tqdm, json
|
||||
from collections import defaultdict
|
||||
from base64 import b64encode
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
connected_clients = defaultdict(list)
|
||||
downloads = defaultdict(dict)
|
||||
completed_downloads = defaultdict(dict)
|
||||
canceled_downloads = defaultdict(dict)
|
||||
|
||||
|
||||
class DownloadRequest(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
security = HTTPBasic()
|
||||
|
||||
|
||||
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
|
||||
return credentials.username
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def get(request: Request):
|
||||
with open("index.html", "r") as f:
|
||||
content = f.read()
|
||||
return HTMLResponse(content=content)
|
||||
|
||||
|
||||
@app.get("/file_exists/")
|
||||
async def file_exists(filename: str):
|
||||
if os.path.isfile(filename):
|
||||
os.rename(filename, filename + "+")
|
||||
return {"exists": os.path.isfile(filename)}
|
||||
|
||||
|
||||
@app.get("/downloads/")
|
||||
async def get_downloads(username: str = Depends(get_current_username)):
|
||||
return {
|
||||
"in_progress": list(downloads[username].keys()),
|
||||
"completed": list(completed_downloads[username].keys()),
|
||||
"canceled": list(canceled_downloads[username].keys()),
|
||||
}
|
||||
|
||||
|
||||
@app.post("/download/")
|
||||
async def download_file(
|
||||
request: DownloadRequest, username: str = Depends(get_current_username)
|
||||
):
|
||||
url = request.url
|
||||
filename = url.split("/")[-1]
|
||||
|
||||
if url in downloads[username]:
|
||||
raise HTTPException(status_code=400, detail="Download already in progress")
|
||||
|
||||
download_task = asyncio.create_task(do_download(url, filename, username))
|
||||
downloads[username][url] = download_task
|
||||
|
||||
return {"message": "Download started"}
|
||||
|
||||
|
||||
async def do_download(url, filename, username):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
file_size = int(resp.headers["Content-Length"])
|
||||
pbar = tqdm.tqdm(
|
||||
total=(file_size / (1024 * 128)),
|
||||
unit="Mb",
|
||||
ascii=True,
|
||||
unit_scale=True,
|
||||
)
|
||||
with open(filename, "wb") as f:
|
||||
chunk_size = 1024
|
||||
downloaded_size = 0
|
||||
last_progress = 0
|
||||
async for chunk in resp.content.iter_any():
|
||||
pbar.update(len(chunk) / (1024 * 128))
|
||||
if url not in downloads[username]:
|
||||
pbar.close()
|
||||
return
|
||||
f.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
# Notify the client about the progress
|
||||
progress = int((downloaded_size / file_size) * 100)
|
||||
# Check if the integer percentage has changed
|
||||
if progress != last_progress:
|
||||
last_progress = progress
|
||||
await notify_clients(progress, url, username)
|
||||
pbar.close()
|
||||
finally:
|
||||
if url in downloads[username]:
|
||||
if url not in canceled_downloads[username]:
|
||||
completed_downloads[username][url] = downloads[username][url]
|
||||
del downloads[username][url]
|
||||
|
||||
|
||||
@app.post("/cancel/")
|
||||
async def cancel_download(
|
||||
request: DownloadRequest, username: str = Depends(get_current_username)
|
||||
):
|
||||
url = request.url
|
||||
if url in downloads[username]:
|
||||
canceled_downloads[username][url] = downloads[username][url]
|
||||
downloads[username][url].cancel()
|
||||
return {"message": "Download canceled"}
|
||||
if url in completed_downloads[username]:
|
||||
del completed_downloads[username][url]
|
||||
return {"message": "Download removed"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="No such download")
|
||||
|
||||
|
||||
@app.websocket("/ws/{username}")
|
||||
async def websocket_endpoint(websocket: WebSocket, username: str):
|
||||
await websocket.accept()
|
||||
connected_clients[username].append(websocket)
|
||||
try:
|
||||
while True:
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
connected_clients[username].remove(websocket)
|
||||
|
||||
|
||||
async def notify_clients(progress, url, username):
|
||||
for client in connected_clients[username]:
|
||||
await client.send_text(json.dumps({"progress": progress, "url": url}))
|
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
|
@ -0,0 +1,33 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.mount("/icons", StaticFiles(directory="icons"), name="icons")
|
||||
|
||||
reds = {"live-calendar": "https://outlook.live.com/calendar/0/view/month",
|
||||
"twitter": "https://twitter.com"}
|
||||
|
||||
@app.get("/{icon_name}")
|
||||
async def redirect_html(icon_name: str):
|
||||
redirect_url = reds.get(icon_name)
|
||||
html_content = f"""<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url={redirect_url}" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
Redirecting...
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(content=html_content)
|
||||
|
||||
@app.get("/{icon_name}/favicon.ico")
|
||||
async def get_icon(icon_name: str):
|
||||
icon_path = Path(f"icons/{icon_name}.ico")
|
||||
if icon_path.exists():
|
||||
return FileResponse(icon_path)
|
||||
else:
|
||||
return {"error": "Icon not found"}
|
|
@ -0,0 +1,28 @@
|
|||
// ==UserScript==
|
||||
// @name Twitter Remove Ad
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 0.1
|
||||
// @description try to take over the world!
|
||||
// @author You
|
||||
// @match https://twitter.com/home
|
||||
// @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com
|
||||
// @grant none
|
||||
// ==/UserScript==
|
||||
|
||||
(function() {
|
||||
let removead = setInterval( function(){
|
||||
console.log("Checking ad..");
|
||||
[...document.querySelectorAll("div[data-testid=cellInnerDiv]")].forEach(e => {
|
||||
var ad = 0;
|
||||
[...e.getElementsByTagName("span")].forEach(f => {
|
||||
if (f.innerText == "Ad") {
|
||||
ad = 1;
|
||||
}
|
||||
});
|
||||
if( ad ) {
|
||||
console.log(e.querySelectorAll("div[data-testid=User-Name]")[0].innerText);
|
||||
e.innerHTML = "";
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
})();
|
Loading…
Reference in New Issue