4 Commits

Author SHA1 Message Date
TheOnlyWayUp 405cb7bcfe Exclude .epub from .gitignore 2024-07-08 12:15:12 +00:00
TheOnlyWayUp d819db27d3 Update VSCode settings.json 2024-07-08 12:14:59 +00:00
TheOnlyWayUp 3edd35829d feat(api/tests): Story testing 2024-07-08 12:14:38 +00:00
TheOnlyWayUp 0eedef7653 fix(api): Make package, for importing in tests 2024-07-08 12:14:08 +00:00
9 changed files with 67 additions and 69 deletions
-1
View File
@@ -1,6 +1,5 @@
__pycache__
venv
*epub
data
*ipynb
build
+7 -1
View File
@@ -1,3 +1,9 @@
{
"python.analysis.autoImportCompletions": true
"python.analysis.autoImportCompletions": true,
"vscord.app.privacyMode.enable": false,
"python.testing.pytestArgs": [
"src"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
+3 -4
View File
@@ -9,12 +9,11 @@ Stars ⭐ are appreciated. Thanks!
## Features
- ⚡ Lightweight Frontend and Minimal Javascript.
- 🪙 Supports Authentication (Download paid stories from your account!)
- 🌐 API Support (Visit the `/docs` path on your instance for more.)
- 🐇 Fast Generation, Ratelimit Handling.
- 🐇 Fast Generation, Basic Ratelimit Handling.
- 🐳 Docker Support
- 🏷️ Generated EPUB File includes Metadata. (Dublin Core Spec)
- 📖 Plays well with E-Readers. (Kindle Support if KOReader present, ReMarkable, KOBO, ...)
- 📖 Plays well with E-Readers. (Kindle Support if KOReader present)
- 💻 Easily Hackable. Extend with ease.
@@ -32,5 +31,5 @@ My thanks to [aerkalov/ebooklib](https://github.com/aerkalov/ebooklib) for a fas
---
<div align="center">
<p>TheOnlyWayUp © 2024</p>
<p>TheOnlyWayUp © 2023</p>
</div>
View File
+4 -4
View File
@@ -174,7 +174,7 @@ async def add_chapters(
):
chapters = []
for cidx, part in enumerate(data["parts"]):
for part in data["parts"]:
content = await fetch_part_content(part["id"], cookies=cookies)
title = part["title"]
clean_title = slugify(title)
@@ -182,7 +182,7 @@ async def add_chapters(
# Thanks https://eu17.proxysite.com/process.php?d=5VyWYcoQl%2BVF0BYOuOavtvjOloFUZz2BJ%2Fepiusk6Nz7PV%2B9i8rs7cFviGftrBNll%2B0a3qO7UiDkTt4qwCa0fDES&b=1
chapter = epub.EpubHtml(
title=title,
file_name=f"{cidx}.xhtml", # Used to be clean_title.xhtml, but that broke Arabic support as slugify turns arabic strings into '', leading to multiple files with the same name, breaking those chapters.
file_name=f"{clean_title}.xhtml",
lang=data["language"]["name"],
)
@@ -200,11 +200,11 @@ async def add_chapters(
img = epub.EpubImage(
media_type="image/jpeg",
content=await response.read(),
file_name=f"static/{cidx}/{idx}.jpeg",
file_name=f"static/{clean_title}/{idx}.jpeg",
)
book.add_item(img)
content = content.replace(
str(image), f'<img src="static/{cidx}/{idx}.jpeg"/>'
str(image), f'<img src="static/{clean_title}/{idx}.jpeg"/>'
)
chapter.set_content(f"<h1>{title}</h1>" + content)
+1 -2
View File
@@ -34,11 +34,10 @@ async def download_book(
if username and not password or password and not username:
return HTMLResponse(
status_code=422,
content='Include both the username <u>and</u> password, or neither. Support is available on the <a href="https://discord.gg/P9RHC4KCwd" target="_blank">Discord</a>',
content='Include both the username _and_ password, or neither. Support is available on the <a href="https://discord.gg/P9RHC4KCwd" target="_blank">Discord</a>',
)
if username and password:
# username and password are URL-Encoded by the frontend. FastAPI automatically decodes them.
try:
cookies = await wp_get_cookies(username=username, password=password)
except ValueError:
View File
+31
View File
@@ -0,0 +1,31 @@
from .. import create_book
import pytest
STORY_ID = 372219540
@pytest.mark.asyncio
async def test_retrieve_story():
story_data = await create_book.retrieve_story(STORY_ID)
story_data.pop("modifyDate", None) # Subject to change
response = {
"id": "372219540",
"title": "WPD Test",
"createDate": "2024-07-02T15:29:13Z",
# "modifyDate": "2024-07-02T15:41:26Z",
"language": {"name": "English"},
"user": {"username": "KindaAssNgl"},
"description": "Testing story for WPD.",
"cover": r"https:\/\/img.wattpad.com\/cover\/372219540-256-k908955.jpg",
"completed": False,
"tags": ["testing", "towu", "wpd"],
"mature": False,
"url": r"https:\/\/www.wattpad.com\/story\/372219540-wpd-test",
"parts": [{"id": 1458516761, "title": "Ganesh"}],
"isPaywalled": False,
}
assert story_data == response
+15 -51
View File
@@ -6,42 +6,21 @@
username: "",
password: "",
};
let after_download_page = false;
let url = "";
let raw_story_id = "";
let is_part_id = false;
let button_disabled = false;
$: button_disabled =
!story_id ||
(is_paid_story && !(credentials.username && credentials.password));
$: {
is_part_id = false;
if (raw_story_id.includes("wattpad.com")) {
// Originally, I was going to call the Wattpad API (wattpad.com/api/v3/stories/${story_id}), but Wattpad kept blocking those requests. I suspect it has something to do with the Origin header, I wasn't able to remove it.
// In the future, if this is considered, it would be cool if we could derive the Story ID from a pasted Part URL. Refer to @AaronBenDaniel's https://github.com/AaronBenDaniel/WattpadDownloader/blob/49b29b245188149f2d24c0b1c59e4c7f90f289a9/src/api/src/create_book.py#L156 (https://www.wattpad.com/api/v3/story_parts/{part_id}?fields=url).
if (raw_story_id.includes("/story/")) {
// https://wattpad.com/story/237369078-wattpad-books-presents
story_id = raw_story_id.split("/story/")[1].split("-")[0];
raw_story_id = story_id;
} else if (raw_story_id.includes("/stories/")) {
// https://www.wattpad.com/api/v3/stories/237369078?fields=...
story_id = raw_story_id.split("/stories/")[1].split("?")[0];
raw_story_id = story_id;
} else {
// https://www.wattpad.com/939051741-wattpad-books-presents-part-name
is_part_id = true;
raw_story_id = "";
story_id = "";
}
} else {
story_id = parseInt(raw_story_id) || ""; // parseInt returns NaN for undefined values.
raw_story_id = story_id;
}
}
$: url =
`/download/${story_id}?om=1` +
(download_images ? "&download_images=true" : "") +
(is_paid_story
? `&username=${credentials.username}&password=${credentials.password}`
: "");
</script>
<div>
@@ -60,42 +39,27 @@
Download your favourite books with a single click!
</p>
<ul class="pt-4 list list-inside text-xl">
<!-- TODO: 'max-lg: hidden' to hide on screen sizes smaller than lg. I'll do this when I figure out how to make this show up _below_ the card on smaller screen sizes. -->
<li>07/24 - 🔡 RTL Language support! (Arabic, etc.)</li>
<li>06/24 - 🔑 Authenticated Downloads!</li>
<li>06/24 - 🖼️ Image Downloading!</li>
<li>06/24 - 🎉 Image Downloading!</li>
</ul>
</div>
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
<form class="card-body">
<div class="form-control">
<input
type="text"
type="number"
placeholder="Story ID"
class="input input-bordered"
class:input-warning={is_part_id}
bind:value={raw_story_id}
bind:value={story_id}
required
name="story_id"
/>
<label class="label" for="story_id">
{#if is_part_id}
<p class=" text-red-500">
Refer to (<button
class="link font-semibold"
onclick="StoryIDTutorialModal.showModal()"
data-umami-event="Part StoryIDTutorialModal Open"
>How to get a Story ID</button
>).
</p>
{:else}
<button
class="label-text link font-semibold"
onclick="StoryIDTutorialModal.showModal()"
data-umami-event="StoryIDTutorialModal Open"
>How to get a Story ID</button
>
{/if}
</label>
<label class="cursor-pointer label">
<span class="label-text"
@@ -203,13 +167,13 @@
></button
>
</form>
<h3 class="font-bold text-lg">Retrieving a Story ID</h3>
<ol class="list list-disc list-inside py-4 space-y-4">
<h3 class="font-bold text-lg">Downloading a Story</h3>
<ol class="list list-disc list-inside py-4 space-y-2">
<li>
Open the Story URL, this page includes the story description and tags.
(For example, <span class="font-mono bg-slate-100 p-1"
Open the Story URL (For example, <span
class="font-mono bg-slate-100 p-1"
>wattpad.com/story/237369078-wattpad-books-presents</span
>).
>)
</li>
<li>
Copy the numbers after the <span class="font-mono bg-slate-100 p-1"