This commit is contained in:
Morgan 2023-12-01 16:05:00 +09:00
parent b7f1c74e38
commit dfd619a1d9
8 changed files with 439 additions and 1 deletions

View File

@ -1 +0,0 @@

242
downloader/index.html Normal file
View File

@ -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 = '&times;';
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 = '&times;';
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>

136
downloader/main.py Normal file
View File

@ -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

33
icon_proxy/main.py Normal file
View File

@ -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"}

28
twt_remove_ad.js Normal file
View File

@ -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);
})();