feat: webui
This commit is contained in:
123
web/assets/index.js
Normal file
123
web/assets/index.js
Normal file
@ -0,0 +1,123 @@
|
||||
const tokenInput = document.querySelector('#token');
|
||||
const rememberTokenCheckbox = document.querySelector('#remember-token');
|
||||
const setTokenButton = document.querySelector('#set-token');
|
||||
const lessonInput = document.querySelector('#lesson-query');
|
||||
const getLessonButton = document.querySelector('#get-lesson');
|
||||
let token = localStorage.getItem('token') || '';
|
||||
|
||||
if (token !== '') {
|
||||
tokenInput.value = token;
|
||||
rememberTokenCheckbox.checked = true;
|
||||
fetch("/api/v1/token", {
|
||||
method: 'POST',
|
||||
body: token,
|
||||
}).then(rsp => {
|
||||
if (rsp.status !== 200) {
|
||||
setTokenButton.innerHTML = `:( (${rsp.status})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function randomString(length) {
|
||||
return `a${Math.random().toString(36).slice(2, length + 2)}`;
|
||||
}
|
||||
|
||||
// React simulator
|
||||
function getResult(id) {
|
||||
const element = document.createElement("div");
|
||||
element.className = "block";
|
||||
document.body.appendChild(document.createElement("br"));
|
||||
document.body.appendChild(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
setTokenButton.addEventListener('click', async () => {
|
||||
token = tokenInput.value;
|
||||
const rsp = await fetch("/api/v1/token", {
|
||||
method: 'POST',
|
||||
body: token,
|
||||
});
|
||||
if (rsp.status !== 200) {
|
||||
setTokenButton.innerHTML = `:( (${rsp.status})`;
|
||||
return;
|
||||
}
|
||||
if (rememberTokenCheckbox.checked) {
|
||||
localStorage.setItem('token', token);
|
||||
} else {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
});
|
||||
|
||||
getLessonButton.addEventListener('click', async () => {
|
||||
const input = lessonInput.value;
|
||||
let id = Number.parseInt(input);
|
||||
if (Number.isNaN(id)) {
|
||||
const url = input.split('/');
|
||||
url.pop();
|
||||
id = Number.parseInt(url.pop());
|
||||
}
|
||||
const rsp = await fetch(`/api/v1/lessons/${id}`);
|
||||
const result = getResult("lesson");
|
||||
const data = await rsp.json();
|
||||
const ids = {};
|
||||
// React :nerd:
|
||||
result.innerHTML = "<h2>Lesson Information</h2>\n";
|
||||
result.innerHTML += `Name: ${decodeURIComponent(data.name)}`;
|
||||
result.innerHTML += "<h3>Videos available</h3>";
|
||||
for (const video of data.videos) {
|
||||
const uniqueId = randomString(15);
|
||||
ids[uniqueId] = video;
|
||||
result.innerHTML += `
|
||||
<input type="checkbox" id="${uniqueId}" name="${uniqueId}" value="${uniqueId}">
|
||||
<label for="${uniqueId}">${decodeURIComponent(video.name)}</label>
|
||||
<br>`;
|
||||
}
|
||||
let videoDlResult = null;
|
||||
const downloadSelectedButton = document.createElement("button");
|
||||
downloadSelectedButton.innerHTML = "Download Selected";
|
||||
downloadSelectedButton.addEventListener('click', async () => {
|
||||
for (const [id, video] of Object.entries(ids)) {
|
||||
const checkbox = document.querySelector(`#${id}`);
|
||||
if (!checkbox.checked) {
|
||||
continue;
|
||||
}
|
||||
const videoRsp = await fetch(`/api/v1/videos/${video.id}`);
|
||||
if (videoRsp.status !== 200) {
|
||||
console.error(`Failed to get video ${video.name}`);
|
||||
continue;
|
||||
}
|
||||
const videoData = await videoRsp.json();
|
||||
if (videoDlResult === null) {
|
||||
result.appendChild(document.createElement("br"));
|
||||
result.appendChild(document.createElement("br"));
|
||||
videoDlResult = document.createElement("div");
|
||||
videoDlResult.className = "block";
|
||||
result.appendChild(videoDlResult);
|
||||
}
|
||||
const progressId = randomString(15);
|
||||
const progressStrId = randomString(15);
|
||||
videoDlResult.innerHTML += `<span>${video.name}.mp4 <progress id="${progressId}" value="0" max="100"></progress> <span id="${progressStrId}">0%</span></span>`
|
||||
const rsp = await fetch(`/api/v1/download-video?${new URLSearchParams({
|
||||
url: encodeURIComponent(videoData.m3u8),
|
||||
output: encodeURIComponent(`videos/${video.name}.mp4`),
|
||||
})}`);
|
||||
const reader = rsp.body.getReader();
|
||||
while (true) {
|
||||
// wait for next encoded chunk
|
||||
const { done, value } = await reader.read();
|
||||
// check if stream is done
|
||||
if (done) break;
|
||||
// Decodes data chunk and yields it
|
||||
const progress = Math.floor(Number.parseFloat(new TextDecoder().decode(value)));
|
||||
document.querySelector(`#${progressId}`).value = progress;
|
||||
document.querySelector(`#${progressStrId}`).innerText = `${progress}%`;
|
||||
}
|
||||
if (rsp.status !== 200) {
|
||||
console.error(`Failed to download ${video.name}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
result.appendChild(downloadSelectedButton);
|
||||
// result.innerHTML += "<button>Download All</button>";
|
||||
|
||||
});
|
||||
61
web/assets/style.css
Normal file
61
web/assets/style.css
Normal file
@ -0,0 +1,61 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Be Vietnam Pro", sans-serif;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
background-color: #303030;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
header {
|
||||
font-size: 32px;
|
||||
padding: 10px;
|
||||
background-color: #ffffff50;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: fit-content;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 10px;
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 5px;
|
||||
background-color: #303030;
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 10px;
|
||||
transition-duration: 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
input[type="password"],
|
||||
input[type="text"] {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
background-color: #303030;
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: 10px;
|
||||
background-color: #303030;
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
52
web/index.html
Normal file
52
web/index.html
Normal file
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
|
||||
</head>
|
||||
<body class="theme-dark">
|
||||
<header>
|
||||
Explorers
|
||||
</header>
|
||||
<br>
|
||||
<div class="main">
|
||||
<div class="block">
|
||||
Token
|
||||
<input id="token" type="password" name="token" placeholder="Put your token here">
|
||||
<button id="set-token">Set</button>
|
||||
<br>
|
||||
<input type="checkbox" id="remember-token" name="remember-token" value="remember-token">
|
||||
<label for="remember-token">Remember this token</label>
|
||||
<br>
|
||||
<button>How do I get token?</button>
|
||||
</div>
|
||||
<br>
|
||||
<div class="block">
|
||||
Lesson ID/URL
|
||||
<input type="text" id="lesson-query" name="query-lesson">
|
||||
<button id="get-lesson">Get</button>
|
||||
</div>
|
||||
<!-- <br>
|
||||
<div class="block">
|
||||
<h2>Lesson Information</h1>
|
||||
Name: Test
|
||||
<h3>Videos available</h3>
|
||||
<input type="checkbox" id="lesson-video-1" name="lesson-video-1" value="remember-token">
|
||||
<label for="lesson-video-2">Video 1</label>
|
||||
<br>
|
||||
<input type="checkbox" id="lesson-video-1" name="lesson-video-2" value="remember-token">
|
||||
<label for="lesson-video-2">Video 2</label>
|
||||
<br>
|
||||
<button>Download Selected</button>
|
||||
<button>Download All</button>
|
||||
<br>
|
||||
<br>
|
||||
<div class="block">
|
||||
<span>Video 1 <progress id="file" value="32" max="100"></progress> 32%</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<script src="assets/index.js"></script>
|
||||
</body>
|
||||
Reference in New Issue
Block a user