ranobelib-python-sdk 0.2.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.
Files changed (21) hide show
  1. ranobelib_python_sdk-0.3.0/PKG-INFO +112 -0
  2. ranobelib_python_sdk-0.3.0/README.md +84 -0
  3. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/pyproject.toml +2 -1
  4. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/pyproject.toml.orig +2 -1
  5. ranobelib_python_sdk-0.2.0/PKG-INFO +0 -478
  6. ranobelib_python_sdk-0.2.0/README.md +0 -451
  7. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/__init__.py +0 -0
  8. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/cache.py +0 -0
  9. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/client.py +0 -0
  10. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exceptions.py +0 -0
  11. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/__init__.py +0 -0
  12. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/_illustrations.py +0 -0
  13. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/_shared.py +0 -0
  14. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/epub.py +0 -0
  15. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/fb2.py +0 -0
  16. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/pdf.py +0 -0
  17. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/exporters/txt.py +0 -0
  18. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/models.py +0 -0
  19. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/numbering.py +0 -0
  20. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/py.typed +0 -0
  21. {ranobelib_python_sdk-0.2.0 → ranobelib_python_sdk-0.3.0}/src/ranobelib/sdk.py +0 -0
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: ranobelib-python-sdk
3
+ Version: 0.3.0
4
+ Summary: Async Python SDK for ranobelib.me: title metadata, chapter/volume downloads, and export to epub/fb2/txt/pdf
5
+ Keywords: ranobelib,ranobe,novel,webnovel,epub,fb2,sdk
6
+ Author: LLIJIYAPNIK
7
+ Author-email: LLIJIYAPNIK <kas4wot@yandex.ru>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: ebooklib>=0.20
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: pydantic>=2.7
21
+ Requires-Dist: weasyprint>=69.0
22
+ Requires-Python: >=3.11
23
+ Project-URL: Homepage, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk
24
+ Project-URL: Repository, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk
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
27
+ Description-Content-Type: text/markdown
28
+
29
+ # ranobelib-python-sdk
30
+
31
+ [![PyPI](https://img.shields.io/pypi/v/ranobelib-python-sdk)](https://pypi.org/project/ranobelib-python-sdk/)
32
+ [![License: MIT](https://img.shields.io/github/license/LLIJIYAPNIK/ranobelib-python-sdk)](LICENSE)
33
+ [![Python versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)](pyproject.toml)
34
+ [![CI](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml)
35
+ [![Coverage Status](https://coveralls.io/repos/github/LLIJIYAPNIK/ranobelib-python-sdk/badge.svg?branch=main)](https://coveralls.io/github/LLIJIYAPNIK/ranobelib-python-sdk?branch=main)
36
+ [![Docs](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml)
37
+
38
+ Async Python SDK for [ranobelib.me](https://ranobelib.me): fetch title metadata, download
39
+ chapters and volumes, and export to epub, fb2, txt, or pdf.
40
+
41
+ The SDK talks to the undocumented but open JSON API behind the site (`api.cdnlibs.org`,
42
+ part of the lib.social network) instead of scraping HTML.
43
+
44
+ > **Status:** all the calls in the quickstart below are implemented — metadata, table of
45
+ > contents, single/bulk chapter and volume downloads, whole-title bulk download, translation
46
+ > selection, and export to
47
+ > `"txt"`, `"fb2"`, `"epub"`, or `"pdf"` (cover + in-chapter illustrations embedded in
48
+ > epub/pdf). Raw API responses are cached to disk, and the underlying client bounds
49
+ > concurrency, paces requests, and retries 429/5xx with backoff. `fmt="pdf"` needs
50
+ > WeasyPrint's native dependencies (Pango/cairo/GTK3) installed on the system — `pip
51
+ > install`/`uv add` alone doesn't guarantee that on every platform; without them,
52
+ > `export(fmt="pdf")` raises the same "unknown format" error the other three formats don't.
53
+ > Authorization (private/18+/paid content) is out of scope — see `CLAUDE.md` for the full
54
+ > architecture and design decisions behind all of this.
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ uv add ranobelib-python-sdk
60
+ ```
61
+
62
+ or
63
+
64
+ ```bash
65
+ pip install ranobelib-python-sdk
66
+ ```
67
+
68
+ Requires Python 3.11+.
69
+
70
+ ## Quickstart
71
+
72
+ ```python
73
+ from ranobelib import RanobeLib
74
+
75
+ async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel") as lib:
76
+ info = await lib.get_info()
77
+ toc = await lib.get_table_of_contents()
78
+
79
+ chapter = await lib.get_chapter(volume=6, number="51.6")
80
+ volume = await lib.get_volume(volume=6)
81
+
82
+ chapters = await lib.get_chapters([(6, "51.6"), (6, "52")])
83
+ volumes = await lib.get_volumes([1, 2, 3])
84
+
85
+ translations = await lib.get_translations(volume=6, number="51.6")
86
+ chapter = await lib.get_chapter(volume=6, number="51.6", branch_id=...)
87
+
88
+ all_volumes = await lib.download_title(translation_index=0, chapter_delay=0.5)
89
+
90
+ await lib.export(chapters, fmt="epub", path="output.epub")
91
+ ```
92
+
93
+ This is the full current public API — everything above is implemented and works today.
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
+
103
+ ## Documentation
104
+
105
+ Full API reference and guides: **<https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/>**
106
+ (built with `mkdocs-material` + `mkdocstrings`, deployed on push to `main`). `CLAUDE.md`
107
+ documents the full architecture, public API contract, and the reasoning behind the SDK's
108
+ design decisions. [`CHANGELOG.md`](CHANGELOG.md) lists what changed in each release.
109
+
110
+ ## License
111
+
112
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,84 @@
1
+ # ranobelib-python-sdk
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/ranobelib-python-sdk)](https://pypi.org/project/ranobelib-python-sdk/)
4
+ [![License: MIT](https://img.shields.io/github/license/LLIJIYAPNIK/ranobelib-python-sdk)](LICENSE)
5
+ [![Python versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)](pyproject.toml)
6
+ [![CI](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml)
7
+ [![Coverage Status](https://coveralls.io/repos/github/LLIJIYAPNIK/ranobelib-python-sdk/badge.svg?branch=main)](https://coveralls.io/github/LLIJIYAPNIK/ranobelib-python-sdk?branch=main)
8
+ [![Docs](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml)
9
+
10
+ Async Python SDK for [ranobelib.me](https://ranobelib.me): fetch title metadata, download
11
+ chapters and volumes, and export to epub, fb2, txt, or pdf.
12
+
13
+ The SDK talks to the undocumented but open JSON API behind the site (`api.cdnlibs.org`,
14
+ part of the lib.social network) instead of scraping HTML.
15
+
16
+ > **Status:** all the calls in the quickstart below are implemented — metadata, table of
17
+ > contents, single/bulk chapter and volume downloads, whole-title bulk download, translation
18
+ > selection, and export to
19
+ > `"txt"`, `"fb2"`, `"epub"`, or `"pdf"` (cover + in-chapter illustrations embedded in
20
+ > epub/pdf). Raw API responses are cached to disk, and the underlying client bounds
21
+ > concurrency, paces requests, and retries 429/5xx with backoff. `fmt="pdf"` needs
22
+ > WeasyPrint's native dependencies (Pango/cairo/GTK3) installed on the system — `pip
23
+ > install`/`uv add` alone doesn't guarantee that on every platform; without them,
24
+ > `export(fmt="pdf")` raises the same "unknown format" error the other three formats don't.
25
+ > Authorization (private/18+/paid content) is out of scope — see `CLAUDE.md` for the full
26
+ > architecture and design decisions behind all of this.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ uv add ranobelib-python-sdk
32
+ ```
33
+
34
+ or
35
+
36
+ ```bash
37
+ pip install ranobelib-python-sdk
38
+ ```
39
+
40
+ Requires Python 3.11+.
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ from ranobelib import RanobeLib
46
+
47
+ async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel") as lib:
48
+ info = await lib.get_info()
49
+ toc = await lib.get_table_of_contents()
50
+
51
+ chapter = await lib.get_chapter(volume=6, number="51.6")
52
+ volume = await lib.get_volume(volume=6)
53
+
54
+ chapters = await lib.get_chapters([(6, "51.6"), (6, "52")])
55
+ volumes = await lib.get_volumes([1, 2, 3])
56
+
57
+ translations = await lib.get_translations(volume=6, number="51.6")
58
+ chapter = await lib.get_chapter(volume=6, number="51.6", branch_id=...)
59
+
60
+ all_volumes = await lib.download_title(translation_index=0, chapter_delay=0.5)
61
+
62
+ await lib.export(chapters, fmt="epub", path="output.epub")
63
+ ```
64
+
65
+ This is the full current public API — everything above is implemented and works today.
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
+
75
+ ## Documentation
76
+
77
+ Full API reference and guides: **<https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/>**
78
+ (built with `mkdocs-material` + `mkdocstrings`, deployed on push to `main`). `CLAUDE.md`
79
+ documents the full architecture, public API contract, and the reasoning behind the SDK's
80
+ design decisions. [`CHANGELOG.md`](CHANGELOG.md) lists what changed in each release.
81
+
82
+ ## License
83
+
84
+ MIT — see [LICENSE](LICENSE).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ranobelib-python-sdk"
3
- version = "0.2.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.2.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,478 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ranobelib-python-sdk
3
- Version: 0.2.0
4
- Summary: Async Python SDK for ranobelib.me: title metadata, chapter/volume downloads, and export to epub/fb2/txt/pdf
5
- Keywords: ranobelib,ranobe,novel,webnovel,epub,fb2,sdk
6
- Author: LLIJIYAPNIK
7
- Author-email: LLIJIYAPNIK <kas4wot@yandex.ru>
8
- License-Expression: MIT
9
- Classifier: Development Status :: 2 - Pre-Alpha
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
- Classifier: Typing :: Typed
18
- Requires-Dist: ebooklib>=0.20
19
- Requires-Dist: httpx>=0.27
20
- Requires-Dist: pydantic>=2.7
21
- Requires-Dist: weasyprint>=69.0
22
- Requires-Python: >=3.11
23
- Project-URL: Homepage, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk
24
- Project-URL: Repository, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk
25
- Project-URL: Issues, https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/issues
26
- Description-Content-Type: text/markdown
27
-
28
- # ranobelib-python-sdk
29
-
30
- [![PyPI](https://img.shields.io/pypi/v/ranobelib-python-sdk)](https://pypi.org/project/ranobelib-python-sdk/)
31
- [![License: MIT](https://img.shields.io/github/license/LLIJIYAPNIK/ranobelib-python-sdk)](LICENSE)
32
- [![Python versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)](pyproject.toml)
33
- [![CI](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml)
34
- [![Coverage Status](https://coveralls.io/repos/github/LLIJIYAPNIK/ranobelib-python-sdk/badge.svg?branch=main)](https://coveralls.io/github/LLIJIYAPNIK/ranobelib-python-sdk?branch=main)
35
- [![Docs](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml)
36
-
37
- Async Python SDK for [ranobelib.me](https://ranobelib.me): fetch title metadata, download
38
- chapters and volumes, and export to epub, fb2, txt, or pdf.
39
-
40
- The SDK talks to the undocumented but open JSON API behind the site (`api.cdnlibs.org`,
41
- part of the lib.social network) instead of scraping HTML.
42
-
43
- > **Status:** all the calls in the quickstart below are implemented — metadata, table of
44
- > contents, single/bulk chapter and volume downloads, whole-title bulk download, translation
45
- > selection, and export to
46
- > `"txt"`, `"fb2"`, `"epub"`, or `"pdf"` (cover + in-chapter illustrations embedded in
47
- > epub/pdf). Raw API responses are cached to disk, and the underlying client bounds
48
- > concurrency, paces requests, and retries 429/5xx with backoff. `fmt="pdf"` needs
49
- > WeasyPrint's native dependencies (Pango/cairo/GTK3) installed on the system — `pip
50
- > install`/`uv add` alone doesn't guarantee that on every platform; without them,
51
- > `export(fmt="pdf")` raises the same "unknown format" error the other three formats don't.
52
- > Authorization (private/18+/paid content) is out of scope — see `CLAUDE.md` for the full
53
- > architecture and design decisions behind all of this.
54
-
55
- ## Installation
56
-
57
- ```bash
58
- uv add ranobelib-python-sdk
59
- ```
60
-
61
- or
62
-
63
- ```bash
64
- pip install ranobelib-python-sdk
65
- ```
66
-
67
- Requires Python 3.11+.
68
-
69
- ## Quickstart
70
-
71
- ```python
72
- from ranobelib import RanobeLib
73
-
74
- async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel") as lib:
75
- info = await lib.get_info()
76
- toc = await lib.get_table_of_contents()
77
-
78
- chapter = await lib.get_chapter(volume=6, number="51.6")
79
- volume = await lib.get_volume(volume=6)
80
-
81
- chapters = await lib.get_chapters([(6, "51.6"), (6, "52")])
82
- volumes = await lib.get_volumes([1, 2, 3])
83
-
84
- translations = await lib.get_translations(volume=6, number="51.6")
85
- chapter = await lib.get_chapter(volume=6, number="51.6", branch_id=...)
86
-
87
- all_volumes = await lib.download_title(translation_index=0, chapter_delay=0.5)
88
-
89
- await lib.export(chapters, fmt="epub", path="output.epub")
90
- ```
91
-
92
- This is the full current public API — everything above is implemented and works today.
93
-
94
- ## Examples
95
-
96
- Every snippet below is a complete, self-contained script — save it as `example.py` and run
97
- `python example.py`. Each one really was run against the live site to produce the "Result"
98
- shown; only chapter content is truncated with `[:200]`/`[:300]` for readability, everything
99
- else is the real output verbatim.
100
-
101
- ### Title metadata
102
-
103
- ```python
104
- import asyncio
105
-
106
- from ranobelib import RanobeLib
107
-
108
-
109
- async def main() -> None:
110
- async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel") as lib:
111
- info = await lib.get_info()
112
- print(info.name)
113
- print(info.rus_name)
114
- print(info.status.label)
115
- print(info.chapter_count)
116
- print([genre.name for genre in info.genres[:5]])
117
-
118
-
119
- asyncio.run(main())
120
- ```
121
-
122
- **Result:**
123
-
124
- ```
125
- Haisukuru Di Di (Novel)
126
- Старшая школа D×D (Новелла)
127
- Завершён
128
- 308
129
- ['Боевик', 'Боевые искусства', 'Вампиры', 'Гарем', 'Драма']
130
- ```
131
-
132
- ### Table of contents
133
-
134
- ```python
135
- import asyncio
136
-
137
- from ranobelib import RanobeLib
138
-
139
-
140
- async def main() -> None:
141
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
142
- volumes = await lib.get_table_of_contents()
143
- for volume in volumes:
144
- print(f"Volume {volume.number}: {len(volume.chapters)} chapters")
145
- print(volumes[0].chapters[0].name)
146
-
147
-
148
- asyncio.run(main())
149
- ```
150
-
151
- **Result:**
152
-
153
- ```
154
- Volume 0: 1 chapters
155
- Volume 1: 46 chapters
156
- (НЕ ГОТОВО) Возможный гарем
157
- ```
158
-
159
- ### A single chapter
160
-
161
- ```python
162
- import asyncio
163
-
164
- from ranobelib import RanobeLib
165
-
166
-
167
- async def main() -> None:
168
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
169
- chapter = await lib.get_chapter(volume=1, number="1")
170
- print(chapter.name)
171
- print(chapter.content[:200])
172
-
173
-
174
- asyncio.run(main())
175
- ```
176
-
177
- **Result:**
178
-
179
- ```
180
- Глава 1
181
- <p data-paragraph-index="1">"Наконец, я наконец-то могу вернуться." - говорю я с широкой
182
- улыбкой на лице, хотя по моим щекам текут слезы. Прошло слишком много времени с тех пор,
183
- как я застрял в этом м
184
- ```
185
-
186
- ### Several chapters at once
187
-
188
- ```python
189
- import asyncio
190
-
191
- from ranobelib import RanobeLib
192
-
193
-
194
- async def main() -> None:
195
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
196
- chapters = await lib.get_chapters([(1, "1"), (1, "2")])
197
- for chapter in chapters:
198
- print(chapter.volume, chapter.number, len(chapter.content))
199
-
200
-
201
- asyncio.run(main())
202
- ```
203
-
204
- **Result:**
205
-
206
- ```
207
- 1 1 30722
208
- 1 2 22747
209
- ```
210
-
211
- ### A whole volume
212
-
213
- ```python
214
- import asyncio
215
-
216
- from ranobelib import RanobeLib
217
-
218
-
219
- async def main() -> None:
220
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
221
- volume = await lib.get_volume(0)
222
- print(volume.number, [chapter.number for chapter in volume.chapters])
223
-
224
-
225
- asyncio.run(main())
226
- ```
227
-
228
- **Result:**
229
-
230
- ```
231
- 0 ['1']
232
- ```
233
-
234
- ### Several volumes at once
235
-
236
- ```python
237
- import asyncio
238
-
239
- from ranobelib import RanobeLib
240
-
241
-
242
- async def main() -> None:
243
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
244
- volumes = await lib.get_volumes([0, 1])
245
- for volume in volumes:
246
- print(volume.number, len(volume.chapters))
247
-
248
-
249
- asyncio.run(main())
250
- ```
251
-
252
- **Result:**
253
-
254
- ```
255
- 0 1
256
- 1 46
257
- ```
258
-
259
- ### Selecting a translation
260
-
261
- `11407--solo-leveling` has two competing translations of its prologue. `get_chapter()`
262
- refuses to guess which one you want — it raises `MultipleTranslationsError` instead —
263
- unless `branch_id` is given explicitly, so you list the options with `get_translations()`
264
- first:
265
-
266
- ```python
267
- import asyncio
268
-
269
- from ranobelib import MultipleTranslationsError, RanobeLib
270
-
271
-
272
- async def main() -> None:
273
- async with RanobeLib("https://ranobelib.me/ru/book/11407--solo-leveling") as lib:
274
- branches = await lib.get_translations(volume=1, number="0")
275
- for branch in branches:
276
- print(branch.branch_id, [team.name for team in branch.teams])
277
-
278
- try:
279
- await lib.get_chapter(volume=1, number="0")
280
- except MultipleTranslationsError as exc:
281
- print("ambiguous, pick one:", [b.branch_id for b in exc.branches])
282
-
283
- chapter = await lib.get_chapter(volume=1, number="0", branch_id=branches[0].branch_id)
284
- print(chapter.content[:60])
285
-
286
-
287
- asyncio.run(main())
288
- ```
289
-
290
- **Result:**
291
-
292
- ```
293
- 2251 ['BerkuD13']
294
- 635 ['Неизвестный']
295
- ambiguous, pick one: [2251, 635]
296
- <p data-paragraph-index="1">Прокачка уровня в одиночку</p><p data-paragraph-index="2">0 . Пролог</p
297
- ```
298
-
299
- ### Downloading a whole title
300
-
301
- `download_title()` fetches every chapter of a title in one call. When a title has chapters
302
- with more than one translation, it raises `MultipleTitleTranslationsError` listing *all* of
303
- them up front (not just the first one hit) — pass `branch_id` or `translation_index` to
304
- resolve them:
305
-
306
- ```python
307
- import asyncio
308
-
309
- from ranobelib import MultipleTitleTranslationsError, RanobeLib
310
-
311
-
312
- async def main() -> None:
313
- async with RanobeLib("https://ranobelib.me/ru/book/40195--enbizaka-no-shitateya") as lib:
314
- volumes = await lib.download_title(chapter_delay=0.5)
315
- for volume in volumes:
316
- print(volume.number, [chapter.number for chapter in volume.chapters])
317
-
318
- slug = "113306--bungou-stray-dogs-gaiden-ayatsuji-yukito-vs-kyogoku-natsuhiko"
319
- async with RanobeLib(f"https://ranobelib.me/ru/book/{slug}") as lib:
320
- try:
321
- await lib.download_title()
322
- except MultipleTitleTranslationsError as exc:
323
- print(f"{len(exc.chapters)} ambiguous chapter(s):")
324
- for chapter in exc.chapters:
325
- print(" ", chapter.volume, chapter.number, [b.branch_id for b in chapter.branches])
326
-
327
- volumes = await lib.download_title(translation_index=0)
328
- print([chapter.number for chapter in volumes[0].chapters])
329
-
330
-
331
- asyncio.run(main())
332
- ```
333
-
334
- **Result:**
335
-
336
- ```
337
- 1 ['0', '1']
338
- 3 ambiguous chapter(s):
339
- 1 0 [12954, 12955]
340
- 1 1 [12955, 12954]
341
- 1 2 [12954, 12955]
342
- ['0', '1', '2', '3', '4', '5']
343
- ```
344
-
345
- Note chapter `1`'s branches are listed in the opposite order from chapters `0`/`2` —
346
- `translation_index=0` resolves by each chapter's own branch order, not a shared `branch_id`,
347
- which is why it picks a different team for chapter `1` than for `0`/`2` here. Use `branch_id`
348
- instead when you know the specific translation you want and it's shared across chapters.
349
-
350
- ### Disk caching
351
-
352
- Raw API responses are cached to disk by default, so a repeated call for the same data
353
- doesn't re-fetch it:
354
-
355
- ```python
356
- import asyncio
357
- import time
358
-
359
- from ranobelib import RanobeLib
360
-
361
-
362
- async def main() -> None:
363
- async with RanobeLib(
364
- "https://ranobelib.me/ru/book/91443--new-hero-in-dxd", cache_dir=".ranobelib_cache"
365
- ) as lib:
366
- start = time.perf_counter()
367
- await lib.get_table_of_contents()
368
- print(f"first call: {time.perf_counter() - start:.3f}s (hits the API)")
369
-
370
- start = time.perf_counter()
371
- await lib.get_table_of_contents()
372
- print(f"second call: {time.perf_counter() - start:.3f}s (served from disk cache)")
373
-
374
- await lib.get_table_of_contents(refresh=True) # Bypasses the cache explicitly.
375
-
376
-
377
- asyncio.run(main())
378
- ```
379
-
380
- **Result** (your exact timings will vary, but the gap between the two calls won't):
381
-
382
- ```
383
- first call: 0.318s (hits the API)
384
- second call: 0.001s (served from disk cache)
385
- ```
386
-
387
- ### Exporting to txt / fb2 / epub / pdf
388
-
389
- ```python
390
- import asyncio
391
-
392
- from ranobelib import RanobeLib
393
-
394
-
395
- async def main() -> None:
396
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
397
- chapters = await lib.get_chapters([(1, "1"), (1, "2")])
398
- for fmt in ("txt", "fb2", "epub", "pdf"):
399
- path = f"output.{fmt}"
400
- result = await lib.export(chapters, fmt=fmt, path=path)
401
- print(fmt, "->", result)
402
-
403
-
404
- asyncio.run(main())
405
- ```
406
-
407
- **Result** (file sizes from the real run — `pdf` needs
408
- [WeasyPrint's native dependencies](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation)
409
- installed; without them this raises `ValueError: Unknown export format 'pdf'. Available:
410
- epub, fb2, txt` instead of the line shown here):
411
-
412
- ```
413
- txt -> output.txt (84,044 bytes)
414
- fb2 -> output.fb2 (87,691 bytes)
415
- epub -> output.epub (1,442,724 bytes, cover + illustrations embedded)
416
- pdf -> output.pdf (cover + illustrations embedded, needs WeasyPrint)
417
- ```
418
-
419
- `output.txt`'s first few lines, for a sense of the format:
420
-
421
- ```
422
- New Hero in DxD
423
-
424
-
425
- Volume 1, Chapter 1: Глава 1
426
-
427
- "Наконец, я наконец-то могу вернуться." - говорю я с широкой улыбкой на лице, хотя по моим
428
- щекам текут слезы. Прошло слишком много времени с тех пор, как я застрял в этом месте, я
429
- больше не могу с этим справляться. Поначалу это было круто, но через нек...
430
- ```
431
-
432
- ### Error handling
433
-
434
- ```python
435
- import asyncio
436
-
437
- from ranobelib import ChapterNotFoundError, RanobeLib, TitleNotFoundError
438
-
439
-
440
- async def main() -> None:
441
- async with RanobeLib("https://ranobelib.me/ru/book/1--this-title-does-not-exist-zzz") as lib:
442
- try:
443
- await lib.get_info()
444
- except TitleNotFoundError as exc:
445
- print(exc)
446
-
447
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
448
- try:
449
- await lib.get_chapter(volume=999, number="9999")
450
- except ChapterNotFoundError as exc:
451
- print(exc)
452
-
453
-
454
- asyncio.run(main())
455
- ```
456
-
457
- **Result:**
458
-
459
- ```
460
- Title not found: '1--this-title-does-not-exist-zzz'
461
- Chapter not found: '91443--new-hero-in-dxd' volume='999' number='9999'
462
- ```
463
-
464
- `AuthRequiredError` (403, paid/early-access content) and `RateLimitError` (429 after
465
- retries are exhausted) follow the same pattern — see the
466
- [API reference](https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/reference/) for every
467
- exception's attributes.
468
-
469
- ## Documentation
470
-
471
- Full API reference and guides: **<https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/>**
472
- (built with `mkdocs-material` + `mkdocstrings`, deployed on push to `main`). `CLAUDE.md`
473
- documents the full architecture, public API contract, and the reasoning behind the SDK's
474
- design decisions.
475
-
476
- ## License
477
-
478
- MIT — see [LICENSE](LICENSE).
@@ -1,451 +0,0 @@
1
- # ranobelib-python-sdk
2
-
3
- [![PyPI](https://img.shields.io/pypi/v/ranobelib-python-sdk)](https://pypi.org/project/ranobelib-python-sdk/)
4
- [![License: MIT](https://img.shields.io/github/license/LLIJIYAPNIK/ranobelib-python-sdk)](LICENSE)
5
- [![Python versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)](pyproject.toml)
6
- [![CI](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/ci.yml)
7
- [![Coverage Status](https://coveralls.io/repos/github/LLIJIYAPNIK/ranobelib-python-sdk/badge.svg?branch=main)](https://coveralls.io/github/LLIJIYAPNIK/ranobelib-python-sdk?branch=main)
8
- [![Docs](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml/badge.svg)](https://github.com/LLIJIYAPNIK/ranobelib-python-sdk/actions/workflows/docs.yml)
9
-
10
- Async Python SDK for [ranobelib.me](https://ranobelib.me): fetch title metadata, download
11
- chapters and volumes, and export to epub, fb2, txt, or pdf.
12
-
13
- The SDK talks to the undocumented but open JSON API behind the site (`api.cdnlibs.org`,
14
- part of the lib.social network) instead of scraping HTML.
15
-
16
- > **Status:** all the calls in the quickstart below are implemented — metadata, table of
17
- > contents, single/bulk chapter and volume downloads, whole-title bulk download, translation
18
- > selection, and export to
19
- > `"txt"`, `"fb2"`, `"epub"`, or `"pdf"` (cover + in-chapter illustrations embedded in
20
- > epub/pdf). Raw API responses are cached to disk, and the underlying client bounds
21
- > concurrency, paces requests, and retries 429/5xx with backoff. `fmt="pdf"` needs
22
- > WeasyPrint's native dependencies (Pango/cairo/GTK3) installed on the system — `pip
23
- > install`/`uv add` alone doesn't guarantee that on every platform; without them,
24
- > `export(fmt="pdf")` raises the same "unknown format" error the other three formats don't.
25
- > Authorization (private/18+/paid content) is out of scope — see `CLAUDE.md` for the full
26
- > architecture and design decisions behind all of this.
27
-
28
- ## Installation
29
-
30
- ```bash
31
- uv add ranobelib-python-sdk
32
- ```
33
-
34
- or
35
-
36
- ```bash
37
- pip install ranobelib-python-sdk
38
- ```
39
-
40
- Requires Python 3.11+.
41
-
42
- ## Quickstart
43
-
44
- ```python
45
- from ranobelib import RanobeLib
46
-
47
- async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel") as lib:
48
- info = await lib.get_info()
49
- toc = await lib.get_table_of_contents()
50
-
51
- chapter = await lib.get_chapter(volume=6, number="51.6")
52
- volume = await lib.get_volume(volume=6)
53
-
54
- chapters = await lib.get_chapters([(6, "51.6"), (6, "52")])
55
- volumes = await lib.get_volumes([1, 2, 3])
56
-
57
- translations = await lib.get_translations(volume=6, number="51.6")
58
- chapter = await lib.get_chapter(volume=6, number="51.6", branch_id=...)
59
-
60
- all_volumes = await lib.download_title(translation_index=0, chapter_delay=0.5)
61
-
62
- await lib.export(chapters, fmt="epub", path="output.epub")
63
- ```
64
-
65
- This is the full current public API — everything above is implemented and works today.
66
-
67
- ## Examples
68
-
69
- Every snippet below is a complete, self-contained script — save it as `example.py` and run
70
- `python example.py`. Each one really was run against the live site to produce the "Result"
71
- shown; only chapter content is truncated with `[:200]`/`[:300]` for readability, everything
72
- else is the real output verbatim.
73
-
74
- ### Title metadata
75
-
76
- ```python
77
- import asyncio
78
-
79
- from ranobelib import RanobeLib
80
-
81
-
82
- async def main() -> None:
83
- async with RanobeLib("https://ranobelib.me/ru/book/6712--high-school-dxd-novel") as lib:
84
- info = await lib.get_info()
85
- print(info.name)
86
- print(info.rus_name)
87
- print(info.status.label)
88
- print(info.chapter_count)
89
- print([genre.name for genre in info.genres[:5]])
90
-
91
-
92
- asyncio.run(main())
93
- ```
94
-
95
- **Result:**
96
-
97
- ```
98
- Haisukuru Di Di (Novel)
99
- Старшая школа D×D (Новелла)
100
- Завершён
101
- 308
102
- ['Боевик', 'Боевые искусства', 'Вампиры', 'Гарем', 'Драма']
103
- ```
104
-
105
- ### Table of contents
106
-
107
- ```python
108
- import asyncio
109
-
110
- from ranobelib import RanobeLib
111
-
112
-
113
- async def main() -> None:
114
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
115
- volumes = await lib.get_table_of_contents()
116
- for volume in volumes:
117
- print(f"Volume {volume.number}: {len(volume.chapters)} chapters")
118
- print(volumes[0].chapters[0].name)
119
-
120
-
121
- asyncio.run(main())
122
- ```
123
-
124
- **Result:**
125
-
126
- ```
127
- Volume 0: 1 chapters
128
- Volume 1: 46 chapters
129
- (НЕ ГОТОВО) Возможный гарем
130
- ```
131
-
132
- ### A single chapter
133
-
134
- ```python
135
- import asyncio
136
-
137
- from ranobelib import RanobeLib
138
-
139
-
140
- async def main() -> None:
141
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
142
- chapter = await lib.get_chapter(volume=1, number="1")
143
- print(chapter.name)
144
- print(chapter.content[:200])
145
-
146
-
147
- asyncio.run(main())
148
- ```
149
-
150
- **Result:**
151
-
152
- ```
153
- Глава 1
154
- <p data-paragraph-index="1">"Наконец, я наконец-то могу вернуться." - говорю я с широкой
155
- улыбкой на лице, хотя по моим щекам текут слезы. Прошло слишком много времени с тех пор,
156
- как я застрял в этом м
157
- ```
158
-
159
- ### Several chapters at once
160
-
161
- ```python
162
- import asyncio
163
-
164
- from ranobelib import RanobeLib
165
-
166
-
167
- async def main() -> None:
168
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
169
- chapters = await lib.get_chapters([(1, "1"), (1, "2")])
170
- for chapter in chapters:
171
- print(chapter.volume, chapter.number, len(chapter.content))
172
-
173
-
174
- asyncio.run(main())
175
- ```
176
-
177
- **Result:**
178
-
179
- ```
180
- 1 1 30722
181
- 1 2 22747
182
- ```
183
-
184
- ### A whole volume
185
-
186
- ```python
187
- import asyncio
188
-
189
- from ranobelib import RanobeLib
190
-
191
-
192
- async def main() -> None:
193
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
194
- volume = await lib.get_volume(0)
195
- print(volume.number, [chapter.number for chapter in volume.chapters])
196
-
197
-
198
- asyncio.run(main())
199
- ```
200
-
201
- **Result:**
202
-
203
- ```
204
- 0 ['1']
205
- ```
206
-
207
- ### Several volumes at once
208
-
209
- ```python
210
- import asyncio
211
-
212
- from ranobelib import RanobeLib
213
-
214
-
215
- async def main() -> None:
216
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
217
- volumes = await lib.get_volumes([0, 1])
218
- for volume in volumes:
219
- print(volume.number, len(volume.chapters))
220
-
221
-
222
- asyncio.run(main())
223
- ```
224
-
225
- **Result:**
226
-
227
- ```
228
- 0 1
229
- 1 46
230
- ```
231
-
232
- ### Selecting a translation
233
-
234
- `11407--solo-leveling` has two competing translations of its prologue. `get_chapter()`
235
- refuses to guess which one you want — it raises `MultipleTranslationsError` instead —
236
- unless `branch_id` is given explicitly, so you list the options with `get_translations()`
237
- first:
238
-
239
- ```python
240
- import asyncio
241
-
242
- from ranobelib import MultipleTranslationsError, RanobeLib
243
-
244
-
245
- async def main() -> None:
246
- async with RanobeLib("https://ranobelib.me/ru/book/11407--solo-leveling") as lib:
247
- branches = await lib.get_translations(volume=1, number="0")
248
- for branch in branches:
249
- print(branch.branch_id, [team.name for team in branch.teams])
250
-
251
- try:
252
- await lib.get_chapter(volume=1, number="0")
253
- except MultipleTranslationsError as exc:
254
- print("ambiguous, pick one:", [b.branch_id for b in exc.branches])
255
-
256
- chapter = await lib.get_chapter(volume=1, number="0", branch_id=branches[0].branch_id)
257
- print(chapter.content[:60])
258
-
259
-
260
- asyncio.run(main())
261
- ```
262
-
263
- **Result:**
264
-
265
- ```
266
- 2251 ['BerkuD13']
267
- 635 ['Неизвестный']
268
- ambiguous, pick one: [2251, 635]
269
- <p data-paragraph-index="1">Прокачка уровня в одиночку</p><p data-paragraph-index="2">0 . Пролог</p
270
- ```
271
-
272
- ### Downloading a whole title
273
-
274
- `download_title()` fetches every chapter of a title in one call. When a title has chapters
275
- with more than one translation, it raises `MultipleTitleTranslationsError` listing *all* of
276
- them up front (not just the first one hit) — pass `branch_id` or `translation_index` to
277
- resolve them:
278
-
279
- ```python
280
- import asyncio
281
-
282
- from ranobelib import MultipleTitleTranslationsError, RanobeLib
283
-
284
-
285
- async def main() -> None:
286
- async with RanobeLib("https://ranobelib.me/ru/book/40195--enbizaka-no-shitateya") as lib:
287
- volumes = await lib.download_title(chapter_delay=0.5)
288
- for volume in volumes:
289
- print(volume.number, [chapter.number for chapter in volume.chapters])
290
-
291
- slug = "113306--bungou-stray-dogs-gaiden-ayatsuji-yukito-vs-kyogoku-natsuhiko"
292
- async with RanobeLib(f"https://ranobelib.me/ru/book/{slug}") as lib:
293
- try:
294
- await lib.download_title()
295
- except MultipleTitleTranslationsError as exc:
296
- print(f"{len(exc.chapters)} ambiguous chapter(s):")
297
- for chapter in exc.chapters:
298
- print(" ", chapter.volume, chapter.number, [b.branch_id for b in chapter.branches])
299
-
300
- volumes = await lib.download_title(translation_index=0)
301
- print([chapter.number for chapter in volumes[0].chapters])
302
-
303
-
304
- asyncio.run(main())
305
- ```
306
-
307
- **Result:**
308
-
309
- ```
310
- 1 ['0', '1']
311
- 3 ambiguous chapter(s):
312
- 1 0 [12954, 12955]
313
- 1 1 [12955, 12954]
314
- 1 2 [12954, 12955]
315
- ['0', '1', '2', '3', '4', '5']
316
- ```
317
-
318
- Note chapter `1`'s branches are listed in the opposite order from chapters `0`/`2` —
319
- `translation_index=0` resolves by each chapter's own branch order, not a shared `branch_id`,
320
- which is why it picks a different team for chapter `1` than for `0`/`2` here. Use `branch_id`
321
- instead when you know the specific translation you want and it's shared across chapters.
322
-
323
- ### Disk caching
324
-
325
- Raw API responses are cached to disk by default, so a repeated call for the same data
326
- doesn't re-fetch it:
327
-
328
- ```python
329
- import asyncio
330
- import time
331
-
332
- from ranobelib import RanobeLib
333
-
334
-
335
- async def main() -> None:
336
- async with RanobeLib(
337
- "https://ranobelib.me/ru/book/91443--new-hero-in-dxd", cache_dir=".ranobelib_cache"
338
- ) as lib:
339
- start = time.perf_counter()
340
- await lib.get_table_of_contents()
341
- print(f"first call: {time.perf_counter() - start:.3f}s (hits the API)")
342
-
343
- start = time.perf_counter()
344
- await lib.get_table_of_contents()
345
- print(f"second call: {time.perf_counter() - start:.3f}s (served from disk cache)")
346
-
347
- await lib.get_table_of_contents(refresh=True) # Bypasses the cache explicitly.
348
-
349
-
350
- asyncio.run(main())
351
- ```
352
-
353
- **Result** (your exact timings will vary, but the gap between the two calls won't):
354
-
355
- ```
356
- first call: 0.318s (hits the API)
357
- second call: 0.001s (served from disk cache)
358
- ```
359
-
360
- ### Exporting to txt / fb2 / epub / pdf
361
-
362
- ```python
363
- import asyncio
364
-
365
- from ranobelib import RanobeLib
366
-
367
-
368
- async def main() -> None:
369
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
370
- chapters = await lib.get_chapters([(1, "1"), (1, "2")])
371
- for fmt in ("txt", "fb2", "epub", "pdf"):
372
- path = f"output.{fmt}"
373
- result = await lib.export(chapters, fmt=fmt, path=path)
374
- print(fmt, "->", result)
375
-
376
-
377
- asyncio.run(main())
378
- ```
379
-
380
- **Result** (file sizes from the real run — `pdf` needs
381
- [WeasyPrint's native dependencies](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation)
382
- installed; without them this raises `ValueError: Unknown export format 'pdf'. Available:
383
- epub, fb2, txt` instead of the line shown here):
384
-
385
- ```
386
- txt -> output.txt (84,044 bytes)
387
- fb2 -> output.fb2 (87,691 bytes)
388
- epub -> output.epub (1,442,724 bytes, cover + illustrations embedded)
389
- pdf -> output.pdf (cover + illustrations embedded, needs WeasyPrint)
390
- ```
391
-
392
- `output.txt`'s first few lines, for a sense of the format:
393
-
394
- ```
395
- New Hero in DxD
396
-
397
-
398
- Volume 1, Chapter 1: Глава 1
399
-
400
- "Наконец, я наконец-то могу вернуться." - говорю я с широкой улыбкой на лице, хотя по моим
401
- щекам текут слезы. Прошло слишком много времени с тех пор, как я застрял в этом месте, я
402
- больше не могу с этим справляться. Поначалу это было круто, но через нек...
403
- ```
404
-
405
- ### Error handling
406
-
407
- ```python
408
- import asyncio
409
-
410
- from ranobelib import ChapterNotFoundError, RanobeLib, TitleNotFoundError
411
-
412
-
413
- async def main() -> None:
414
- async with RanobeLib("https://ranobelib.me/ru/book/1--this-title-does-not-exist-zzz") as lib:
415
- try:
416
- await lib.get_info()
417
- except TitleNotFoundError as exc:
418
- print(exc)
419
-
420
- async with RanobeLib("https://ranobelib.me/ru/book/91443--new-hero-in-dxd") as lib:
421
- try:
422
- await lib.get_chapter(volume=999, number="9999")
423
- except ChapterNotFoundError as exc:
424
- print(exc)
425
-
426
-
427
- asyncio.run(main())
428
- ```
429
-
430
- **Result:**
431
-
432
- ```
433
- Title not found: '1--this-title-does-not-exist-zzz'
434
- Chapter not found: '91443--new-hero-in-dxd' volume='999' number='9999'
435
- ```
436
-
437
- `AuthRequiredError` (403, paid/early-access content) and `RateLimitError` (429 after
438
- retries are exhausted) follow the same pattern — see the
439
- [API reference](https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/reference/) for every
440
- exception's attributes.
441
-
442
- ## Documentation
443
-
444
- Full API reference and guides: **<https://LLIJIYAPNIK.github.io/ranobelib-python-sdk/>**
445
- (built with `mkdocs-material` + `mkdocstrings`, deployed on push to `main`). `CLAUDE.md`
446
- documents the full architecture, public API contract, and the reasoning behind the SDK's
447
- design decisions.
448
-
449
- ## License
450
-
451
- MIT — see [LICENSE](LICENSE).