@@ -9,6 +9,8 @@ asttokens==2.4.1
|
|||||||
async-timeout==4.0.3
|
async-timeout==4.0.3
|
||||||
attrs==23.1.0
|
attrs==23.1.0
|
||||||
backoff==2.2.1
|
backoff==2.2.1
|
||||||
|
beautifulsoup4==4.12.3
|
||||||
|
bs4==0.0.2
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
comm==0.2.0
|
comm==0.2.0
|
||||||
debugpy==1.8.0
|
debugpy==1.8.0
|
||||||
@@ -48,6 +50,7 @@ pyzmq==25.1.2
|
|||||||
rich==13.7.0
|
rich==13.7.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
sniffio==1.3.0
|
sniffio==1.3.0
|
||||||
|
soupsieve==2.5
|
||||||
stack-data==0.6.3
|
stack-data==0.6.3
|
||||||
starlette==0.32.0.post1
|
starlette==0.32.0.post1
|
||||||
tornado==6.4
|
tornado==6.4
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import backoff
|
|||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
from aiohttp_client_cache.session import CachedSession
|
from aiohttp_client_cache.session import CachedSession
|
||||||
from aiohttp_client_cache import FileBackend
|
from aiohttp_client_cache import FileBackend
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
@@ -116,19 +117,38 @@ async def set_cover(book, data):
|
|||||||
book.set_cover("cover.jpg", await fetch_cover(data["cover"]))
|
book.set_cover("cover.jpg", await fetch_cover(data["cover"]))
|
||||||
|
|
||||||
|
|
||||||
async def add_chapters(book, data):
|
async def add_chapters(book, data, download_images: bool = False):
|
||||||
chapters = []
|
chapters = []
|
||||||
|
|
||||||
for part in data["parts"]:
|
for part in data["parts"]:
|
||||||
content = await fetch_part_content(part["id"])
|
content = await fetch_part_content(part["id"])
|
||||||
title = part["title"]
|
title = part["title"]
|
||||||
|
clean_title = slugify(title)
|
||||||
|
|
||||||
# Thanks https://eu17.proxysite.com/process.php?d=5VyWYcoQl%2BVF0BYOuOavtvjOloFUZz2BJ%2Fepiusk6Nz7PV%2B9i8rs7cFviGftrBNll%2B0a3qO7UiDkTt4qwCa0fDES&b=1
|
# Thanks https://eu17.proxysite.com/process.php?d=5VyWYcoQl%2BVF0BYOuOavtvjOloFUZz2BJ%2Fepiusk6Nz7PV%2B9i8rs7cFviGftrBNll%2B0a3qO7UiDkTt4qwCa0fDES&b=1
|
||||||
chapter = epub.EpubHtml(
|
chapter = epub.EpubHtml(
|
||||||
title=title,
|
title=title,
|
||||||
file_name=f"{slugify(title)}.xhtml",
|
file_name=f"{clean_title}.xhtml",
|
||||||
lang=data["language"]["name"],
|
lang=data["language"]["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if download_images:
|
||||||
|
soup = BeautifulSoup(content, "lxml")
|
||||||
|
async with CachedSession(cache=cache, headers=headers) as session:
|
||||||
|
for idx, image in enumerate(soup.find_all("img")):
|
||||||
|
if not image["src"]:
|
||||||
|
continue
|
||||||
|
async with session.get(image["src"]) as response:
|
||||||
|
img = epub.EpubImage(
|
||||||
|
media_type="image/jpeg",
|
||||||
|
content=await response.read(),
|
||||||
|
file_name=f"static/{clean_title}/{idx}.jpeg",
|
||||||
|
)
|
||||||
|
book.add_item(img)
|
||||||
|
content = content.replace(
|
||||||
|
str(image), f'<img src="static/{clean_title}/{idx}.jpeg"/>'
|
||||||
|
)
|
||||||
|
|
||||||
chapter.set_content(f"<h1>{title}</h1>" + content)
|
chapter.set_content(f"<h1>{title}</h1>" + content)
|
||||||
|
|
||||||
chapters.append(chapter)
|
chapters.append(chapter)
|
||||||
|
|||||||
+19
-7
@@ -1,6 +1,6 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.responses import FileResponse, StreamingResponse
|
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
|
||||||
from ebooklib import epub
|
from ebooklib import epub
|
||||||
from create_book import retrieve_story, set_cover, set_metadata, add_chapters, slugify
|
from create_book import retrieve_story, set_cover, set_metadata, add_chapters, slugify
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -17,17 +17,29 @@ def home():
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/download/{story_id}")
|
@app.get("/download/{story_id}")
|
||||||
async def download_book(story_id: int):
|
async def download_book(story_id: int, download_images: bool = False):
|
||||||
data = await retrieve_story(story_id)
|
data = await retrieve_story(story_id)
|
||||||
book = epub.EpubBook()
|
book = epub.EpubBook()
|
||||||
|
|
||||||
# Metadata and Cover are updated
|
# Metadata and Cover are updated
|
||||||
set_metadata(book, data)
|
try:
|
||||||
|
set_metadata(book, data)
|
||||||
|
except KeyError:
|
||||||
|
# raise HTTPException(
|
||||||
|
# status_code=404,
|
||||||
|
# detail='Story not found. Check the ID - Support is available on the <a href="https://discord.gg/P9RHC4KCwd" target="_blank">Discord</a>',
|
||||||
|
# )
|
||||||
|
# return FileResponse(BUILD_PATH / "index.html", status_code=404)
|
||||||
|
return HTMLResponse(
|
||||||
|
status_code=404,
|
||||||
|
content='Story not found. Check the ID - Support is available on the <a href="https://discord.gg/P9RHC4KCwd" target="_blank">Discord</a>',
|
||||||
|
)
|
||||||
|
|
||||||
await set_cover(book, data)
|
await set_cover(book, data)
|
||||||
# print("Metadata Downloaded")
|
# print("Metadata Downloaded")
|
||||||
|
|
||||||
# Chapters are downloaded
|
# Chapters are downloaded
|
||||||
async for title in add_chapters(book, data):
|
async for title in add_chapters(book, data, download_images=download_images):
|
||||||
# print(f"Part ({title}) downloaded")
|
# print(f"Part ({title}) downloaded")
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -46,7 +58,7 @@ async def download_book(story_id: int):
|
|||||||
BytesIO(book_data),
|
BytesIO(book_data),
|
||||||
media_type="application/epub+zip",
|
media_type="application/epub+zip",
|
||||||
headers={
|
headers={
|
||||||
"Content-Disposition": f'attachment; filename="{slugify(data["title"])}_{story_id}.epub"' # Thanks https://stackoverflow.com/a/72729058
|
"Content-Disposition": f'attachment; filename="{slugify(data["title"])}_{story_id}_{"images" if download_images else ""}.epub"' # Thanks https://stackoverflow.com/a/72729058
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,4 +69,4 @@ app.mount("/", StaticFiles(directory=BUILD_PATH), "static")
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=80)
|
uvicorn.run(app, host="0.0.0.0", port=1112)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,57 +1,105 @@
|
|||||||
<script>
|
<script>
|
||||||
let story_id = "";
|
let story_id = "";
|
||||||
|
let download_images = false;
|
||||||
|
let after_download_page = true;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="hero min-h-screen bg-base-200">
|
<div class="hero min-h-screen">
|
||||||
<div class="hero-content flex-col lg:flex-row-reverse">
|
<div
|
||||||
<div class="text-center lg:text-left lg:p-10">
|
class="hero-content flex-col lg:flex-row-reverse glass p-16 rounded shadow-sm"
|
||||||
<h1
|
>
|
||||||
class="font-extrabold text-transparent text-5xl bg-clip-text bg-gradient-to-r to-pink-600 via-yellow-600 from-red-700"
|
{#if !after_download_page}
|
||||||
>
|
<div class="text-center lg:text-left lg:p-10">
|
||||||
Wattpad Downloader
|
<h1
|
||||||
</h1>
|
class="font-extrabold text-transparent text-5xl bg-clip-text bg-gradient-to-r to-pink-600 via-yellow-600 from-red-700"
|
||||||
<p class="py-6">
|
>
|
||||||
Download your favourite books as EPUBs with a single click!
|
Wattpad Downloader
|
||||||
</p>
|
</h1>
|
||||||
</div>
|
<p class="pt-6 text-lg">
|
||||||
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
|
Download your favourite books with a single click!
|
||||||
<form class="card-body">
|
</p>
|
||||||
<div class="form-control">
|
<ul class="pt-4 list list-inside text-xl">
|
||||||
<input
|
<li>06/24 - 🎉 Image Downloading!</li>
|
||||||
type="number"
|
</ul>
|
||||||
placeholder="Story ID"
|
</div>
|
||||||
class="input input-bordered"
|
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
|
||||||
bind:value={story_id}
|
<form class="card-body">
|
||||||
required
|
<div class="form-control">
|
||||||
name="story_id"
|
<input
|
||||||
/>
|
type="number"
|
||||||
<label class="label" for="story_id">
|
placeholder="Story ID"
|
||||||
<button
|
class="input input-bordered"
|
||||||
class="label-text link"
|
bind:value={story_id}
|
||||||
onclick="StoryIDTutorialModal.showModal()"
|
required
|
||||||
data-umami-event="StoryIDTutorialModal Open"
|
name="story_id"
|
||||||
>How to get a Story ID</button
|
/>
|
||||||
|
<label class="label" for="story_id">
|
||||||
|
<button
|
||||||
|
class="label-text link"
|
||||||
|
onclick="StoryIDTutorialModal.showModal()"
|
||||||
|
data-umami-event="StoryIDTutorialModal Open"
|
||||||
|
>How to get a Story ID</button
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-6">
|
||||||
|
<a
|
||||||
|
class="btn btn-primary rounded-l-none"
|
||||||
|
class:btn-disabled={!story_id}
|
||||||
|
data-umami-event="Download"
|
||||||
|
href={`/download/${story_id}${download_images ? "?download_images=true" : ""}`}
|
||||||
|
on:click={() => (after_download_page = true)}>Download</a
|
||||||
>
|
>
|
||||||
</label>
|
<label class="cursor-pointer label">
|
||||||
</div>
|
<span class="label-text"
|
||||||
|
>Include Images (<strong>Slower Download</strong>)</span
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-warning shadow-md"
|
||||||
|
bind:checked={download_images}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div class="form-control mt-6">
|
<button
|
||||||
<a
|
data-feedback-fish
|
||||||
class="btn btn-primary rounded-l-none"
|
class="link pb-4"
|
||||||
class:btn-disabled={!story_id}
|
data-umami-event="Feedback">Feedback</button
|
||||||
href={`/download/${story_id}`}
|
>
|
||||||
data-umami-event="Download"
|
</div>
|
||||||
download
|
{:else}
|
||||||
onclick="AfterDownloadModal.showModal()">Download</a
|
<div class="text-center max-w-4xl">
|
||||||
|
<h1 class="font-bold text-3xl">
|
||||||
|
Your download has <span
|
||||||
|
class="text-transparent bg-clip-text bg-gradient-to-r to-pink-600 via-yellow-600 from-red-700"
|
||||||
|
>Started</span
|
||||||
>
|
>
|
||||||
|
</h1>
|
||||||
|
<div class="py-4 space-y-2">
|
||||||
|
<p class="text-2xl">
|
||||||
|
If you found this site useful, please consider <a
|
||||||
|
href="https://github.com/TheOnlyWayUp/WattpadDownloader"
|
||||||
|
target="_blank"
|
||||||
|
class="link"
|
||||||
|
data-umami-event="Star">starring the project</a
|
||||||
|
> to support WattpadDownloader.
|
||||||
|
</p>
|
||||||
|
<p class="text-lg pt-2">
|
||||||
|
You can also join us on <a
|
||||||
|
href="https://discord.gg/P9RHC4KCwd"
|
||||||
|
target="_blank"
|
||||||
|
class="link"
|
||||||
|
data-umami-event="Discord">discord</a
|
||||||
|
>, where we release features early and discuss updates.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<a href="/" class="btn btn-outline btn-lg mt-10">Download More</a>
|
||||||
|
</div>
|
||||||
<button data-feedback-fish class="link pb-2" data-umami-event="Feedback"
|
{/if}
|
||||||
>Feedback</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,78 +138,3 @@
|
|||||||
<button>close</button>
|
<button>close</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<dialog id="AfterDownloadModal" class="modal">
|
|
||||||
<div class="modal-box">
|
|
||||||
<h3 class="font-bold text-lg">Your download has started</h3>
|
|
||||||
<div class="py-4 space-y-2">
|
|
||||||
<p class="text-xl">
|
|
||||||
Hi, thanks for using my site! If you found it useful, please consider <a
|
|
||||||
href="https://liberapay.com/TheOnlyWayUp/"
|
|
||||||
target="_blank"
|
|
||||||
class="link"
|
|
||||||
data-umami-event="Donate">donating</a
|
|
||||||
> to keep this project alive.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You can also join us on <a
|
|
||||||
href="https://discord.gg/P9RHC4KCwd"
|
|
||||||
target="_blank"
|
|
||||||
class="link"
|
|
||||||
data-umami-event="Discord">discord</a
|
|
||||||
>, where we discuss updates and features.
|
|
||||||
</p>
|
|
||||||
<p class="text-lg">
|
|
||||||
Please take a look at <a
|
|
||||||
href="https://rambhat.la"
|
|
||||||
class="link"
|
|
||||||
data-umami-event="My Work">my work</a
|
|
||||||
>!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="pt-2">
|
|
||||||
<form method="dialog">
|
|
||||||
<!-- if there is a button in form, it will close the modal -->
|
|
||||||
<button
|
|
||||||
class="btn w-full btn-sm btn-ghost"
|
|
||||||
data-umami-event="AfterDownloadModal Close">Close</button
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<footer
|
|
||||||
class="footer footer-center p-4 bg-base-300 text-base-content bottom-0 fixed"
|
|
||||||
>
|
|
||||||
<aside>
|
|
||||||
<div class="grid grid-cols-3 max-w-lg w-full">
|
|
||||||
<a
|
|
||||||
href="https://liberapay.com/TheOnlyWayUp/"
|
|
||||||
target="_blank"
|
|
||||||
class="link"
|
|
||||||
data-umami-event="Footer Donate">Donate</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://rambhat.la"
|
|
||||||
target="_blank"
|
|
||||||
class="link"
|
|
||||||
data-umami-event="Footer AboutMe">About Me</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://discord.gg/P9RHC4KCwd"
|
|
||||||
target="_blank"
|
|
||||||
class="link"
|
|
||||||
data-umami-event="Footer Discord">Discord</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
Copyright © 2024 - All rights reserved by <a
|
|
||||||
href="https://rambhat.la"
|
|
||||||
class="link"
|
|
||||||
target="_blank"
|
|
||||||
data-umami-event="CopyrightHolder">Dhanush R</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</aside>
|
|
||||||
</footer>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user