FanFicFare adapters fetch metadata and chapters, and its epub writer builds each book from the store. Environment variables configure the app, and a default personal.ini is copied to the config folder on first start.
156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
"""Build an epub from stored data without network access, then read it back."""
|
|
|
|
import base64
|
|
import datetime
|
|
import re
|
|
import zipfile
|
|
|
|
import pytest
|
|
from defusedxml import ElementTree
|
|
|
|
from bindery import config, fff
|
|
from bindery.rules import compile_rules, normalize_rules
|
|
|
|
URL = "https://www.royalroad.com/fiction/1234/test-story"
|
|
PNG = base64.b64decode(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
|
)
|
|
IMG_URL = "https://www.royalroadcdn.com/public/img.png"
|
|
COVER = {"url": "https://www.royalroadcdn.com/public/covers/1234.png", "ext": "png", "mime": "image/png", "data": PNG}
|
|
|
|
|
|
def metahtml(url=URL):
|
|
adapter = fff.get_adapter(url)
|
|
story = adapter.story
|
|
story.setMetadata("title", "Test Story")
|
|
story.addToList("author", "Writer")
|
|
story.addToList("authorId", "42")
|
|
story.setMetadata("description", "<p>About the story</p>")
|
|
story.setMetadata("datePublished", datetime.datetime(2024, 1, 2))
|
|
story.setMetadata("dateUpdated", datetime.datetime(2024, 3, 4))
|
|
story.addToList("genre", "Fantasy")
|
|
return story.dump_html_metadata()
|
|
|
|
|
|
def chapters():
|
|
base = "https://www.royalroad.com/fiction/1234/test-story/chapter/{0}/c{0}"
|
|
return [
|
|
{"url": base.format(10), "title": "One", "html": '<div><p class="rnd1">Zorian wakes.</p></div>'},
|
|
{"url": base.format(11), "title": "Two", "html": f'<div><p>Picture</p><img src="{IMG_URL}"/></div>'},
|
|
{"url": base.format(15), "title": "Three", "html": "<div><p>Zorian again.</p></div>"},
|
|
]
|
|
|
|
|
|
def test_build_and_read_back(tmp_path):
|
|
rules = compile_rules(normalize_rules([{"pattern": "Zorian", "replacement": "Z"}]))
|
|
out = tmp_path / "book.epub"
|
|
images = [{"url": IMG_URL, "ext": "png", "mime": "image/png", "data": PNG}]
|
|
meta = fff.build_epub(URL, metahtml(), chapters(), images, None, rules, {"title": "Kept Title"}, out)
|
|
assert meta["title"] == "Kept Title"
|
|
assert meta["authors"] == ["Writer"]
|
|
assert meta["pubdate"].startswith("2024-01-02")
|
|
|
|
with zipfile.ZipFile(out) as z:
|
|
names = z.namelist()
|
|
text = "".join(z.read(n).decode("utf-8", "ignore") for n in names if n.endswith(".xhtml"))
|
|
opf = z.read("content.opf").decode()
|
|
assert "Z wakes." in text and "Zorian" not in text
|
|
assert any(n.startswith("OEBPS/images/ffdl-") and n.endswith(".png") for n in names)
|
|
assert re.search(r"<dc:title[^>]*>Kept Title</dc:title>", opf)
|
|
|
|
contents = fff.read_epub(out)
|
|
assert [c["title"] for c in contents.chapters] == ["One", "Two", "Three"]
|
|
assert [c["url"] for c in contents.chapters] == [c["url"] for c in chapters()]
|
|
assert IMG_URL in contents.chapters[1]["html"]
|
|
assert contents.images and contents.images[0]["url"] == IMG_URL
|
|
|
|
|
|
def test_a_title_with_entities_builds_and_imports_in_fanficfare_form(tmp_path):
|
|
# FanFicFare's form, as the site titles in the store have it.
|
|
title = "Folly & Magic <1>"
|
|
out = tmp_path / "book.epub"
|
|
fff.build_epub(URL, metahtml(), [dict(chapters()[0], title=title)], [], None, [], {}, out)
|
|
with zipfile.ZipFile(out) as z:
|
|
for name in z.namelist():
|
|
if name.endswith((".xhtml", ".ncx", ".opf")):
|
|
ElementTree.fromstring(z.read(name))
|
|
# An import stores the same title as a download, so the next update finds no rename.
|
|
assert fff.read_epub(out).chapters[0]["title"] == title
|
|
assert fff.plain_title(title) == "Folly & Magic <1>"
|
|
assert fff.escape_title("Folly & Magic <1>") == title
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("personal_ini", "expected"),
|
|
[
|
|
("", "coveronly"),
|
|
("[www.royalroad.com]\ninclude_images:false\n", "coveronly"),
|
|
("[defaults]\ninclude_images:true\n", "true"),
|
|
("[defaults]\ninclude_images:coveronly\n", "coveronly"),
|
|
],
|
|
)
|
|
def test_include_images_is_never_off(personal_ini, expected, tmp_path, monkeypatch):
|
|
path = tmp_path / "personal.ini"
|
|
path.write_text(personal_ini)
|
|
monkeypatch.setattr(config, "PERSONAL_INI", path)
|
|
assert fff.get_configuration(URL).getConfig("include_images") == expected
|
|
|
|
|
|
@pytest.mark.parametrize("personal_ini", ["", "[defaults]\ninclude_images:false\n"])
|
|
def test_the_cover_goes_in_the_epub_without_include_images(personal_ini, tmp_path, monkeypatch):
|
|
path = tmp_path / "personal.ini"
|
|
path.write_text(personal_ini)
|
|
monkeypatch.setattr(config, "PERSONAL_INI", path)
|
|
out = tmp_path / "book.epub"
|
|
fff.build_epub(URL, metahtml(), chapters(), [], COVER, [], {}, out)
|
|
with zipfile.ZipFile(out) as z:
|
|
names = z.namelist()
|
|
opf = z.read("content.opf").decode()
|
|
assert "OEBPS/cover.xhtml" in names
|
|
assert any(n.startswith("OEBPS/images/cover.") for n in names)
|
|
assert re.search(r'<meta name="cover" content="[^"]+"', opf)
|
|
|
|
|
|
def test_locked_fields_replace_site_metadata(tmp_path):
|
|
out = tmp_path / "book.epub"
|
|
overrides = {
|
|
"title": "Kept Title",
|
|
"authors": ["Pen Name", "Co Writer"],
|
|
"comments": "<p>Kept blurb</p>",
|
|
"series": ["Kept Saga", 3.0],
|
|
"pubdate": "2019-05-06T00:00:00+00:00",
|
|
"languages": "de",
|
|
"tags": ["mine", "reread"],
|
|
}
|
|
meta = fff.build_epub(URL, metahtml(), chapters(), [], None, [], overrides, out)
|
|
assert meta["authors"] == ["Pen Name", "Co Writer"]
|
|
assert meta["tags"] == ["mine", "reread"]
|
|
with zipfile.ZipFile(out) as z:
|
|
opf = z.read("content.opf").decode()
|
|
assert re.findall(r"<dc:creator[^>]*>([^<]*)</dc:creator>", opf) == ["Pen Name", "Co Writer"]
|
|
assert re.findall(r"<dc:subject>([^<]*)</dc:subject>", opf) == ["mine", "reread"]
|
|
assert 'name="calibre:series" content="Kept Saga"' in opf
|
|
assert 'name="calibre:series_index" content="3.00"' in opf
|
|
assert "<dc:language>de</dc:language>" in opf
|
|
assert "2019-05-06" in opf
|
|
assert "Kept blurb" in opf
|
|
|
|
|
|
def test_unlocked_fields_keep_site_metadata(tmp_path):
|
|
out = tmp_path / "book.epub"
|
|
meta = fff.build_epub(URL, metahtml(), chapters(), [], None, [], {}, out)
|
|
assert meta["title"] == "Test Story"
|
|
assert meta["authors"] == ["Writer"]
|
|
assert "Fantasy" in meta["tags"]
|
|
|
|
|
|
def test_supported_sites():
|
|
assert fff.is_supported_site("www.royalroad.com")
|
|
assert fff.is_supported_site("WWW.RoyalRoad.com ")
|
|
assert not fff.is_supported_site("example.com")
|
|
|
|
|
|
def test_normalize_url():
|
|
assert fff.normalize_url(URL) == ("https://www.royalroad.com/fiction/1234", "www.royalroad.com")
|
|
assert fff.normalize_url("https://example.com/nothing") is None
|