ranobelib-python-sdk 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/PKG-INFO +15 -3
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/README.md +13 -2
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/pyproject.toml +2 -1
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/pyproject.toml.orig +2 -1
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/__init__.py +4 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exceptions.py +49 -1
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/sdk.py +97 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/cache.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/client.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/__init__.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/_illustrations.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/_shared.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/epub.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/fb2.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/pdf.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/txt.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/models.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/numbering.py +0 -0
- {ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ranobelib-python-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Async Python SDK for ranobelib.me: title metadata, chapter/volume downloads, and export to epub/fb2/txt/pdf
|
|
5
5
|
Keywords: ranobelib,ranobe,novel,webnovel,epub,fb2,sdk
|
|
6
6
|
Author: LLIJIYAPNIK
|
|
@@ -23,6 +23,7 @@ Requires-Python: >=3.11
|
|
|
23
23
|
Project-URL: Homepage, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk
|
|
24
24
|
Project-URL: Repository, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk
|
|
25
25
|
Project-URL: Issues, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/issues
|
|
26
|
+
Project-URL: Changelog, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/blob/main/CHANGELOG.md
|
|
26
27
|
Description-Content-Type: text/markdown
|
|
27
28
|
|
|
28
29
|
# ranobelib-python-sdk
|
|
@@ -41,7 +42,8 @@ The SDK talks to the undocumented but open JSON API behind the site (`api.cdnlib
|
|
|
41
42
|
part of the lib.social network) instead of scraping HTML.
|
|
42
43
|
|
|
43
44
|
> **Status:** all the calls in the quickstart below are implemented — metadata, table of
|
|
44
|
-
> contents, single/bulk chapter and volume downloads,
|
|
45
|
+
> contents, single/bulk chapter and volume downloads, whole-title bulk download, translation
|
|
46
|
+
> selection, and export to
|
|
45
47
|
> `"txt"`, `"fb2"`, `"epub"`, or `"pdf"` (cover + in-chapter illustrations embedded in
|
|
46
48
|
> epub/pdf). Raw API responses are cached to disk, and the underlying client bounds
|
|
47
49
|
> concurrency, paces requests, and retries 429/5xx with backoff. `fmt="pdf"` needs
|
|
@@ -83,17 +85,27 @@ async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel")
|
|
|
83
85
|
translations = await lib.get_translations(volume=6, number="51.6")
|
|
84
86
|
chapter = await lib.get_chapter(volume=6, number="51.6", branch_id=...)
|
|
85
87
|
|
|
88
|
+
all_volumes = await lib.download_title(translation_index=0, chapter_delay=0.5)
|
|
89
|
+
|
|
86
90
|
await lib.export(chapters, fmt="epub", path="output.epub")
|
|
87
91
|
```
|
|
88
92
|
|
|
89
93
|
This is the full current public API — everything above is implemented and works today.
|
|
90
94
|
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
Complete, runnable scripts for every feature above — title metadata, table of contents,
|
|
98
|
+
single/bulk chapter and volume downloads, translation selection, whole-title bulk download,
|
|
99
|
+
disk caching, export to all four formats, and error handling — live in
|
|
100
|
+
**[`examples/`](examples/)**, one heavily commented file per feature. Start there for
|
|
101
|
+
copy-pasteable code.
|
|
102
|
+
|
|
91
103
|
## Documentation
|
|
92
104
|
|
|
93
105
|
Full API reference and guides: **<https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/>**
|
|
94
106
|
(built with `mkdocs-material` + `mkdocstrings`, deployed on push to `main`). `CLAUDE.md`
|
|
95
107
|
documents the full architecture, public API contract, and the reasoning behind the SDK's
|
|
96
|
-
design decisions.
|
|
108
|
+
design decisions. [`CHANGELOG.md`](CHANGELOG.md) lists what changed in each release.
|
|
97
109
|
|
|
98
110
|
## License
|
|
99
111
|
|
|
@@ -14,7 +14,8 @@ The SDK talks to the undocumented but open JSON API behind the site (`api.cdnlib
|
|
|
14
14
|
part of the lib.social network) instead of scraping HTML.
|
|
15
15
|
|
|
16
16
|
> **Status:** all the calls in the quickstart below are implemented — metadata, table of
|
|
17
|
-
> contents, single/bulk chapter and volume downloads,
|
|
17
|
+
> contents, single/bulk chapter and volume downloads, whole-title bulk download, translation
|
|
18
|
+
> selection, and export to
|
|
18
19
|
> `"txt"`, `"fb2"`, `"epub"`, or `"pdf"` (cover + in-chapter illustrations embedded in
|
|
19
20
|
> epub/pdf). Raw API responses are cached to disk, and the underlying client bounds
|
|
20
21
|
> concurrency, paces requests, and retries 429/5xx with backoff. `fmt="pdf"` needs
|
|
@@ -56,17 +57,27 @@ async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel")
|
|
|
56
57
|
translations = await lib.get_translations(volume=6, number="51.6")
|
|
57
58
|
chapter = await lib.get_chapter(volume=6, number="51.6", branch_id=...)
|
|
58
59
|
|
|
60
|
+
all_volumes = await lib.download_title(translation_index=0, chapter_delay=0.5)
|
|
61
|
+
|
|
59
62
|
await lib.export(chapters, fmt="epub", path="output.epub")
|
|
60
63
|
```
|
|
61
64
|
|
|
62
65
|
This is the full current public API — everything above is implemented and works today.
|
|
63
66
|
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
Complete, runnable scripts for every feature above — title metadata, table of contents,
|
|
70
|
+
single/bulk chapter and volume downloads, translation selection, whole-title bulk download,
|
|
71
|
+
disk caching, export to all four formats, and error handling — live in
|
|
72
|
+
**[`examples/`](examples/)**, one heavily commented file per feature. Start there for
|
|
73
|
+
copy-pasteable code.
|
|
74
|
+
|
|
64
75
|
## Documentation
|
|
65
76
|
|
|
66
77
|
Full API reference and guides: **<https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/>**
|
|
67
78
|
(built with `mkdocs-material` + `mkdocstrings`, deployed on push to `main`). `CLAUDE.md`
|
|
68
79
|
documents the full architecture, public API contract, and the reasoning behind the SDK's
|
|
69
|
-
design decisions.
|
|
80
|
+
design decisions. [`CHANGELOG.md`](CHANGELOG.md) lists what changed in each release.
|
|
70
81
|
|
|
71
82
|
## License
|
|
72
83
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ranobelib-python-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Async Python SDK for ranobelib.me: title metadata, chapter/volume downloads, and export to epub/fb2/txt/pdf"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -40,6 +40,7 @@ email = "kas4wot@yandex.ru"
|
|
|
40
40
|
Homepage = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk"
|
|
41
41
|
Repository = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk"
|
|
42
42
|
Issues = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/issues"
|
|
43
|
+
Changelog = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/blob/main/CHANGELOG.md"
|
|
43
44
|
|
|
44
45
|
[build-system]
|
|
45
46
|
requires = ["uv_build>=0.11.26,<0.12.0"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ranobelib-python-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Async Python SDK for ranobelib.me: title metadata, chapter/volume downloads, and export to epub/fb2/txt/pdf"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
Homepage = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk"
|
|
32
32
|
Repository = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk"
|
|
33
33
|
Issues = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/issues"
|
|
34
|
+
Changelog = "https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/blob/main/CHANGELOG.md"
|
|
34
35
|
|
|
35
36
|
[build-system]
|
|
36
37
|
requires = ["uv_build>=0.11.26,<0.12.0"]
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Async Python SDK for ranobelib.me."""
|
|
2
2
|
|
|
3
3
|
from ranobelib.exceptions import (
|
|
4
|
+
AmbiguousChapter,
|
|
4
5
|
AuthRequiredError,
|
|
5
6
|
ChapterNotFoundError,
|
|
7
|
+
MultipleTitleTranslationsError,
|
|
6
8
|
MultipleTranslationsError,
|
|
7
9
|
RanobeLibError,
|
|
8
10
|
RateLimitError,
|
|
@@ -13,10 +15,12 @@ from ranobelib.models import Chapter, ChapterBranch, Title, Volume
|
|
|
13
15
|
from ranobelib.sdk import RanobeLib
|
|
14
16
|
|
|
15
17
|
__all__ = [
|
|
18
|
+
"AmbiguousChapter",
|
|
16
19
|
"AuthRequiredError",
|
|
17
20
|
"Chapter",
|
|
18
21
|
"ChapterBranch",
|
|
19
22
|
"ChapterNotFoundError",
|
|
23
|
+
"MultipleTitleTranslationsError",
|
|
20
24
|
"MultipleTranslationsError",
|
|
21
25
|
"RanobeLib",
|
|
22
26
|
"RanobeLibError",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from ranobelib.models import ChapterBranch
|
|
@@ -93,6 +93,54 @@ def _describe_branch(branch: ChapterBranch) -> str:
|
|
|
93
93
|
return branch.user.username
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
class AmbiguousChapter(NamedTuple):
|
|
97
|
+
"""One chapter that ``RanobeLib.download_title()`` couldn't resolve a translation for.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
volume: The chapter's volume number.
|
|
101
|
+
number: The chapter number.
|
|
102
|
+
branches: The chapter's available translations, as returned by
|
|
103
|
+
``RanobeLib.get_translations()``.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
volume: str
|
|
107
|
+
number: str
|
|
108
|
+
branches: list[ChapterBranch]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class MultipleTitleTranslationsError(RanobeLibError):
|
|
112
|
+
"""Raised by ``download_title()`` when one or more chapters have an unresolved translation.
|
|
113
|
+
|
|
114
|
+
Unlike ``MultipleTranslationsError`` (one chapter), this can list several: a title-wide
|
|
115
|
+
bulk download checks every chapter up front rather than stopping at the first ambiguous
|
|
116
|
+
one, so a caller doesn't have to fix one chapter, rerun, hit the next, and rerun again.
|
|
117
|
+
Pass ``branch_id`` or ``translation_index`` to ``download_title()`` to resolve them, or
|
|
118
|
+
fetch the listed chapters individually with ``get_chapter(..., branch_id=...)``.
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
slug_url: The title's ``{id}--{slug}`` identifier.
|
|
122
|
+
chapters: The ambiguous chapters and their available translations.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self, slug_url: str, *, chapters: list[AmbiguousChapter]) -> None:
|
|
126
|
+
self.slug_url = slug_url
|
|
127
|
+
self.chapters = chapters
|
|
128
|
+
lines = [
|
|
129
|
+
f" volume={chapter.volume!r} number={chapter.number!r}: "
|
|
130
|
+
+ ", ".join(
|
|
131
|
+
f"branch_id={branch.branch_id} ({_describe_branch(branch)})"
|
|
132
|
+
for branch in chapter.branches
|
|
133
|
+
)
|
|
134
|
+
for chapter in chapters
|
|
135
|
+
]
|
|
136
|
+
super().__init__(
|
|
137
|
+
f"{len(chapters)} chapter(s) in {slug_url!r} have more than one translation "
|
|
138
|
+
"unresolved by the given branch_id/translation_index. Pass branch_id or "
|
|
139
|
+
"translation_index to download_title() to select one for every chapter listed, "
|
|
140
|
+
"or fetch them individually with get_chapter(..., branch_id=...):\n" + "\n".join(lines)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
96
144
|
class AuthRequiredError(RanobeLibError):
|
|
97
145
|
"""Raised when the requested content requires authorization (paid or early access).
|
|
98
146
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from types import TracebackType
|
|
7
8
|
from typing import Any, Self
|
|
@@ -9,7 +10,9 @@ from typing import Any, Self
|
|
|
9
10
|
from ranobelib.cache import DEFAULT_CACHE_DIR, DiskCache
|
|
10
11
|
from ranobelib.client import ApiClient
|
|
11
12
|
from ranobelib.exceptions import (
|
|
13
|
+
AmbiguousChapter,
|
|
12
14
|
ChapterNotFoundError,
|
|
15
|
+
MultipleTitleTranslationsError,
|
|
13
16
|
MultipleTranslationsError,
|
|
14
17
|
VolumeNotFoundError,
|
|
15
18
|
)
|
|
@@ -215,6 +218,82 @@ class RanobeLib:
|
|
|
215
218
|
raw_chapters = await self._get_chapters()
|
|
216
219
|
return [await self._build_volume(volume, raw_chapters) for volume in volumes]
|
|
217
220
|
|
|
221
|
+
async def download_title(
|
|
222
|
+
self,
|
|
223
|
+
*,
|
|
224
|
+
branch_id: int | None = None,
|
|
225
|
+
translation_index: int | None = None,
|
|
226
|
+
chapter_delay: float = 0.0,
|
|
227
|
+
) -> list[Volume]:
|
|
228
|
+
"""Download every chapter of the title, across all its volumes.
|
|
229
|
+
|
|
230
|
+
Fetches the chapter list once, then every chapter's content, sequentially, in the
|
|
231
|
+
API's own order — same pacing/retry/backoff as any other chapter fetch (see
|
|
232
|
+
``ApiClient``), plus ``chapter_delay`` on top if given.
|
|
233
|
+
|
|
234
|
+
Resolving translations is checked for the whole title up front, not chapter by
|
|
235
|
+
chapter: if any chapters have more than one translation, this fetches nothing and
|
|
236
|
+
raises ``MultipleTitleTranslationsError`` listing *all* of them at once (unless
|
|
237
|
+
``branch_id`` or ``translation_index`` resolves every one of them), rather than
|
|
238
|
+
failing partway through a long download on the first ambiguous chapter.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
branch_id: Translation to use for every chapter that has more than one — same
|
|
242
|
+
meaning as ``get_chapter(..., branch_id=...)``, applied title-wide. Chapters
|
|
243
|
+
with only one translation are unaffected. Mutually exclusive with
|
|
244
|
+
``translation_index``.
|
|
245
|
+
translation_index: Alternative to ``branch_id``: pick the translation at this
|
|
246
|
+
position in each ambiguous chapter's ``branches`` list (``0`` for the first,
|
|
247
|
+
and so on) — useful when ambiguous chapters don't share one ``branch_id``
|
|
248
|
+
across the title. Mutually exclusive with ``branch_id``.
|
|
249
|
+
chapter_delay: Extra delay, in seconds, after fetching each chapter, on top of
|
|
250
|
+
``ApiClient``'s own per-request pacing — to be gentler on the API during a
|
|
251
|
+
large bulk download. Defaults to no extra delay.
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
ValueError: If both ``branch_id`` and ``translation_index`` are given.
|
|
255
|
+
MultipleTitleTranslationsError: If the title has one or more chapters with more
|
|
256
|
+
than one translation that ``branch_id``/``translation_index`` didn't resolve.
|
|
257
|
+
"""
|
|
258
|
+
if branch_id is not None and translation_index is not None:
|
|
259
|
+
raise ValueError("Pass at most one of branch_id or translation_index, not both.")
|
|
260
|
+
|
|
261
|
+
raw_chapters = await self._get_chapters()
|
|
262
|
+
|
|
263
|
+
planned: list[tuple[str, str, int | None]] = []
|
|
264
|
+
unresolved: list[AmbiguousChapter] = []
|
|
265
|
+
for item in raw_chapters:
|
|
266
|
+
volume_str: str = item["volume"]
|
|
267
|
+
number: str = item["number"]
|
|
268
|
+
branches = item.get("branches") or []
|
|
269
|
+
if len(branches) <= 1:
|
|
270
|
+
planned.append((volume_str, number, None))
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
resolved, selected = _resolve_bulk_branch_id(
|
|
274
|
+
branches, branch_id=branch_id, translation_index=translation_index
|
|
275
|
+
)
|
|
276
|
+
if resolved:
|
|
277
|
+
planned.append((volume_str, number, selected))
|
|
278
|
+
else:
|
|
279
|
+
unresolved.append(
|
|
280
|
+
AmbiguousChapter(
|
|
281
|
+
volume=volume_str,
|
|
282
|
+
number=number,
|
|
283
|
+
branches=[ChapterBranch.model_validate(branch) for branch in branches],
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if unresolved:
|
|
288
|
+
raise MultipleTitleTranslationsError(self._slug_url, chapters=unresolved)
|
|
289
|
+
|
|
290
|
+
chapters = []
|
|
291
|
+
for index, (volume_str, number, selected_branch_id) in enumerate(planned):
|
|
292
|
+
chapters.append(await self._fetch_chapter(volume_str, number, selected_branch_id))
|
|
293
|
+
if chapter_delay and index < len(planned) - 1:
|
|
294
|
+
await asyncio.sleep(chapter_delay)
|
|
295
|
+
return _group_into_volumes(chapters)
|
|
296
|
+
|
|
218
297
|
async def export(self, chapters: list[Chapter], *, fmt: str, path: str | Path) -> Path:
|
|
219
298
|
"""Export chapters to a file.
|
|
220
299
|
|
|
@@ -307,6 +386,24 @@ def _group_into_volumes(chapters: list[Chapter]) -> list[Volume]:
|
|
|
307
386
|
return [Volume(number=number, chapters=items) for number, items in grouped.items()]
|
|
308
387
|
|
|
309
388
|
|
|
389
|
+
def _resolve_bulk_branch_id(
|
|
390
|
+
branches: list[dict[str, Any]], *, branch_id: int | None, translation_index: int | None
|
|
391
|
+
) -> tuple[bool, int | None]:
|
|
392
|
+
"""Resolve which branch_id to request for one ambiguous chapter in ``download_title()``.
|
|
393
|
+
|
|
394
|
+
Returns ``(True, branch_id)`` if resolved, ``(False, None)`` if ``branch_id``/
|
|
395
|
+
``translation_index`` doesn't identify one of this chapter's branches.
|
|
396
|
+
"""
|
|
397
|
+
if branch_id is not None:
|
|
398
|
+
for branch in branches:
|
|
399
|
+
if branch.get("branch_id") == branch_id:
|
|
400
|
+
return True, branch_id
|
|
401
|
+
return False, None
|
|
402
|
+
if translation_index is not None and 0 <= translation_index < len(branches):
|
|
403
|
+
return True, branches[translation_index].get("branch_id")
|
|
404
|
+
return False, None
|
|
405
|
+
|
|
406
|
+
|
|
310
407
|
def _find_raw_chapter(
|
|
311
408
|
raw_chapters: list[dict[str, Any]], volume_str: str, number: str
|
|
312
409
|
) -> dict[str, Any] | None:
|
|
File without changes
|
|
File without changes
|
{ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/__init__.py
RENAMED
|
File without changes
|
{ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/_illustrations.py
RENAMED
|
File without changes
|
{ranobelib_python_sdk-0.1.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/_shared.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|