Mangades/src/pages/[manga]/index.svelte
2021-05-24 17:50:34 +02:00

243 lines
No EOL
6.7 KiB
Svelte

<script>
import { page, url } from "@roxi/routify/runtime/helpers";
import { Zip, ZipPassThrough } from "fflate";
import { prepareEpub } from "../../util/generateEpub";
// import * as streamSaver from "streamsaver";
import request from "../../util/request";
export var scoped;
var mangaId = scoped.id;
$: mangaId = scoped.id;
var manga = scoped.manga;
$: manga = scoped.manga;
var relationships = scoped.mangaRelationships;
$: relationships = scoped.mangaRelationships;
async function getMangaChapters(id) {
const data = await request("manga/" + id + "/feed?translatedLanguage[]=en");
data.results.sort((a, b) => a.data.attributes.chapter - b.data.attributes.chapter);
return data;
}
$: chapters = getMangaChapters(mangaId);
console.log(manga);
console.log(chapters);
console.log(relationships);
var progress = 0;
var state = "idle";
var text = "Choose a chapter to view online or download EPUB";
var pagesDone = 0;
var totalPages = 0;
$: progress = pagesDone / (totalPages || 1);
$: if(totalPages) text = `Saving page ${pagesDone + 1} of ${totalPages}`;
var enc = new TextEncoder();
async function prepare(chapter) {
state = "active";
text = "Starting download of chapter " + chapter.data.attributes.chapter;
const { baseUrl } = await request("at-home/server/" + chapter.data.id);
const quality = "data";
const URLs = [];
const hashes = [];
for(const hash of chapter.data.attributes[quality]) {
URLs.push(`${baseUrl}/${quality}/${chapter.data.attributes.hash}/${hash}`);
hashes.push(hash);
}
text = "Found " + URLs.length + " pages";
totalPages += URLs.length;
const file = streamSaver.createWriteStream(`${manga.title.en} ${chapter.data.attributes.chapter}.epub`, {
writableStrategy: undefined, // (optional)
readableStrategy: undefined, // (optional)
});
const zip = await prepareEpub({
title: `${manga.title.en} ${chapter.data.attributes.chapter}`,
id: `https://manga.danbulant.eu/${mangaId}/${chapter.data.id}`,
file,
chapter: chapter.data.attributes.chapter,
links: hashes,
updatedAt: chapter.data.attributes.updatedAt
});
for(var i = 0; i < URLs.length; i++) {
const url = URLs[i];
const hash = hashes[i];
const res = await fetch(url);
const image = new ZipPassThrough("OEBPS/" + hash);
zip.add(image);
image.push(new Uint8Array(await res.arrayBuffer()), true);
const textContent = new ZipPassThrough("OEBPS/" + i + ".xhtml");
zip.add(textContent);
textContent.push(enc.encode(`<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Page ${i + 1}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="EPB-UUID" content=""/>
</head>
<body>
<img style="margin:auto;height:100%;" src="${hash}" />
</body>
</html>`), true);
pagesDone++;
}
zip.end();
if(pagesDone === totalPages) {
text = "Done!";
state = "idle";
pagesDone = 0;
totalPages = 0;
setTimeout(() => {
if(totalPages === 0) {
text = "Choose a chapter to view online or download EPUB";
}
}, 3000);
}
}
</script>
<svelte:head>
<title>Chapters of {manga.title.en}</title>
</svelte:head>
<main>
<h1>{manga.title.en}</h1>
<div class="linklist">
<a href={$url("..")}>Go back to search page</a>
</div>
<br>
<div class="state {state}">
<div class="progress" style="width: {progress * 100}%;"></div>
<p>
{text}
</p>
</div>
<p>
<b>
Do not close the tab when a download is in progress.
</b>
</p>
{#await chapters}
Loading chapters...
{:then chapters}
{#if chapters.results.filter(c => c.data.attributes.translatedLanguage === "en").length === 0}
<p>No chapters found.</p>
{/if}
<table>
<tbody>
{#each chapters.results.filter(c => c.data.attributes.translatedLanguage === "en") as chapter}
<tr>
<td class="no-wrap">{chapter.data.attributes.volume ? "Vol " + chapter.data.attributes.volume : ""}</td>
<td class="no-wrap">Chapter {chapter.data.attributes.chapter}</td>
<td>{chapter.data.attributes.title}</td>
<td class="action no-wrap"><span on:click={() => prepare(chapter)}>Download</span></td>
<td class="action no-wrap"><a href={$url("./" + chapter.data.id)} on:click|stopPropagation>View</a></td>
</tr>
{/each}
</tbody>
</table>
{/await}
</main>
<style>
main {
font-size: 1.1rem;
}
.no-wrap {
white-space: nowrap;
}
tbody {
list-style-type: disc;
}
table {
border-collapse: collapse;
width: 100%;
}
tr {
border: 1px solid black;
}
tr:hover {
background: rgba(0,0,0,0.1);
}
td {
padding: 5px 5px;
}
td.action {
font-weight: bold;
color: black;
text-decoration: none;
cursor: pointer;
}
td.action:hover {
text-decoration: underline;
color: rgb(0,100,200);
}
td.action a {
color: inherit;
}
.state {
border-radius: 10px;
border-width: 4px;
border-style: solid;
padding: 10px;
position: relative;
transition: all .3s;
}
.state.idle {
background: rgb(140, 209, 255);
border-color: rgb(77, 184, 255);
}
.state.active {
background: rgb(255, 255, 81);
border-color: yellow;
}
.state.error {
background: rgb(255, 103, 103);
border-color: rgb(255, 59, 59);
}
.state p {
margin: 0;
z-index: 1;
position: relative;
}
.progress {
z-index: 0;
position: absolute;
top: 0;
left: 0;
height: 100%;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
.state.active .progress {
background: rgb(140, 209, 255);
}
</style>