lanraragi_api 0.9.70.1__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.
- lanraragi_api-0.9.70.1/PKG-INFO +78 -0
- lanraragi_api-0.9.70.1/README.md +66 -0
- lanraragi_api-0.9.70.1/pyproject.toml +13 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/__init__.py +1 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/__init__.py +28 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/archive.py +442 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/base.py +313 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/category.py +145 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/database.py +108 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/minion.py +89 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/misc.py +219 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/search.py +157 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/shinobu.py +35 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/base/tankoubon.py +211 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/enhanced/__init__.py +0 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/enhanced/script.py +74 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/enhanced/server_side.py +47 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/lanraragi.py +105 -0
- lanraragi_api-0.9.70.1/src/lanraragi_api/py.typed +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: lanraragi_api
|
|
3
|
+
Version: 0.9.70.1
|
|
4
|
+
Summary: A Python library for LANraragi API.
|
|
5
|
+
Author: Gustav
|
|
6
|
+
Author-email: Gustav <gustaavv.git@yahoo.com>
|
|
7
|
+
Requires-Dist: pydantic==2.13.0
|
|
8
|
+
Requires-Dist: requests==2.33.1
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Project-URL: repository, https://github.com/gustaavv/lanraragi-api
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# lanraragi-api
|
|
14
|
+
|
|
15
|
+
A Python library for [LANraragi](https://github.com/Difegue/LANraragi) API.
|
|
16
|
+
|
|
17
|
+
> Many thanks to the author of this wonderful manga server.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
Install this package:
|
|
24
|
+
|
|
25
|
+
```shell
|
|
26
|
+
pip install lanraragi_api
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Get metadata of a random archive:
|
|
30
|
+
|
|
31
|
+
> See [demo.py](demo.py)
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from lanraragi_api import LANraragiAPI
|
|
35
|
+
from lanraragi_api.base.archive import ArchiveMetadata
|
|
36
|
+
|
|
37
|
+
apikey = "your-key"
|
|
38
|
+
server = "http://127.0.0.1:3000"
|
|
39
|
+
api = LANraragiAPI(server, key=apikey)
|
|
40
|
+
|
|
41
|
+
archives: list[ArchiveMetadata] = api.search.get_random_archives()
|
|
42
|
+
print(archives[0])
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## How to use?
|
|
48
|
+
|
|
49
|
+
All the APIs in the `lanraragi_api.base` package are
|
|
50
|
+
from [the official LANraragi document](https://sugoi.gitbook.io/lanraragi/api-documentation/getting-started), which you will be using in most times.
|
|
51
|
+
|
|
52
|
+
Functions in the `lanraragi_api.enhanced` package are built on the base APIs, offering useful functionalities.
|
|
53
|
+
|
|
54
|
+
- `server_side.py` contains server-side functions. The code is the same to that of LANraragi, only translated from Perl to Python.
|
|
55
|
+
- `script.py` contains functions for operation and management.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Release versions
|
|
62
|
+
|
|
63
|
+
Every release of lanraragi-api is made only for the corresponding release of LANraragi. So you should choose the correct lanraragi-api version based on the server version.
|
|
64
|
+
|
|
65
|
+
| LANraragi | lanraragi-api |
|
|
66
|
+
| ---------- | ---------------------------------------- |
|
|
67
|
+
| `v.0.9.0` | `0.9.0.0`, `0.9.0.1`, ... , `0.9.0.x` |
|
|
68
|
+
| `v.0.9.40` | `0.9.40.0`, `0.9.40.1`, ... , `0.9.40.y` |
|
|
69
|
+
|
|
70
|
+
In order to make it simple, the first three version numbers are always the same, while the last version number of lanraragi-api serves as patches (just choose the latest one).
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
Python version: 3.10
|
|
77
|
+
|
|
78
|
+
Code formatter: default setting of [VS Code's Black Formatter extension](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# lanraragi-api
|
|
2
|
+
|
|
3
|
+
A Python library for [LANraragi](https://github.com/Difegue/LANraragi) API.
|
|
4
|
+
|
|
5
|
+
> Many thanks to the author of this wonderful manga server.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
Install this package:
|
|
12
|
+
|
|
13
|
+
```shell
|
|
14
|
+
pip install lanraragi_api
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Get metadata of a random archive:
|
|
18
|
+
|
|
19
|
+
> See [demo.py](demo.py)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from lanraragi_api import LANraragiAPI
|
|
23
|
+
from lanraragi_api.base.archive import ArchiveMetadata
|
|
24
|
+
|
|
25
|
+
apikey = "your-key"
|
|
26
|
+
server = "http://127.0.0.1:3000"
|
|
27
|
+
api = LANraragiAPI(server, key=apikey)
|
|
28
|
+
|
|
29
|
+
archives: list[ArchiveMetadata] = api.search.get_random_archives()
|
|
30
|
+
print(archives[0])
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## How to use?
|
|
36
|
+
|
|
37
|
+
All the APIs in the `lanraragi_api.base` package are
|
|
38
|
+
from [the official LANraragi document](https://sugoi.gitbook.io/lanraragi/api-documentation/getting-started), which you will be using in most times.
|
|
39
|
+
|
|
40
|
+
Functions in the `lanraragi_api.enhanced` package are built on the base APIs, offering useful functionalities.
|
|
41
|
+
|
|
42
|
+
- `server_side.py` contains server-side functions. The code is the same to that of LANraragi, only translated from Perl to Python.
|
|
43
|
+
- `script.py` contains functions for operation and management.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Release versions
|
|
50
|
+
|
|
51
|
+
Every release of lanraragi-api is made only for the corresponding release of LANraragi. So you should choose the correct lanraragi-api version based on the server version.
|
|
52
|
+
|
|
53
|
+
| LANraragi | lanraragi-api |
|
|
54
|
+
| ---------- | ---------------------------------------- |
|
|
55
|
+
| `v.0.9.0` | `0.9.0.0`, `0.9.0.1`, ... , `0.9.0.x` |
|
|
56
|
+
| `v.0.9.40` | `0.9.40.0`, `0.9.40.1`, ... , `0.9.40.y` |
|
|
57
|
+
|
|
58
|
+
In order to make it simple, the first three version numbers are always the same, while the last version number of lanraragi-api serves as patches (just choose the latest one).
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
Python version: 3.10
|
|
65
|
+
|
|
66
|
+
Code formatter: default setting of [VS Code's Black Formatter extension](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lanraragi_api"
|
|
3
|
+
version = "0.9.70.1"
|
|
4
|
+
description = "A Python library for LANraragi API."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Gustav", email = "gustaavv.git@yahoo.com" }]
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = ["pydantic==2.13.0", "requests==2.33.1"]
|
|
9
|
+
urls = { repository = "https://github.com/gustaavv/lanraragi-api" }
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["uv_build>=0.11.7,<0.12.0"]
|
|
13
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from lanraragi_api.lanraragi import LANraragiAPI
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from lanraragi_api.base.archive import ArchiveAPI, ArchiveMetadata
|
|
2
|
+
from lanraragi_api.base.base import (
|
|
3
|
+
APIError,
|
|
4
|
+
APIHttpError,
|
|
5
|
+
APIOperationError,
|
|
6
|
+
APIRequestError,
|
|
7
|
+
APIResponseDecodeError,
|
|
8
|
+
Auth,
|
|
9
|
+
MinionJobResponse,
|
|
10
|
+
OperationResponse,
|
|
11
|
+
)
|
|
12
|
+
from lanraragi_api.base.category import CategoryAPI, CategoryMetadata
|
|
13
|
+
from lanraragi_api.base.database import (
|
|
14
|
+
BackupArchiveMetadata,
|
|
15
|
+
BackupCategoryMetadata,
|
|
16
|
+
DatabaseAPI,
|
|
17
|
+
DatabaseBackup,
|
|
18
|
+
)
|
|
19
|
+
from lanraragi_api.base.minion import MinionAPI
|
|
20
|
+
from lanraragi_api.base.misc import MiscAPI
|
|
21
|
+
from lanraragi_api.base.search import SearchAPI
|
|
22
|
+
from lanraragi_api.base.shinobu import ShinobuAPI
|
|
23
|
+
from lanraragi_api.base.tankoubon import (
|
|
24
|
+
TankoubonAPI,
|
|
25
|
+
TankoubonDetailResponse,
|
|
26
|
+
TankoubonListResponse,
|
|
27
|
+
TankoubonMetadata,
|
|
28
|
+
)
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
from os.path import isfile
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from requests import Response
|
|
6
|
+
|
|
7
|
+
from lanraragi_api.base.base import (
|
|
8
|
+
APIResponseDecodeError,
|
|
9
|
+
BaseAPICall,
|
|
10
|
+
MinionJobResponse,
|
|
11
|
+
OperationResponse,
|
|
12
|
+
)
|
|
13
|
+
from lanraragi_api.base.category import CategoryMetadata
|
|
14
|
+
|
|
15
|
+
ARCHIVE_TAG_VALUES_SET = "ONLY_VALUES"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ArchiveMetadata(BaseModel):
|
|
19
|
+
arcid: str = Field(...)
|
|
20
|
+
extension: str = Field(...)
|
|
21
|
+
filename: str = Field(...)
|
|
22
|
+
isnew: bool | str | None = Field(default=None)
|
|
23
|
+
lastreadtime: int = Field(...)
|
|
24
|
+
pagecount: int = Field(...)
|
|
25
|
+
progress: int = Field(...)
|
|
26
|
+
size: int = Field(...)
|
|
27
|
+
summary: str | None = Field(default=None)
|
|
28
|
+
toc: list[dict] | None = Field(default=None)
|
|
29
|
+
|
|
30
|
+
# k1:v1, k2:v21, k2:v22, v3, v4
|
|
31
|
+
# allow duplicate keys, only values
|
|
32
|
+
tags: str = Field(...)
|
|
33
|
+
title: str = Field(...)
|
|
34
|
+
|
|
35
|
+
def __tags_to_dict(self) -> dict[str, list[str]]:
|
|
36
|
+
tags = self.tags.split(",")
|
|
37
|
+
ans = {}
|
|
38
|
+
for t in tags:
|
|
39
|
+
if t == "":
|
|
40
|
+
continue
|
|
41
|
+
t = t.strip()
|
|
42
|
+
if ":" in t:
|
|
43
|
+
kv = t.split(":")
|
|
44
|
+
k = kv[0]
|
|
45
|
+
v = kv[1]
|
|
46
|
+
if k not in ans:
|
|
47
|
+
ans[k] = []
|
|
48
|
+
ans[k].append(v)
|
|
49
|
+
else:
|
|
50
|
+
k = ARCHIVE_TAG_VALUES_SET
|
|
51
|
+
if k not in ans:
|
|
52
|
+
ans[k] = []
|
|
53
|
+
ans[k].append(t)
|
|
54
|
+
return ans
|
|
55
|
+
|
|
56
|
+
def __dict_to_tags(self, json: dict[str, list[str]]):
|
|
57
|
+
"""
|
|
58
|
+
The function will modify the object
|
|
59
|
+
"""
|
|
60
|
+
tags = ""
|
|
61
|
+
modified: bool = False
|
|
62
|
+
for k in json:
|
|
63
|
+
for v in json[k]:
|
|
64
|
+
modified = True
|
|
65
|
+
if k == ARCHIVE_TAG_VALUES_SET:
|
|
66
|
+
tags += f"{v},"
|
|
67
|
+
else:
|
|
68
|
+
tags += f"{k}:{v},"
|
|
69
|
+
if modified:
|
|
70
|
+
tags = tags[:-1]
|
|
71
|
+
self.tags = tags
|
|
72
|
+
|
|
73
|
+
def get_artists(self) -> list[str]:
|
|
74
|
+
return self.__tags_to_dict()["artist"]
|
|
75
|
+
|
|
76
|
+
def set_artists(self, artists: list[str]):
|
|
77
|
+
json = self.__tags_to_dict()
|
|
78
|
+
json["artist"] = artists
|
|
79
|
+
self.__dict_to_tags(json)
|
|
80
|
+
|
|
81
|
+
def remove_artists(self):
|
|
82
|
+
json = self.__tags_to_dict()
|
|
83
|
+
json["artist"] = []
|
|
84
|
+
self.__dict_to_tags(json)
|
|
85
|
+
|
|
86
|
+
def has_artists(self) -> bool:
|
|
87
|
+
return "artist" in self.tags
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ArchiveAPI(BaseAPICall):
|
|
91
|
+
"""
|
|
92
|
+
Everything dealing with Archives.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def get_all_archives(self) -> list[ArchiveMetadata]:
|
|
96
|
+
"""
|
|
97
|
+
Get the Archive Index in JSON form. You can use the IDs of this JSON
|
|
98
|
+
with the other endpoints.
|
|
99
|
+
:return: list of archives
|
|
100
|
+
"""
|
|
101
|
+
return self.request_model_list("GET", "/api/archives", ArchiveMetadata)
|
|
102
|
+
|
|
103
|
+
def get_archive(self, id: str) -> Optional[ArchiveMetadata]:
|
|
104
|
+
"""
|
|
105
|
+
Get Metadata (title, tags) for a given Archive using deprecated endpoint.
|
|
106
|
+
:param id: ID of the Archive to process.
|
|
107
|
+
:return: archive
|
|
108
|
+
"""
|
|
109
|
+
path = f"/api/archives/{id}"
|
|
110
|
+
resp = self.request("GET", path, expected_statuses={200, 400})
|
|
111
|
+
if resp.status_code == 400:
|
|
112
|
+
return None
|
|
113
|
+
payload = self.parse_json_response(resp, path)
|
|
114
|
+
return self.parse_model(ArchiveMetadata, payload, path)
|
|
115
|
+
|
|
116
|
+
def get_untagged_archives(self) -> list[str]:
|
|
117
|
+
"""
|
|
118
|
+
Get archives that don't have any tags recorded. This follows the same
|
|
119
|
+
rules as the Batch Tagging filter and will include archives that have
|
|
120
|
+
parody:, date_added:, series: or artist: tags.
|
|
121
|
+
:return: list of archive IDs
|
|
122
|
+
"""
|
|
123
|
+
path = "/api/archives/untagged"
|
|
124
|
+
payload = self.request_json("GET", path)
|
|
125
|
+
if not isinstance(payload, list):
|
|
126
|
+
raise APIResponseDecodeError(self._to_url(path), "response is not a list")
|
|
127
|
+
return payload
|
|
128
|
+
|
|
129
|
+
def get_archive_metadata(self, id: str) -> Optional[ArchiveMetadata]:
|
|
130
|
+
"""
|
|
131
|
+
Get Metadata (title, tags) for a given Archive.
|
|
132
|
+
:param id: ID of the Archive to process.
|
|
133
|
+
:return: archive
|
|
134
|
+
"""
|
|
135
|
+
path = f"/api/archives/{id}/metadata"
|
|
136
|
+
resp = self.request("GET", path, expected_statuses={200, 400})
|
|
137
|
+
if resp.status_code == 400:
|
|
138
|
+
return None
|
|
139
|
+
payload = self.parse_json_response(resp, path)
|
|
140
|
+
return self.parse_model(ArchiveMetadata, payload, path)
|
|
141
|
+
|
|
142
|
+
def get_archive_categories(self, id: str) -> list[CategoryMetadata]:
|
|
143
|
+
"""
|
|
144
|
+
Get all the Categories which currently refer to this Archive ID.
|
|
145
|
+
:param id: ID of the Archive to process.
|
|
146
|
+
:return: list of category metadata
|
|
147
|
+
"""
|
|
148
|
+
path = f"/api/archives/{id}/categories"
|
|
149
|
+
payload = self.request_json("GET", path)
|
|
150
|
+
clist = payload.get("categories")
|
|
151
|
+
if not isinstance(clist, list):
|
|
152
|
+
raise APIResponseDecodeError(self._to_url(path), "missing categories list")
|
|
153
|
+
return [self.parse_model(CategoryMetadata, c, path) for c in clist]
|
|
154
|
+
|
|
155
|
+
def get_archive_tankoubons(self, id: str) -> list[str]:
|
|
156
|
+
"""
|
|
157
|
+
Get all the Tankoubons which currently refer to this Archive ID.
|
|
158
|
+
|
|
159
|
+
Tankoubon: 単行本
|
|
160
|
+
:param id: ID of the Archive to process.
|
|
161
|
+
:return: list of tankoubon ids
|
|
162
|
+
"""
|
|
163
|
+
path = f"/api/archives/{id}/tankoubons"
|
|
164
|
+
payload = self.request_json("GET", path)
|
|
165
|
+
tankoubons = payload.get("tankoubons")
|
|
166
|
+
if not isinstance(tankoubons, list):
|
|
167
|
+
raise APIResponseDecodeError(self._to_url(path), "missing tankoubons list")
|
|
168
|
+
return tankoubons
|
|
169
|
+
|
|
170
|
+
def get_archive_thumbnail(
|
|
171
|
+
self, id: str, page: int = 1, no_fallback: bool | None = None
|
|
172
|
+
) -> Response:
|
|
173
|
+
"""
|
|
174
|
+
Get a Thumbnail image for a given Archive. This endpoint will return
|
|
175
|
+
a placeholder image if it doesn't already exist.
|
|
176
|
+
|
|
177
|
+
If you want to queue generation of the thumbnail in the background,
|
|
178
|
+
you can use the no_fallback query parameter. This will give you a
|
|
179
|
+
background job ID instead of the placeholder.
|
|
180
|
+
|
|
181
|
+
:param id: ID of the Archive to process.
|
|
182
|
+
:param page: Specify which page you want to get a thumbnail for.
|
|
183
|
+
Defaults to the cover, aka page 1.
|
|
184
|
+
:param no_fallback: Disables the placeholder image, queues the
|
|
185
|
+
thumbnail for extraction and returns a JSON with code 202. This
|
|
186
|
+
parameter does nothing if the image already exists. (You will get the
|
|
187
|
+
image with code 200 no matter what)
|
|
188
|
+
:return: the response object
|
|
189
|
+
"""
|
|
190
|
+
no_fallback_value = None
|
|
191
|
+
if no_fallback is not None:
|
|
192
|
+
no_fallback_value = "true" if no_fallback else "false"
|
|
193
|
+
|
|
194
|
+
return self.request(
|
|
195
|
+
"GET",
|
|
196
|
+
f"/api/archives/{id}/thumbnail",
|
|
197
|
+
params={"page": page, "no_fallback": no_fallback_value},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def queue_extraction_of_page_thumbnails(
|
|
201
|
+
self, id: str, force: bool = False
|
|
202
|
+
) -> MinionJobResponse:
|
|
203
|
+
"""
|
|
204
|
+
Create thumbnails for every page of a given Archive. This endpoint will
|
|
205
|
+
queue generation of the thumbnails in the background.
|
|
206
|
+
|
|
207
|
+
If all thumbnails are detected as already existing, the call will
|
|
208
|
+
return HTTP code 200.
|
|
209
|
+
|
|
210
|
+
This endpoint can be called multiple times -- If a thumbnailing job is
|
|
211
|
+
already in progress for the given ID, it'll just give you the ID for
|
|
212
|
+
that ongoing job.
|
|
213
|
+
:param id: ID of the Archive to process.
|
|
214
|
+
:param force: Whether to force regeneration of all thumbnails even if
|
|
215
|
+
they already exist.
|
|
216
|
+
:return: operation result
|
|
217
|
+
"""
|
|
218
|
+
return self.request_operation(
|
|
219
|
+
"POST",
|
|
220
|
+
f"/api/archives/{id}/files/thumbnails",
|
|
221
|
+
model=MinionJobResponse,
|
|
222
|
+
params={"force": force},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def download_archive(self, id: str) -> Response:
|
|
226
|
+
"""
|
|
227
|
+
Download an Archive from the server.
|
|
228
|
+
|
|
229
|
+
:param id: ID of the Archive to download.
|
|
230
|
+
:return: the response object
|
|
231
|
+
"""
|
|
232
|
+
return self.request("GET", f"/api/archives/{id}/download")
|
|
233
|
+
|
|
234
|
+
def extract_archive(self, id: str, force: bool = False) -> dict:
|
|
235
|
+
"""
|
|
236
|
+
Get a list of URLs pointing to the images contained in an archive.
|
|
237
|
+
If necessary, this endpoint also launches a background Minion job to
|
|
238
|
+
extract the archive so it is ready for reading.
|
|
239
|
+
|
|
240
|
+
:param id: ID of the Archive to process.
|
|
241
|
+
:param force: Force a full background re-extraction of the Archive.
|
|
242
|
+
Existing cached files might still be used in subsequent
|
|
243
|
+
/api/archives/:id/page calls until the Archive is fully re-extracted.
|
|
244
|
+
:return: operation result
|
|
245
|
+
"""
|
|
246
|
+
return self.request_json(
|
|
247
|
+
"GET", f"/api/archives/{id}/files", params={"force": force}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def add_archive_toc(self, id: str, page: int, title: str) -> OperationResponse:
|
|
251
|
+
"""
|
|
252
|
+
Add a Table of Contents entry for an archive.
|
|
253
|
+
:param id: ID of the Archive to process.
|
|
254
|
+
:param page: Page number where the chapter starts.
|
|
255
|
+
:param title: Chapter title.
|
|
256
|
+
:return: operation result
|
|
257
|
+
"""
|
|
258
|
+
return self.request_operation(
|
|
259
|
+
"PUT", f"/api/archives/{id}/toc", params={"page": page, "title": title}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def delete_archive_toc(self, id: str, page: int) -> OperationResponse:
|
|
263
|
+
"""
|
|
264
|
+
Delete a Table of Contents entry for an archive.
|
|
265
|
+
:param id: ID of the Archive to process.
|
|
266
|
+
:param page: Page number of the chapter entry to remove.
|
|
267
|
+
:return: operation result
|
|
268
|
+
"""
|
|
269
|
+
return self.request_operation(
|
|
270
|
+
"DELETE", f"/api/archives/{id}/toc", params={"page": page}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def get_archive_page(self, id: str, path: str) -> Response:
|
|
274
|
+
"""
|
|
275
|
+
Get a specific image page from an archive.
|
|
276
|
+
:param id: ID of the Archive to process.
|
|
277
|
+
:param path: Path to the image in extracted archive files.
|
|
278
|
+
:return: the response object
|
|
279
|
+
"""
|
|
280
|
+
return self.request("GET", f"/api/archives/{id}/page", params={"path": path})
|
|
281
|
+
|
|
282
|
+
def set_archive_new_flag(self, id: str) -> OperationResponse:
|
|
283
|
+
"""
|
|
284
|
+
Sets/restores the "New!" flag on an archive.
|
|
285
|
+
|
|
286
|
+
:param id: ID of the Archive to process.
|
|
287
|
+
:return: operation result
|
|
288
|
+
"""
|
|
289
|
+
return self.request_operation("PUT", f"/api/archives/{id}/isnew")
|
|
290
|
+
|
|
291
|
+
def clear_archive_new_flag(self, id: str) -> OperationResponse:
|
|
292
|
+
"""
|
|
293
|
+
Clears the "New!" flag on an archive.
|
|
294
|
+
|
|
295
|
+
:param id: ID of the Archive to process.
|
|
296
|
+
:return: operation result
|
|
297
|
+
"""
|
|
298
|
+
return self.request_operation("DELETE", f"/api/archives/{id}/isnew")
|
|
299
|
+
|
|
300
|
+
def update_reading_progression(self, id: str, page: int) -> OperationResponse:
|
|
301
|
+
"""
|
|
302
|
+
Tell the server which page of this Archive you're currently
|
|
303
|
+
showing/reading, so that it updates its internal reading progression
|
|
304
|
+
accordingly.
|
|
305
|
+
|
|
306
|
+
This endpoint will also update the date this Archive was last read,
|
|
307
|
+
using the current server timestamp.
|
|
308
|
+
|
|
309
|
+
You should call this endpoint only when you're sure the user is
|
|
310
|
+
currently reading the page you present.
|
|
311
|
+
|
|
312
|
+
Don't use it when preloading images off the server.
|
|
313
|
+
|
|
314
|
+
Whether to make reading progression regressible or not is up to
|
|
315
|
+
the client. (The web client will reduce progression if the user
|
|
316
|
+
starts reading previous pages)
|
|
317
|
+
|
|
318
|
+
Consider however removing the "New!" flag from an archive when you
|
|
319
|
+
start updating its progress - The web client won't display any
|
|
320
|
+
reading progression if the new flag is still set.
|
|
321
|
+
|
|
322
|
+
⚠ If the server is configured to use clientside progress tracking,
|
|
323
|
+
this API call will return an error!
|
|
324
|
+
|
|
325
|
+
Make sure to check using /api/info whether the server tracks reading
|
|
326
|
+
progression or not before calling this endpoint.
|
|
327
|
+
:param id: ID of the Archive to process
|
|
328
|
+
:param page: Current page to update the reading progress to. Must be
|
|
329
|
+
a positive integer, and inferior or equal to the total page number of
|
|
330
|
+
the archive.
|
|
331
|
+
:return: operation result
|
|
332
|
+
"""
|
|
333
|
+
return self.request_operation("PUT", f"/api/archives/{id}/progress/{page}")
|
|
334
|
+
|
|
335
|
+
def upload_archive(
|
|
336
|
+
self,
|
|
337
|
+
archive_path: str,
|
|
338
|
+
title: str = None,
|
|
339
|
+
tags: str = None,
|
|
340
|
+
summary: str = None,
|
|
341
|
+
category_id: str = None,
|
|
342
|
+
file_checksum: str = None,
|
|
343
|
+
) -> OperationResponse:
|
|
344
|
+
"""
|
|
345
|
+
Upload an Archive to the server.
|
|
346
|
+
|
|
347
|
+
If a SHA1 checksum of the Archive is included, the server will perform
|
|
348
|
+
an optional in-transit, file integrity validation, and reject the
|
|
349
|
+
upload if the server-side checksum does not match.
|
|
350
|
+
:param archive_path: filepath of the archive
|
|
351
|
+
:param title: Title of the Archive.
|
|
352
|
+
:param tags: Set of tags you want to insert in the database alongside
|
|
353
|
+
the archive.
|
|
354
|
+
:param summary: summary
|
|
355
|
+
:param category_id: Category ID you'd want the archive to be added to.
|
|
356
|
+
:param file_checksum: SHA1 checksum of the archive for in-transit
|
|
357
|
+
validation.
|
|
358
|
+
:return: operation result
|
|
359
|
+
"""
|
|
360
|
+
# deal with windows path separator
|
|
361
|
+
archive_path = archive_path.replace("\\", "/")
|
|
362
|
+
|
|
363
|
+
if not isfile(archive_path):
|
|
364
|
+
raise FileNotFoundError(f"File {archive_path} not found")
|
|
365
|
+
|
|
366
|
+
with open(archive_path, "rb") as archive_file:
|
|
367
|
+
return self.request_operation(
|
|
368
|
+
"PUT",
|
|
369
|
+
"/api/archives/upload",
|
|
370
|
+
files={
|
|
371
|
+
"file": (
|
|
372
|
+
archive_path.split("/")[-1],
|
|
373
|
+
archive_file,
|
|
374
|
+
"application/octet-stream",
|
|
375
|
+
)
|
|
376
|
+
},
|
|
377
|
+
data={
|
|
378
|
+
"title": title,
|
|
379
|
+
"tags": tags,
|
|
380
|
+
"summary": summary,
|
|
381
|
+
"category_id": category_id,
|
|
382
|
+
"file_checksum": file_checksum,
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def update_thumbnail(self, id: str, page: int = 1) -> OperationResponse:
|
|
387
|
+
"""
|
|
388
|
+
Update the cover thumbnail for the given Archive. You can specify a
|
|
389
|
+
page number to use as the thumbnail, or you can use the default
|
|
390
|
+
thumbnail.
|
|
391
|
+
:param id: ID of the Archive to process.
|
|
392
|
+
:param page: Page you want to make the thumbnail out of. Defaults to 1.
|
|
393
|
+
:return: operation result
|
|
394
|
+
"""
|
|
395
|
+
return self.request_operation(
|
|
396
|
+
"PUT", f"/api/archives/{id}/thumbnail", params={"page": page}
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def update_archive_metadata(
|
|
400
|
+
self,
|
|
401
|
+
id: str,
|
|
402
|
+
archive: ArchiveMetadata | None = None,
|
|
403
|
+
*,
|
|
404
|
+
title: str | None = None,
|
|
405
|
+
tags: str | None = None,
|
|
406
|
+
summary: str | None = None,
|
|
407
|
+
) -> OperationResponse:
|
|
408
|
+
"""
|
|
409
|
+
Update tags, title and summary for the given Archive.
|
|
410
|
+
:param id: ID of the Archive to process.
|
|
411
|
+
:param archive: Optional backward-compatible metadata object.
|
|
412
|
+
:param title: Archive title to set. If omitted and archive is provided,
|
|
413
|
+
uses archive.title.
|
|
414
|
+
:param tags: Archive tags string to set. If omitted and archive is
|
|
415
|
+
provided, uses archive.tags.
|
|
416
|
+
:param summary: Archive summary to set. If omitted and archive is
|
|
417
|
+
provided, uses archive.summary.
|
|
418
|
+
:return: operation result
|
|
419
|
+
"""
|
|
420
|
+
if archive is not None:
|
|
421
|
+
if title is None:
|
|
422
|
+
title = archive.title
|
|
423
|
+
if tags is None:
|
|
424
|
+
tags = archive.tags
|
|
425
|
+
if summary is None:
|
|
426
|
+
summary = archive.summary
|
|
427
|
+
|
|
428
|
+
return self.request_operation(
|
|
429
|
+
"PUT",
|
|
430
|
+
f"/api/archives/{id}/metadata",
|
|
431
|
+
params={"title": title, "tags": tags, "summary": summary},
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def delete_archive(self, id: str) -> OperationResponse:
|
|
435
|
+
"""
|
|
436
|
+
Delete both the archive metadata and the file stored on the server.
|
|
437
|
+
|
|
438
|
+
🙏 Please ask your user for confirmation before invoking this endpoint.
|
|
439
|
+
:param id: ID of the Archive to process.
|
|
440
|
+
:return: operation result
|
|
441
|
+
"""
|
|
442
|
+
return self.request_operation("DELETE", f"/api/archives/{id}")
|