initial commit
This commit is contained in:
26
.bumpversion.toml
Normal file
26
.bumpversion.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[tool.bumpversion]
|
||||
current_version = "0.5.2"
|
||||
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
||||
serialize = ["{major}.{minor}.{patch}"]
|
||||
search = "{current_version}"
|
||||
replace = "{new_version}"
|
||||
regex = false
|
||||
ignore_missing_version = false
|
||||
ignore_missing_files = false
|
||||
tag = false
|
||||
sign_tags = false
|
||||
tag_name = "v{new_version}"
|
||||
tag_message = "Bump version: {current_version} → {new_version}"
|
||||
allow_dirty = false
|
||||
commit = false
|
||||
message = "Bump version: {current_version} → {new_version}"
|
||||
moveable_tags = []
|
||||
commit_args = ""
|
||||
setup_hooks = []
|
||||
pre_commit_hooks = []
|
||||
post_commit_hooks = []
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "src/komgapi/__init__.py"
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "pyproject.toml"
|
||||
234
.gitignore
vendored
Normal file
234
.gitignore
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# ---> Qt
|
||||
# C++ objects and libs
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.a
|
||||
*.la
|
||||
*.lai
|
||||
*.so
|
||||
*.so.*
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
# Qt-es
|
||||
object_script.*.Release
|
||||
object_script.*.Debug
|
||||
*_plugin_import.cpp
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
*.qbs.user.*
|
||||
*.moc
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
qrc_*.cpp
|
||||
ui_*.h
|
||||
*.qmlc
|
||||
*.jsc
|
||||
Makefile*
|
||||
*build-*
|
||||
*.qm
|
||||
*.prl
|
||||
|
||||
# Qt unit tests
|
||||
target_wrapper.*
|
||||
|
||||
# QtCreator
|
||||
*.autosave
|
||||
|
||||
# QtCreator Qml
|
||||
*.qmlproject.user
|
||||
*.qmlproject.user.*
|
||||
|
||||
# QtCreator CMake
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# QtCreator 4.8< compilation database
|
||||
compile_commands.json
|
||||
|
||||
# QtCreator local machine specific files for imported projects
|
||||
*creator.user*
|
||||
|
||||
*_qmlcache.qrc
|
||||
|
||||
|
||||
.history
|
||||
depend
|
||||
output/output/LOGtoJSON.exe
|
||||
|
||||
.pytest_cache
|
||||
output
|
||||
docs/
|
||||
config.yaml
|
||||
**/tempCodeRunnerFile.py
|
||||
|
||||
uv.lock
|
||||
.history
|
||||
.venv
|
||||
venv
|
||||
*.log
|
||||
14
.hatch.yaml
Normal file
14
.hatch.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
build:
|
||||
exclude:
|
||||
- .git
|
||||
- .gitignore
|
||||
- .hatch.yaml
|
||||
- .travis.yml
|
||||
- .coveragerc
|
||||
- .editorconfig
|
||||
- .gitattributes
|
||||
- .readthedocs.yml
|
||||
- .readthedocs
|
||||
- .trunk
|
||||
- .venv
|
||||
- .vscode
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 WorldTeacher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
14
pyproject.toml
Normal file
14
pyproject.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "komgapi"
|
||||
version = "0.5.2"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "WorldTeacher", email = "coding_contact@pm.me" }
|
||||
]
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
6
src/komgapi/__init__.py
Normal file
6
src/komgapi/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
__version__ = "0.5.2"
|
||||
__all__ = ["komgAPI"]
|
||||
|
||||
from .komgapi import KOMGAPI_REST as komgapi
|
||||
from .endpoints import *
|
||||
from .schemas import *
|
||||
12
src/komgapi/endpoints/__init__.py
Normal file
12
src/komgapi/endpoints/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from .common_controller import CommonBookController
|
||||
from .library_controller import LibraryController
|
||||
from .series_controller import SeriesController
|
||||
from .readlist_controller import ReadListController
|
||||
from .series_collection_controller import SeriesCollectionController
|
||||
from .book_controller import BookController
|
||||
from .announcement_controller import AnnouncementController
|
||||
from .user_controller import UserController
|
||||
from .settings_controller import SettingsController
|
||||
from .claim_controller import ClaimController
|
||||
from .referential_controller import ReferentialController
|
||||
|
||||
21
src/komgapi/endpoints/announcement_controller.py
Normal file
21
src/komgapi/endpoints/announcement_controller.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class AnnouncementController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getAnnouncements(self) -> List[Announcement]:
|
||||
url = self.url + "announcements"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for announcement in data:
|
||||
ret.append(Announcement(**announcement))
|
||||
return ret
|
||||
143
src/komgapi/endpoints/baseapi.py
Normal file
143
src/komgapi/endpoints/baseapi.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import requests
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from typing import Any, Union
|
||||
from limit import limit
|
||||
|
||||
|
||||
class BaseAPI:
|
||||
def __init__(self, username, password, url, timeout=20, api_version=1) -> None:
|
||||
self._username = username
|
||||
self._password = password
|
||||
self.url = url + f"api/v{api_version}/"
|
||||
self.timeout = timeout
|
||||
|
||||
def setParams(self, locals: dict) -> dict:
|
||||
return {
|
||||
param_name: param
|
||||
for param_name, param in locals.items()
|
||||
if param is not None and param_name not in ["self", "series_idurl"]
|
||||
}
|
||||
|
||||
def test_connection(self):
|
||||
"""Test the connection to the server.
|
||||
|
||||
Returns:
|
||||
bool: True if the connection is successful, False otherwise.
|
||||
"""
|
||||
try:
|
||||
requests.get(self.url, timeout=self.timeout)
|
||||
return True
|
||||
except requests.exceptions.RequestException:
|
||||
return False
|
||||
|
||||
def overwriteVersion(self, version: int):
|
||||
self.url = self.url.replace("api/v1/", f"api/v{version}/")
|
||||
return self
|
||||
|
||||
@limit(1, 1)
|
||||
def getRequest(self, url, params: Union[dict, None] = None) -> Any:
|
||||
if params is None:
|
||||
params = {}
|
||||
try:
|
||||
# ic(url, params)
|
||||
response = requests.get(
|
||||
url,
|
||||
auth=(self._username, self._password),
|
||||
params=params,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
# print(response.content)
|
||||
return response.json()
|
||||
except ConnectionError as e:
|
||||
message = f"Connection Error: {e}"
|
||||
raise KomgaError(message) from e
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise KomgaError(f"Timeout Error: {e}") from e
|
||||
|
||||
def postRequest(self, url, data: Union[dict, None] = None):
|
||||
if data is None:
|
||||
data = {}
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
auth=(self._username, self._password),
|
||||
json=data,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
status_code = response.status_code
|
||||
if status_code != 202:
|
||||
raise ResultErrror(f"Result Error: {response.json()}")
|
||||
elif status_code == 200:
|
||||
return response.json()
|
||||
except ConnectionError as e:
|
||||
message = f"Connection Error: {e}"
|
||||
raise KomgaError(message) from e
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise KomgaError(f"Timeout Error: {e}") from e
|
||||
|
||||
def patchRequest(self, url, data: Union[dict, None] = None):
|
||||
if data is None:
|
||||
data = {}
|
||||
try:
|
||||
response = requests.patch(
|
||||
url,
|
||||
auth=(self._username, self._password),
|
||||
json=data,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
if response.status_code != 204:
|
||||
raise ResultErrror(f"Result Error: {response.json()}")
|
||||
except ConnectionError as e:
|
||||
message = f"Connection Error: {e}"
|
||||
raise KomgaError(message) from e
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise KomgaError(f"Timeout Error: {e}") from e
|
||||
|
||||
def putRequest(self, url, data: Union[dict, None] = None):
|
||||
if data is None:
|
||||
data = {}
|
||||
try:
|
||||
response = requests.put(
|
||||
url,
|
||||
auth=(self._username, self._password),
|
||||
json=data,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except ConnectionError as e:
|
||||
message = f"Connection Error: {e}"
|
||||
raise KomgaError(message) from e
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise KomgaError(f"Timeout Error: {e}") from e
|
||||
|
||||
def deleteRequest(self, url):
|
||||
try:
|
||||
response = requests.delete(
|
||||
url, auth=(self._username, self._password), timeout=self.timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except ConnectionError as e:
|
||||
message = f"Connection Error: {e}"
|
||||
raise KomgaError(message) from e
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise KomgaError(f"Timeout Error: {e}") from e
|
||||
|
||||
@classmethod
|
||||
def from_env(cls):
|
||||
"""Create a KOMGA API object from environment variables.
|
||||
|
||||
Returns:
|
||||
KOMGAPI_REST: The KOMGA API object.
|
||||
"""
|
||||
import os
|
||||
|
||||
return cls(
|
||||
os.environ["KOMGA_USERNAME"],
|
||||
os.environ["KOMGA_PASSWORD"],
|
||||
os.environ["KOMGA_URL"],
|
||||
)
|
||||
501
src/komgapi/endpoints/book_controller.py
Normal file
501
src/komgapi/endpoints/book_controller.py
Normal file
@@ -0,0 +1,501 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class BookController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getBooks(
|
||||
self,
|
||||
search_string: str = None,
|
||||
library_id: List[str] = None,
|
||||
media_status: List[str] = None,
|
||||
read_status: List[str] = None,
|
||||
released_after: List[str] = None,
|
||||
tag: List[str] = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
sort: List[str] = None,
|
||||
) -> List[Book]:
|
||||
"""Get all Books that match the given query.
|
||||
|
||||
Args:
|
||||
----
|
||||
- search_string (str, optional): An optional filter limiting the search to only return results containing the string. Defaults to None.
|
||||
- library_id (List[str], optional): An optional filter limiting the searched libraries. Defaults to None.
|
||||
- media_status (List[str], optional): An optional filter limiting the status of the books. Available values : UNKNOWN, ERROR, READY, UNSUPPORTED, OUTDATED. Defaults to None.
|
||||
- read_status (List[str], optional): An optional filter limiting the read status of the books. Available values : UNREAD, READ, IN_PROGRESS. Defaults to None.
|
||||
- released_after (List[str], optional): An optional filter limiting the search to books released after the set date. Defaults to None.
|
||||
- tag (List[str], optional): Optional tags limiting the search. Defaults to None.
|
||||
- unpaged (bool, optional): Set to False if a single Page of results should be returned. By default, a page contains 20 entries. Defaults to True.
|
||||
- page (int, optional): An integer to get a set page. Defaults to None.
|
||||
- size (int, optional): An integer setting the size of the page. Defaults to None.
|
||||
- sort (List[str], optional): An option to sort the result. Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported. Defaults to None.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
List[Book]: A list of all books that match the query. Each book is represented as a Book object.
|
||||
"""
|
||||
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "books"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for book in data["content"]:
|
||||
ret.append(Book(**book))
|
||||
return ret
|
||||
|
||||
def getBook(self, book_id: str) -> Book:
|
||||
"""Get a specific book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Book: the book wrapped in a Book class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}"
|
||||
data = self.getRequest(url)
|
||||
return Book(**data)
|
||||
|
||||
def analyzeBook(self, book_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Analyze a specific book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to analyze.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/analyze"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
@typing_extensions.deprecated("This function is not implemented yet.")
|
||||
def deleteBookFile(self, book_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Not implemented yet."""
|
||||
# url = self.url + f"books/{book_id}/file"
|
||||
# data = self.deleteRequest(url)
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO: add getBookFile (with /*) if this gets a better description
|
||||
def getManifest(self, book_id: str) -> Manifest:
|
||||
"""Get the manifest of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the manifest from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Manifest: the manifest wrapped in a Manifest class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/manifest"
|
||||
data = self.getRequest(url)
|
||||
return Manifest(**data)
|
||||
|
||||
def getSpecificManifest(self, book_id: str, manifest: str) -> Manifest:
|
||||
"""Get the specified Manifest. 3 options are available.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): The ID of the book to get the manifest from.
|
||||
- manifest (str): the manifest to get. Options are epub, pdf, divina
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Manifest: the manifest wrapped in a Manifest class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/manifest/{manifest}"
|
||||
data = self.getRequest(url)
|
||||
return Manifest(**data)
|
||||
|
||||
def patchBookMetadata(
|
||||
self, book_id: str, metadata: BookMetadata
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get the metadata of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the metadata from.
|
||||
- metadata (BookMetadata): the metadata to set. All fields need to be set.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Metadata: the metadata wrapped in a Metadata class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/metadata"
|
||||
data = self.patchRequest(url, metadata.model_dump())
|
||||
return data
|
||||
|
||||
def refreshBookMetadata(self, book_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Refresh the metadata of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to refresh the metadata from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/metadata/refresh"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def getNextBook(self, book_id: str) -> Book:
|
||||
"""Get the next Book in the series. If this is not possible, a HTTPError is raised.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the next book from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Book: the next book wrapped in a Book class.
|
||||
"""
|
||||
|
||||
url = self.url + f"books/{book_id}/next"
|
||||
data = self.getRequest(url)
|
||||
return Book(**data)
|
||||
|
||||
def getBookPages(self, book_id: str) -> List[Page]:
|
||||
"""Get the pages of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the pages from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Page]: A list of all pages that match the query. Each page is represented as a Page object.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/pages"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for page in data:
|
||||
ret.append(Page(**page))
|
||||
return ret
|
||||
|
||||
def getBookPage(
|
||||
self,
|
||||
book_id: str,
|
||||
page: int,
|
||||
convert: str = None,
|
||||
zero_based: bool = False,
|
||||
Accept: List[str] = None,
|
||||
) -> Page:
|
||||
"""Get a specific page of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the page from.
|
||||
- page (int): the page to get.
|
||||
- convert (str, optional): The format to convert the page to. Available values : JPEG, PNG. Defaults to None.
|
||||
- zero_based (bool, optional): If set to true, pages will start at zero. Defaults to False.
|
||||
- Accept (List[str]): "Some very limited server driven content negotiation is handled. If a book is a PDF book, and the Accept header contains 'application/pdf' as a more specific type than other 'image/' types, a raw PDF page will be returned."
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Page: the page wrapped in a Page class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/pages/{page}"
|
||||
data = self.getRequest(url)
|
||||
return Page(**data)
|
||||
|
||||
def getBookPositions(self, book_id: str) -> List[Position]:
|
||||
"""Get the positions in an ebook. USE UNCLEAR.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the id of the book to get the positions from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Position]: A list of all positions that match the query. Each position is represented as a Position object.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/positions"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for position in data:
|
||||
ret.append(Position(**position))
|
||||
return ret
|
||||
|
||||
def getPreviousBook(self, book_id: str) -> Book:
|
||||
"""Get the previous Book in the series. If this is not possible, a HTTPError is raised.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the previous book from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Book: the previous book wrapped in a Book class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/previous"
|
||||
data = self.getRequest(url)
|
||||
return Book(**data)
|
||||
|
||||
def setProgress(self, book_id: str, progress: Progress) -> Optional[Dict[str, Any]]:
|
||||
"""Set the progress of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to set the progress to.
|
||||
- progress (Progress): the progress to set.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/progression"
|
||||
data = self.putRequest(url, progress.model_dump())
|
||||
return data
|
||||
|
||||
def deleteProgress(self, book_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Set the book as unread.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to delete the progress from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/read-progress"
|
||||
|
||||
def setProgress(
|
||||
self, book_id: str, progress: Dict[str, Any]
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Set the progress of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to set the progress to.
|
||||
- progress (Progress): the progress to set. Needs to be a dict in the format: {"page": int, "completed": bool}
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/read-progress"
|
||||
data = self.putRequest(url, progress)
|
||||
return data
|
||||
|
||||
def getBookReadlists(self, book_id: str) -> List[Readlist]:
|
||||
"""Get the readlists of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the readlists from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Readlist]: A list of all readlists that match the query. Each readlist is represented as a Readlist object.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/readlists"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for readlist in data:
|
||||
ret.append(Readlist(**readlist))
|
||||
return ret
|
||||
|
||||
def getBookThumbnail(self, book_id: str) -> bytes:
|
||||
"""Get the thumbnail of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the thumbnail from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- bytes: the thumbnail as a bytestring.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/thumbnail"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def getBookThumbnails(self, book_id: str) -> List[Thumbnail]:
|
||||
"""Get the metadata of all available thumbnails for the book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the thumbnails from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Thumbnail]: A list of all possible thumbnails wrapped in a Thumbnail class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/thumbnails"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for thumb in data:
|
||||
ret.append(Thumbnail(**thumb))
|
||||
return ret
|
||||
|
||||
def setBookThumbnail(
|
||||
self, book_id: str, selected: bool, file: bytes
|
||||
) -> Union[Optional[Dict[str, Any]], Thumbnail]:
|
||||
"""Set a thumbnail based on a bytestring sent through this function.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to set the thumbnail to.
|
||||
- selected (bool): Whether the thumbnail should be selected.Set to True if the thumbnail should be selected.
|
||||
- file (bytes): the thumbnail to set as a bytestring.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Union[Optional[Dict[str, Any]],Thumbnail]: If successful, the server will return the thumbnail data wrapped in a Thumbnail class. If a violation is found, the server will return a dictionary with the violation.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/thumbnails"
|
||||
params = {"selected": selected, "thumbnail": file}
|
||||
data = self.postRequest(url, params)
|
||||
return Thumbnail(**data)
|
||||
|
||||
def getSpecificBookThumbnail(self, book_id: str, thumbnail_id: str) -> bytes:
|
||||
"""Get a specific thumbnail based on the book id and the thumbnail id.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the thumbnail from.
|
||||
- thumbnail_id (str): the ID of the thumbnail to get.1
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- bytes: the thumbnail as a bytestring.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/thumbnails/{thumbnail_id}"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def deleteSpecificBookThumbnail(
|
||||
self, book_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Delete a specific Thumbnail based on book id and thumbnail id.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to delete the thumbnail from.
|
||||
- thumbnail_id (str): the ID of the thumbnail to delete.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/thumbnails/{thumbnail_id}"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def setSpecificBookThumbnail(
|
||||
self, book_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Set the thumbnail to be selected.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to set the thumbnail to.
|
||||
- thumbnail_id (str): the ID of the thumbnail to set.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/thumbnail/{thumbnail_id}/selected"
|
||||
data = self.putRequest(url)
|
||||
return data
|
||||
|
||||
def getDuplicates(
|
||||
self,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
sort: List[str] = None,
|
||||
) -> List[Duplicate]:
|
||||
"""Get a list of duplicates.
|
||||
|
||||
Args:
|
||||
----
|
||||
- unpaged (bool, optional): If the result should be unpaged. Defaults to True.
|
||||
- page (int, optional): the page to request. Defaults to None.
|
||||
- size (int, optional): the size of a page. Defaults to None.
|
||||
- sort (List[str], optional): Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.. Defaults to None.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Duplicate]: A list of all duplicates that match the query. Each duplicate is represented as a Duplicate object.
|
||||
"""
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "books/duplicates"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for duplicate in data["content"]:
|
||||
ret.append(Duplicate(**duplicate))
|
||||
return ret
|
||||
|
||||
@typing_extensions.deprecated("This function is not implemented yet.")
|
||||
def importBook(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def getLatestBooks(
|
||||
self, unpaged: str = False, page: int = None, size: int = None
|
||||
) -> List[Book]:
|
||||
"""Get a list of the newly added or updated books. By default, it returns up to 20 entries.
|
||||
Setting unpaged to True will return all entries.
|
||||
|
||||
Args:
|
||||
----
|
||||
- unpaged (str, optional): If the result should be paged. Defaults to False.
|
||||
- page (int, optional): The page to request. Defaults to None.
|
||||
- size (int, optional): The size of the page. Defaults to None.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Book]: A list of all books that match the query. Each book is represented as a Book object.
|
||||
"""
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "books/latest"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for book in data["content"]:
|
||||
ret.append(Book(**book))
|
||||
return ret
|
||||
|
||||
def getOnDeckBooks(
|
||||
self, library_id: List[str] = None, page: int = None, size: int = None
|
||||
) -> List[Book]:
|
||||
"""Return first unread book of series with at least one book read and no books in progress..
|
||||
|
||||
Args:
|
||||
----
|
||||
- library_id (List[str], optional): Optional Filter to set specific libraries. Defaults to None.
|
||||
- page (int, optional): _description_. Defaults to None.
|
||||
- size (int, optional): _description_. Defaults to None.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- List[Book]: A list of all books that match the query. Each book is represented as a Book object.
|
||||
"""
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "books/ondeck"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for book in data["content"]:
|
||||
ret.append(Book(**book))
|
||||
return ret
|
||||
18
src/komgapi/endpoints/claim_controller.py
Normal file
18
src/komgapi/endpoints/claim_controller.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class ClaimController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getClaim(self) -> dict:
|
||||
url = self.url + "claim"
|
||||
data = self.getRequest(url)
|
||||
return data
|
||||
91
src/komgapi/endpoints/common_controller.py
Normal file
91
src/komgapi/endpoints/common_controller.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from komgapi.schemas import *
|
||||
|
||||
|
||||
class CommonBookController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getBookFile(
|
||||
self, book_id: str, download_path: str = "~/Downloads"
|
||||
) -> pathlib.Path:
|
||||
"""Download the book file.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to download.
|
||||
- download_path (str, optional): The path to download the file to. Defaults to "~/Downloads".
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- pathlib.Path: The path to the downloaded file.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/file"
|
||||
download_path = pathlib.Path(download_path).expanduser()
|
||||
subprocess.run(
|
||||
[
|
||||
"curl",
|
||||
"-u",
|
||||
f"{self._username}:{self._password}",
|
||||
"-o",
|
||||
f"{download_path}/{book_id}.zip",
|
||||
url,
|
||||
]
|
||||
)
|
||||
return pathlib.Path(f"{download_path}/{book_id}.zip")
|
||||
|
||||
def getRawPage(self, book_id: str, page: int) -> bytes:
|
||||
"""Get the raw page of a book.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the page from.
|
||||
- page (int): the page to get.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- bytes: the page as a bytestring.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/pages/{page}/raw"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def getPageThumbnail(self, book_id: str, page: int) -> bytes:
|
||||
"""Get the thumbnail of a page.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the page from.
|
||||
- page (int): the page to get.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- bytes: the thumbnail as a bytestring.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/pages/{page}/thumbnail"
|
||||
ic(url)
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def getProgress(self, book_id: str) -> Progress:
|
||||
"""Get the progress of a book. The progress contains a timestamp of the latest modification, the device where the modification was made (if the data is present), and a Locator object.
|
||||
|
||||
Args:
|
||||
----
|
||||
- book_id (str): the ID of the book to get the progress from.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- Progress: the progress wrapped in a Progress class.
|
||||
"""
|
||||
url = self.url + f"books/{book_id}/progression"
|
||||
data = self.getRequest(url)
|
||||
return Progress(**data)
|
||||
|
||||
@typing_extensions.deprecated("This function is not implemented yet.")
|
||||
def getbookRessource(self, book_id: str, ressource: str) -> bytes:
|
||||
raise NotImplementedError
|
||||
58
src/komgapi/endpoints/library_controller.py
Normal file
58
src/komgapi/endpoints/library_controller.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class LibraryController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getLibraries(self) -> List[Library]:
|
||||
url = self.url + "libraries"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for library in data:
|
||||
ret.append(Library(**library))
|
||||
return ret
|
||||
|
||||
def createLibrary(
|
||||
self, library: CreateLibrary
|
||||
) -> Union[Library, Optional[Dict[str, Any]]]:
|
||||
url = self.url + "libraries"
|
||||
data = self.postRequest(url, library.model_dump())
|
||||
return Library(**data) if data else None
|
||||
|
||||
def getLibrary(self, library_id: str) -> Library:
|
||||
url = self.url + f"libraries/{library_id}"
|
||||
data = self.getRequest(url)
|
||||
return Library(**data)
|
||||
|
||||
def patchLibrary(self, library_id: str, changed_library: Dict[str, Any]) -> None:
|
||||
url = self.url + f"libraries/{library_id}"
|
||||
data = self.patchRequest(url, changed_library)
|
||||
return data
|
||||
|
||||
def analyzeLibrary(self, library_id: str) -> Optional[Dict[str, Any]]:
|
||||
url = self.url + f"libraries/{library_id}/analyze"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def emptyTrash(self, library_id: str) -> None:
|
||||
url = self.url + f"libraries/{library_id}/empty-trash"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def refreshMetadata(self, library_id: str) -> None:
|
||||
url = self.url + f"libraries/{library_id}/metadata/refresh"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def scanLibrary(self, library_id: str) -> Optional[Dict[str, Any]]:
|
||||
url = self.url + f"libraries/{library_id}/scan"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
256
src/komgapi/endpoints/readlist_controller.py
Normal file
256
src/komgapi/endpoints/readlist_controller.py
Normal file
@@ -0,0 +1,256 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class ReadListController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getReadlists(
|
||||
self,
|
||||
search_string: str = None,
|
||||
library_id: List[str] = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
) -> List[Series]:
|
||||
"""Get a list of all readlists.
|
||||
|
||||
Args:
|
||||
search_string (str, optional): The string to search for. Defaults to None.
|
||||
library_id (List[str], optional): The library to search in. Defaults to None.
|
||||
unpaged (bool, optional): Set to False to request the result in N pages. Defaults to True.
|
||||
page (int, optional): Used in conjunction with unpaged. Sets the requested page. Defaults to None.
|
||||
size (int, optional): Defines the size of the page. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[dict]: A list of all readlists.
|
||||
"""
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "readlists"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for readlist in data["content"]:
|
||||
ic(readlist)
|
||||
ret.append(Series(**readlist))
|
||||
return ret
|
||||
|
||||
def setReadlist(
|
||||
self, readlist: Readlist
|
||||
) -> Union[Readlist, Optional[Dict[str, Any]]]:
|
||||
"""Create a new readlist.
|
||||
|
||||
Args:
|
||||
readlist (Readlist): The readlist to create.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + "readlists"
|
||||
data = self.postRequest(url, readlist.model_dump())
|
||||
return Readlist(**data) if data else None
|
||||
|
||||
def getReadlist(self, readlist_id: str) -> Series:
|
||||
"""Get a specific readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to get.
|
||||
|
||||
Returns:
|
||||
Series: The readlist wrapped in a Series class.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}"
|
||||
data = self.getRequest(url)
|
||||
return Series(**data)
|
||||
|
||||
def deleteReadlist(self, readlist_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Delete a specific readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to delete.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def patchReadlist(
|
||||
self, readlist_id: str, changed_readlist: Dict[str, Any]
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Change the metadata of a readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to change.
|
||||
changed_readlist (Dict[str,Any]): The changed readlist. This should be a dictionary with the changed fields. Only the fields that are changed need to be included.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}"
|
||||
data = self.patchRequest(url, changed_readlist)
|
||||
return data
|
||||
|
||||
def getReadlistBooks(
|
||||
self,
|
||||
readlist_id: str,
|
||||
library_id: List[str] = None,
|
||||
read_status: List[str] = None,
|
||||
media_status: List[str] = None,
|
||||
deleted: bool = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
author: List[str] = None,
|
||||
) -> List[Book]:
|
||||
url = self.url + f"readlists/{readlist_id}/books"
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for book in data["content"]:
|
||||
ret.append(Book(**book))
|
||||
return ret
|
||||
|
||||
def getNextReadlistBook(self, readlist_id: str, book_id: str) -> Book:
|
||||
"""Get the next book in a readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to get the next book from.
|
||||
book_id (str): The ID of the current book.
|
||||
|
||||
Returns:
|
||||
Book: The next book wrapped in a Book class.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/books/{book_id}/next"
|
||||
data = self.getRequest(url)
|
||||
return Book(**data)
|
||||
|
||||
def getPreviousReadlistBook(self, readlist_id: str, book_id: str) -> Book:
|
||||
"""Get the previous book in a readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to get the previous book from.
|
||||
book_id (str): The ID of the current book.
|
||||
|
||||
Returns:
|
||||
Book: The previous book wrapped in a Book class.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/books/{book_id}/previous"
|
||||
data = self.getRequest(url)
|
||||
return Book(**data)
|
||||
|
||||
def getReadlistFile(
|
||||
self, readlist_id: str, download_path: str = "~/Downloads"
|
||||
) -> pathlib.Path:
|
||||
"""Download the complete readlist as a giant zip file.
|
||||
Unlike the other functions, this one has no timeout. It will wait until the file is downloaded.
|
||||
WARNING: THIS WILL TAKE A VERY LONG TIME FOR LARGE READLISTS. USE WITH CAUTION.
|
||||
My test: 800mb took ~7min to download.
|
||||
Args:
|
||||
readlist_id (str): ID of the readlist to download the file from.
|
||||
download_path (str, optional): The path to download the file to. Defaults to "~/Downloads".
|
||||
|
||||
Returns:
|
||||
pathlib.Path: The path to the downloaded file.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/file"
|
||||
download_path = pathlib.Path("~/Downloads").expanduser()
|
||||
subprocess.run(
|
||||
[
|
||||
"curl",
|
||||
"-u",
|
||||
f"{self._username}:{self._password}",
|
||||
"-o",
|
||||
f"{download_path}/{readlist_id}.zip",
|
||||
url,
|
||||
]
|
||||
)
|
||||
return pathlib.Path(f"{download_path}/{readlist_id}.zip")
|
||||
|
||||
def getReadlistThumbnail(self, readlist_id: str) -> str:
|
||||
"""Get the current active thumbnail of a readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): the ID of the readlist to get the thumbnail from.
|
||||
|
||||
Returns:
|
||||
str: the thumbnail of the readlist formatted as bytestring.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/thumbnail"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def getReadlistThumbnails(self, readlist_id: str) -> List[ReadlistThumbnail]:
|
||||
"""Get the metadata of all available thumbnails for the readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): the ID of the readlist to get the thumbnails from.
|
||||
|
||||
Returns:
|
||||
List[Thumbnail]: A list of all possible thumbnails wrapped in a Thumbnail class.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/thumbnails"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for thumb in data:
|
||||
ret.append(ReadlistThumbnail(**thumb))
|
||||
return ret
|
||||
|
||||
def setReadlistThumbnail(
|
||||
self, readlist_id: str, selected: bool, thumbnail: bytes
|
||||
) -> Union[Thumbnail, Optional[Dict[str, Any]]]:
|
||||
"""Set a new thumbnail for the readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to set the thumbnail to.
|
||||
selected (bool): If the thumbnail should be selected.
|
||||
thumbnail (bytes): The thumbnail to set.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/thumbnail"
|
||||
data = self.postRequest(url, {"selected": selected, "thumbnail": thumbnail})
|
||||
return Thumbnail(**data) if data else None
|
||||
|
||||
def getReadlistSpecificThumbnail(
|
||||
self, readlist_id: str, thumbnail_id: str
|
||||
) -> bytes:
|
||||
url = self.url + f"readlists/{readlist_id}/thumbnails/{thumbnail_id}"
|
||||
try:
|
||||
data = requests.get(
|
||||
url, auth=(self._username, self._password), timeout=self.timeout
|
||||
)
|
||||
return data.content
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise KomgaError(f"Timeout Error: {e}") from e
|
||||
|
||||
def deleteReadlistSpecificThumbnail(
|
||||
self, readlist_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Delete a specific thumbnail of a readlist.
|
||||
|
||||
Args:
|
||||
readlist_id (str): The ID of the readlist to delete the thumbnail from.
|
||||
thumbnail_id (str): The ID of the thumbnail to delete.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"readlists/{readlist_id}/thumbnails/{thumbnail_id}"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def setReadlistSpecificThumbnail(self, readlist_id: str, thumbnail_id: str):
|
||||
url = self.url + f"readlists/{readlist_id}/thumbnail/{thumbnail_id}/selected"
|
||||
response = self.putRequest(url)
|
||||
return response
|
||||
120
src/komgapi/endpoints/referential_controller.py
Normal file
120
src/komgapi/endpoints/referential_controller.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class ReferentialController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getAgeRatings(self, library_id: str, collection_id: str) -> str:
|
||||
url = self.url + f"age-ratings"
|
||||
data = self.getRequest(url)
|
||||
return data
|
||||
|
||||
def getAuthors(
|
||||
self,
|
||||
search: str = None,
|
||||
library_id: str = None,
|
||||
collection_id: str = None,
|
||||
series_id: str = None,
|
||||
) -> List[Author]:
|
||||
url = self.url + f"authors"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return [Author(**author) for author in data]
|
||||
|
||||
def getAuthorNames(self, search: str = None) -> List[str]:
|
||||
url = self.url + f"authors/names"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getAuthorRoles(self) -> List[str]:
|
||||
url = self.url + f"authors/roles"
|
||||
data = self.getRequest(url)
|
||||
return data
|
||||
|
||||
def getGenres(self, library_id: str = None, collection_id: str = None) -> List[str]:
|
||||
url = self.url + f"genres"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getLanguages(
|
||||
self, library_id: str = None, collection_id: str = None
|
||||
) -> List[str]:
|
||||
url = self.url + f"languages"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getPublishers(
|
||||
self, search: str = None, library_id: str = None, collection_id: str = None
|
||||
) -> List[str]:
|
||||
url = self.url + f"publishers"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getReleaseDates(
|
||||
self, library_id: str = None, collection_id: str = None
|
||||
) -> List[str]:
|
||||
url = self.url + f"release-dates"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getSharingLabels(
|
||||
self, library_id: str = None, collection_id: str = None
|
||||
) -> List[str]:
|
||||
url = self.url + f"sharing-labels"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getTags(self, library_id: str = None, collection_id: str = None) -> List[str]:
|
||||
url = self.url + f"tags"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getBookTags(self, library_id: str = None, readlist_id: str = None) -> List[str]:
|
||||
url = self.url + f"book-tags"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getSeriesTags(
|
||||
self, library_id: str = None, collection_id: str = None
|
||||
) -> List[str]:
|
||||
url = self.url + f"series-tags"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getAuthorsV2(
|
||||
self,
|
||||
search: str = None,
|
||||
role: str = None,
|
||||
library_id: str = None,
|
||||
collection_id: str = None,
|
||||
series_id: str = None,
|
||||
readlist_id: str = None,
|
||||
unpaged: bool = None,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
) -> List[Author]:
|
||||
url = self.url + f"authors"
|
||||
params = self.setParams(locals())
|
||||
print(params)
|
||||
import requests
|
||||
|
||||
data: requests.Response = self.overwriteVersion(2).getRequest(url, params)
|
||||
content = data.json()
|
||||
return [Author(**author) for author in content]
|
||||
136
src/komgapi/endpoints/series_collection_controller.py
Normal file
136
src/komgapi/endpoints/series_collection_controller.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class SeriesCollectionController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getCollections(
|
||||
self,
|
||||
search_string: str = None,
|
||||
library_id: List[str] = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
) -> List[Collection]:
|
||||
url = self.url + "collections"
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for collection in data["content"]:
|
||||
ret.append(Collection(**collection))
|
||||
return ret
|
||||
|
||||
def createCollection(self, collection: dict) -> Optional[Dict[str, Any]]:
|
||||
"""Create a new collection.
|
||||
|
||||
Args:
|
||||
collection (dict): A dictionary with the collection data.Needs to contain this:
|
||||
{
|
||||
"name": string,
|
||||
"ordered": bool,
|
||||
"seriesIds": [
|
||||
string
|
||||
]
|
||||
}
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: An optional dictionary with the created collection.
|
||||
"""
|
||||
url = self.url + "collections"
|
||||
data = self.postRequest(url, collection)
|
||||
return data
|
||||
|
||||
def getCollection(self, collection_id: str) -> Collection:
|
||||
url = self.url + f"collections/{collection_id}"
|
||||
data = self.getRequest(url)
|
||||
return Collection(**data)
|
||||
|
||||
def deleteCollection(self, collection_id: str) -> Optional[Dict[str, Any]]:
|
||||
url = self.url + f"collections/{collection_id}"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def patchCollection(
|
||||
self, collection_id: str, changed_collection: dict
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
url = self.url + f"collections/{collection_id}"
|
||||
data = self.patchRequest(url, changed_collection)
|
||||
return data
|
||||
|
||||
def getCollectionSeries(
|
||||
self,
|
||||
collection_id: str,
|
||||
library_id: List[str] = None,
|
||||
read_status: List[str] = None,
|
||||
publisher: List[str] = None,
|
||||
language: List[str] = None,
|
||||
genre: List[str] = None,
|
||||
tag: List[str] = None,
|
||||
age_rating: List[str] = None,
|
||||
deleted: bool = None,
|
||||
complete: bool = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
author: List[str] = None,
|
||||
) -> List[Series]:
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + f"collections/{collection_id}/series"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for series in data["content"]:
|
||||
ret.append(Series(**series))
|
||||
return ret
|
||||
|
||||
def getCollectionThumbnail(self, collection_id: str) -> str:
|
||||
url = self.url + f"collections/{collection_id}/thumbnail"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def getCollectionThumbnails(self, collection_id: str) -> List[Thumbnail]:
|
||||
url = self.url + f"collections/{collection_id}/thumbnails"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for thumb in data:
|
||||
ret.append(Thumbnail(**thumb))
|
||||
return ret
|
||||
|
||||
def setCollectionThumbnail(
|
||||
self, collection_id: str, thumbnail_id: str, selected: bool
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
url = self.url + f"collections/{collection_id}/thumbnail/{thumbnail_id}"
|
||||
data = self.putRequest(url, {"selected": selected})
|
||||
return data
|
||||
|
||||
def getSpecificCollectionThumbnail(
|
||||
self, collection_id: str, thumbnail_id: str
|
||||
) -> bytes:
|
||||
url = self.url + f"collections/{collection_id}/thumbnails/{thumbnail_id}"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
return data.content
|
||||
|
||||
def deleteSpecificCollectionThumbnail(
|
||||
self, collection_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
url = self.url + f"collections/{collection_id}/thumbnails/{thumbnail_id}"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def setSpecificCollectionThumbnail(
|
||||
self, collection_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
url = (
|
||||
self.url + f"collections/{collection_id}/thumbnail/{thumbnail_id}/selected"
|
||||
)
|
||||
data = self.putRequest(url)
|
||||
return data
|
||||
530
src/komgapi/endpoints/series_controller.py
Normal file
530
src/komgapi/endpoints/series_controller.py
Normal file
@@ -0,0 +1,530 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class SeriesController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getAllSeries(
|
||||
self,
|
||||
library_id: List[str] = None,
|
||||
collection_id: List[str] = None,
|
||||
status: List[str] = None,
|
||||
publisher: List[str] = None,
|
||||
lang: List[str] = None,
|
||||
genre: List[str] = None,
|
||||
tag: List[str] = None,
|
||||
age_rating: List[str] = None,
|
||||
release_year: List[str] = None,
|
||||
deleted: bool = None,
|
||||
complete: bool = None,
|
||||
unpaged: bool = True,
|
||||
sort: List[str] = None,
|
||||
author: List[str] = None,
|
||||
oneshot: bool = None,
|
||||
size: int = None,
|
||||
page: int = None,
|
||||
) -> list[Series]:
|
||||
"""Get all series from the server.
|
||||
By default, this will return all series in the server. You can filter the results by using the parameters.
|
||||
|
||||
Args:
|
||||
----
|
||||
- library_id (List[str], optional): The library to be queried. If None, all available libraries will be used. Defaults to None.
|
||||
- collection_id (List[str], optional): The collection to be queried. Defaults to None.
|
||||
- status (List[str], optional): The status of the series. Can be: ENDED,ONGOING,ABANDONED,HIATUS. Defaults to None.
|
||||
- publisher (List[str], optional): Publisher(s) to be searched for. Defaults to None.
|
||||
- lang (List[str], optional): Language to query for. Uses two-letter codec. Defaults to None.
|
||||
- genre (List[str], optional): Genre(s) to query for. Defaults to None.
|
||||
- tag (List[str], optional): Tag(s) to query. Defaults to None.
|
||||
- age_rating (List[str], optional): A custom age-rating to search for. Needs to be configured manually. Defaults to None.
|
||||
- release_year (List[str], optional): When the series were released. Defaults to None.
|
||||
- deleted (bool, optional): If the series is deleted. Defaults to None.
|
||||
- complete (bool, optional): Turn to true to only search series that are complete. Complete requires TotalBookCount to be set and equal to BookCount. Defaults to None.
|
||||
- unpaged (bool, optional): Set to False if a single Page of results should be returned. By default, a page contains 20 entries. Defaults to True.
|
||||
- sort (List[str], optional): Sorting of the returned data. Sort using asc|desc. Multiple sort criteria are supported. Defaults to None.
|
||||
- author (List[str], optional): Author(s) to include in the query. Defaults to None.
|
||||
- oneshot (bool, optional): If the series is categorized as oneshot. Defaults to None.
|
||||
- size (int, optional): The size of the page. Defaults to None.
|
||||
- page (int, optional): The page to be returned. Defaults to None.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
- list[Series]: a list of all series that match the query. Each series is represented as a Series object.
|
||||
"""
|
||||
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "series"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for series in data["content"]:
|
||||
ret.append(Series(**series))
|
||||
return ret
|
||||
|
||||
def getSeries(self, series_id: str) -> Series:
|
||||
"""Get a single series from the server.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to get.
|
||||
|
||||
Returns:
|
||||
Series: the series that matches the ID wrapped in a Series class.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}"
|
||||
data = self.getRequest(url)
|
||||
return Series(**data)
|
||||
|
||||
def analyzeSeries(self, series_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Instruct the server to analyze a series. This will update the metadata of the series.
|
||||
|
||||
Args:
|
||||
series_id (str): the ID of the series to analyze.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/analyze"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def getSeriesBooks(
|
||||
self,
|
||||
series_id: str,
|
||||
media_status: List[str] = None,
|
||||
read_status: List[str] = None,
|
||||
tag: List[str] = None,
|
||||
deleted: bool = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
sort: List[str] = None,
|
||||
author: List[str] = None,
|
||||
) -> List[Book]:
|
||||
"""Get all books in a series.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to get books from.
|
||||
media_status (List[str], optional): The status of the media. Can be: UNKNOWN,ERROR,READY,UNSUPPORTED,OUTDATED. Defaults to None.
|
||||
read_status (List[str], optional): The read status of the book. Can be: UNREAD,READ,IN_PROGRESS. Defaults to None.
|
||||
tag (List[str], optional): Tag(s) to query. Defaults to None.
|
||||
deleted (bool, optional): If the series is deleted. Defaults to None.
|
||||
unpaged (bool, optional): Set to False if a single Page of results should be returned. By default, a page contains 20 entries. Defaults to True.
|
||||
page (int, optional): The page to be returned. Defaults to None.
|
||||
size (int, optional): The size of the page. Defaults to None.
|
||||
sort (List[str], optional): Sorting of the returned data. Sort using asc|desc. Multiple sort criteria are supported. Defaults to None.
|
||||
author (List[str], optional): Author(s) to include in the query. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[Book]: A list of all books that match the query. Each book is represented as a Book object.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/books"
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for book in data["content"]:
|
||||
ret.append(Book(**book))
|
||||
return ret
|
||||
|
||||
def getSeriesCollections(self, series_id: str) -> Optional[List[Collection]]:
|
||||
"""Get all collections assigned to a series.
|
||||
|
||||
Args:
|
||||
series_id (str): the ID of the series to get collections from.
|
||||
|
||||
Returns:
|
||||
Optional[List[Collection]]: A list of all collections that are assigned to the series. Each collection is represented as a Collection object. If no collections are assigned, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/collections"
|
||||
data = self.getRequest(url)
|
||||
if data is None:
|
||||
return None
|
||||
ret = []
|
||||
for collection in data:
|
||||
ret.append(Collection(**collection))
|
||||
return ret
|
||||
|
||||
def getFile(
|
||||
self, series_id: str, filename: str, download_path: str = "~/Downloads"
|
||||
) -> pathlib.Path:
|
||||
"""Download the complete series as a giant zip file.
|
||||
Unlike the other functions, this one has no timeout. It will wait until the file is downloaded.
|
||||
WARNING: THIS WILL TAKE A VERY LONG TIME FOR LARGE SERIES. USE WITH CAUTION.
|
||||
My test: 800mb took ~7min to download.
|
||||
Args:
|
||||
series_id (str): ID of the series to download the file from.
|
||||
filename (str): The name of the file to download.
|
||||
download_path (str, optional): The path to download the file to. Defaults to "~/Downloads".
|
||||
|
||||
Returns:
|
||||
pathlib.Path: The path to the downloaded file.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/file"
|
||||
download_path = pathlib.Path(download_path).expanduser()
|
||||
subprocess.run(
|
||||
[
|
||||
"curl",
|
||||
"-u",
|
||||
f"{self._username}:{self._password}",
|
||||
"-o",
|
||||
f"{download_path}/{filename}",
|
||||
url,
|
||||
]
|
||||
)
|
||||
return pathlib.Path(f"{download_path}/{filename}")
|
||||
|
||||
@typing_extensions.deprecated("This function is not implemented yet.")
|
||||
def deleteFile(self, series_id: str) -> None:
|
||||
"""Not implemented yet."""
|
||||
# url = self.url + f"series/{series_id}/file"
|
||||
# data = self.deleteRequest(url)
|
||||
raise NotImplementedError
|
||||
|
||||
def patchMetadata(self, series_id: str, changed_metadata: Dict[str, Any]) -> None:
|
||||
"""Change the metadata of a series.
|
||||
The changed metadata should be a dictionary with the changed fields. Only the fields that are changed need to be included.
|
||||
Args:
|
||||
series_id (str): The ID of the series to change.
|
||||
changed_metadata (Dict[str,Any]): The changed metadata. This should be a dictionary with the changed fields. Only the fields that are changed need to be included.
|
||||
|
||||
Returns:
|
||||
None
|
||||
Example for full metadata:
|
||||
|
||||
{
|
||||
"status": "ENDED",
|
||||
"statusLock": true,
|
||||
"title": "string",
|
||||
"titleLock": true,
|
||||
"titleSort": "string",
|
||||
"titleSortLock": true,
|
||||
"summary": "string",
|
||||
"summaryLock": true,
|
||||
"publisher": "string",
|
||||
"publisherLock": true,
|
||||
"readingDirectionLock": true,
|
||||
"ageRatingLock": true,
|
||||
"language": "string",
|
||||
"languageLock": true,
|
||||
"genresLock": true,
|
||||
"tagsLock": true,
|
||||
"totalBookCountLock": true,
|
||||
"sharingLabelsLock": true,
|
||||
"linksLock": true,
|
||||
"alternateTitlesLock": true,
|
||||
"tags": [
|
||||
"string"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"label": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"readingDirection": "LEFT_TO_RIGHT",
|
||||
"ageRating": 0,
|
||||
"genres": [
|
||||
"string"
|
||||
],
|
||||
"totalBookCount": 0,
|
||||
"sharingLabels": [
|
||||
"string"
|
||||
],
|
||||
"alternateTitles": [
|
||||
{
|
||||
"label": "string",
|
||||
"title": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/metadata"
|
||||
data = self.patchRequest(url, changed_metadata)
|
||||
return data
|
||||
|
||||
def refreshMetadata(self, series_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Refresh the metadata of a series.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to refresh.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/metadata/refresh"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def setReadProgress(self, series_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Mark all books in a series as read.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to get the read progress from.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/read-progress"
|
||||
data = self.postRequest(url)
|
||||
return data
|
||||
|
||||
def deleteReadProgress(self, series_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Delete the read progress of a series.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to delete the read progress from.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]] : If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/read-progress"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def getSeriesThumbnail(self, series_id: str) -> str:
|
||||
"""Get the current active thumbnail of a series.
|
||||
|
||||
Args:
|
||||
series_id (str): the ID of the series to get the thumbnail from.
|
||||
|
||||
Returns:
|
||||
str: the thumbnail of the series formatted as bytestring.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/thumbnail"
|
||||
data = requests.get(url, auth=(self._username, self._password))
|
||||
data = data.content
|
||||
return data
|
||||
|
||||
def getSeriesThumbnails(self, series_id: str) -> List[Thumbnail]:
|
||||
"""Get the metadata of all available thumbnails for the series.
|
||||
|
||||
Args:
|
||||
series_id (str): the ID of the series to get the thumbnails from.
|
||||
|
||||
Returns:
|
||||
List[Thumbnail]: A list of all possible thumbnails wrapped in a Thumbnail class.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/thumbnails"
|
||||
data = self.getRequest(url)
|
||||
ret = []
|
||||
for thumb in data:
|
||||
ret.append(Thumbnail(**thumb))
|
||||
return ret
|
||||
|
||||
def postThumbnail(
|
||||
self, series_id: str, thumbnail: bytes, selected: bool = None
|
||||
) -> Union[Thumbnail, Dict[str, Any]]:
|
||||
"""Set a new thumbnail for the series.
|
||||
|
||||
Args:
|
||||
thumbnail (bytes): The thumbnail to set. This should be a bytestring.
|
||||
selected (bool, optional): If the thumbnail should be set as the active thumbnail. Defaults to None.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/thumbnail"
|
||||
data = self.postRequest(url, {"selected": selected, "thumbnail": thumbnail})
|
||||
return Thumbnail(**data) if data else None
|
||||
|
||||
def getSpecificThumbnail(self, series_id: str, thumbnail_id: str) -> Thumbnail:
|
||||
"""Get a specific thumbnail of a series.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to get the thumbnail from.
|
||||
thumbnail_id (str): The ID of the thumbnail to get.
|
||||
|
||||
Returns:
|
||||
Thumbnail: The thumbnail wrapped in a Thumbnail class.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/thumbnails/{thumbnail_id}"
|
||||
data = self.getRequest(url)
|
||||
return Thumbnail(**data)
|
||||
|
||||
def deleteSpecificThumbnail(
|
||||
self, series_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Delete a specific thumbnail of a series.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to delete the thumbnail from.
|
||||
thumbnail_id (str): The ID of the thumbnail to delete.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str,Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/thumbnails/{thumbnail_id}"
|
||||
data = self.deleteRequest(url)
|
||||
return data
|
||||
|
||||
def setThumbnail(
|
||||
self, series_id: str, thumbnail_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Set a specific thumbnail as the active thumbnail of a series.
|
||||
|
||||
Args:
|
||||
series_id (str): The ID of the series to set the thumbnail to.
|
||||
thumbnail_id (str): The ID of the thumbnail to set.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: If a violation is found, the server will return a dictionary with the violation. If no violation is found, None is returned.
|
||||
"""
|
||||
url = self.url + f"series/{series_id}/thumbnail/{thumbnail_id}/selected"
|
||||
data = self.putRequest(url)
|
||||
return data
|
||||
|
||||
def getAlphabeticalGroups(
|
||||
self,
|
||||
search_string: str = None,
|
||||
library_id: List[str] = None,
|
||||
collection_id: List[str] = None,
|
||||
status: List[str] = None,
|
||||
read_status: List[str] = None,
|
||||
publisher: List[str] = None,
|
||||
language: List[str] = None,
|
||||
genre: List[str] = None,
|
||||
tag: List[str] = None,
|
||||
age_rating: List[str] = None,
|
||||
release_year: List[str] = None,
|
||||
sharing_label: List[str] = None,
|
||||
deleted: bool = None,
|
||||
complete: bool = None,
|
||||
oneshot: bool = None,
|
||||
search_regex: str = None,
|
||||
author: List[str] = None,
|
||||
) -> List[dict]:
|
||||
"""Get a list of all series sorted by the starting letter of the series name.
|
||||
|
||||
Args:
|
||||
search_string (str): The string to search for. Defaults to None.1
|
||||
library_id (List[str], optional): The library to search in. Defaults to None.
|
||||
collection_id (List[str], optional): The collection to search in. Defaults to None.
|
||||
status (List[str], optional): The status of the series. Available values : ENDED, ONGOING, ABANDONED, HIATUS. Defaults to None.
|
||||
read_status (List[str], optional): The read status of the series. Available values : UNREAD, READ, IN_PROGRESS. Defaults to None.
|
||||
publisher (List[str], optional): The publisher(s) to search for. Defaults to None.
|
||||
language (List[str], optional): The language(s) to search for. Defaults to None.
|
||||
genre (List[str], optional): The genre(s) to search for. Defaults to None.
|
||||
tag (List[str], optional): The tag(s) to search for. Defaults to None.
|
||||
age_rating (List[str], optional): The age rating to filter for. Needs to be set manually. Defaults to None.
|
||||
release_year (List[str], optional): The release year(s) to limit the search. Defaults to None.
|
||||
sharing_label (List[str], optional): The sharing label(s) to . Defaults to None.
|
||||
deleted (bool, optional): If deleted entries should be shown as well. Defaults to None.
|
||||
complete (bool, optional): If only complete entries should be shown. Defaults to None.
|
||||
oneshot (bool, optional): _description_. Defaults to None.
|
||||
search_regex (str, optional): _description_. Defaults to None.
|
||||
author (List[str], optional): _description_. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[dict]: _description_
|
||||
"""
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "series/alphabetical-groups"
|
||||
data = self.getRequest(url, params)
|
||||
return data
|
||||
|
||||
def getLatestSeries(
|
||||
self,
|
||||
library_id: List[str] = None,
|
||||
deleted: bool = None,
|
||||
oneshot: bool = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
) -> List[Series]:
|
||||
"""Get the latest changed series from the server.
|
||||
|
||||
Args:
|
||||
library_id (List[str], optional): Limits the request to the specified libraries. Defaults to None.
|
||||
deleted (bool, optional): Set to true to include the deleted series. Defaults to None.
|
||||
oneshot (bool, optional): Set to true to limit search to oneshot series only. Defaults to None.
|
||||
unpaged (bool, optional): Set to False to request the result in N pages. Defaults to True.
|
||||
page (int, optional): Used in conjunction with unpaged. Sets the requested page. Defaults to None.
|
||||
size (int, optional): Defines the size of the page. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[Series]: A list of all series that match the query. Each series is represented as a Series object.
|
||||
"""
|
||||
|
||||
url = self.url + "series/latest"
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for series in data["content"]:
|
||||
ret.append(Series(**series))
|
||||
return ret
|
||||
|
||||
def getNewSeries(
|
||||
self,
|
||||
library_id: List[str] = None,
|
||||
deleted: bool = None,
|
||||
oneshot: bool = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
) -> List[Series]:
|
||||
"""Get the newest series from the server.
|
||||
|
||||
Args:
|
||||
library_id (List[str], optional): Limits the request to the specified libraries. Defaults to None.
|
||||
deleted (bool, optional): Set to true to include the deleted series. Defaults to None.
|
||||
oneshot (bool, optional): Set to true to limit search to oneshot series only. Defaults to None.
|
||||
unpaged (bool, optional): Set to False to request the result in N pages. Defaults to True.
|
||||
page (int, optional): Used in conjunction with unpaged. Sets the requested page. Defaults to None.
|
||||
size (int, optional): Defines the size of the page. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[Series]: A list of all series that match the query. Each series is represented as a Series object.
|
||||
"""
|
||||
|
||||
url = self.url + "series/new"
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for series in data["content"]:
|
||||
ret.append(Series(**series))
|
||||
return ret
|
||||
|
||||
def getUpdatedSeries(
|
||||
self,
|
||||
library_id: List[str] = None,
|
||||
deleted: bool = None,
|
||||
oneshot: bool = None,
|
||||
unpaged: bool = True,
|
||||
page: int = None,
|
||||
size: int = None,
|
||||
) -> List["Series"]:
|
||||
"""Get the latest updated series from the server.
|
||||
|
||||
Args:
|
||||
library_id (List[str], optional): Limits the request to the specified libraries. Defaults to None.
|
||||
deleted (bool, optional): Set to true to include the deleted series. Defaults to None.
|
||||
oneshot (bool, optional): Set to true to limit search to oneshot series only. Defaults to None.
|
||||
unpaged (bool, optional): Set to False to request the result in N pages. Defaults to True.
|
||||
page (int, optional): Used in conjunction with unpaged. Sets the requested page. Defaults to None.
|
||||
size (int, optional): Defines the size of the page. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[Series]: A list of all series that match the query. Each series is represented as a Series object.
|
||||
"""
|
||||
|
||||
params = locals()
|
||||
params = self.setParams(params)
|
||||
url = self.url + "series/updated"
|
||||
data = self.getRequest(url, params)
|
||||
ret = []
|
||||
for series in data["content"]:
|
||||
ret.append(Series(**series))
|
||||
return ret
|
||||
22
src/komgapi/endpoints/settings_controller.py
Normal file
22
src/komgapi/endpoints/settings_controller.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class SettingsController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
super().__init__(username, password, url, timeout)
|
||||
|
||||
def getSettings(self) -> Settings:
|
||||
url = self.url + "settings"
|
||||
data = self.getRequest(url)
|
||||
return Settings(**data)
|
||||
|
||||
def updateSettings(self, settings: Settings) -> None:
|
||||
url = self.url + "settings"
|
||||
data = self.patchRequest(url, settings.model_dump())
|
||||
92
src/komgapi/endpoints/user_controller.py
Normal file
92
src/komgapi/endpoints/user_controller.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from .baseapi import BaseAPI
|
||||
import pathlib
|
||||
import subprocess
|
||||
import requests
|
||||
import typing_extensions
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from komgapi.errors import KomgaError, LoginError, ResultErrror
|
||||
from komgapi.schemas import * # Progress, Series
|
||||
|
||||
|
||||
class UserController(BaseAPI):
|
||||
def __init__(self, username, password, url, timeout=20, api_version="2") -> None:
|
||||
super().__init__(username, password, url, timeout, api_version="2")
|
||||
|
||||
def getUsers(self) -> List[User]:
|
||||
url = self.url + "users"
|
||||
data = self.getRequest(url)
|
||||
return [User(**user) for user in data]
|
||||
|
||||
def createUser(self, user: CreateUser) -> User:
|
||||
url = self.url + "users"
|
||||
data = self.postRequest(url, user.model_dump())
|
||||
return User(**data)
|
||||
|
||||
def deleteUser(self, user_id: str) -> None:
|
||||
url = self.url + f"users/{user_id}"
|
||||
self.deleteRequest(url)
|
||||
|
||||
def updateUser(self, user_id: str, user: User) -> None:
|
||||
userData = user.model_dump()
|
||||
# remove id, email and password
|
||||
userData.pop("id")
|
||||
userData.pop("email")
|
||||
userData.pop("password")
|
||||
url = self.url + f"users/{user_id}"
|
||||
data = self.patchRequest(url, user)
|
||||
|
||||
def getLatestAuthenticationActivity(self, user_id, api_key):
|
||||
raise NotImplementedError
|
||||
|
||||
def changePassword(self, user_id: str, password: str) -> None:
|
||||
url = self.url + f"users/{user_id}/password"
|
||||
self.patchRequest(url, {"password": password})
|
||||
|
||||
def getUserAuthenticationActivity(
|
||||
self,
|
||||
unpaged: bool = None,
|
||||
page: int = 0,
|
||||
size: int = 20,
|
||||
sort: List[str] = None,
|
||||
) -> Authentication:
|
||||
url = self.url + "users/authentification-activity"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
return Authentication(**data)
|
||||
|
||||
def getMe(self) -> User | Violation:
|
||||
url = self.url + "users/me"
|
||||
data = self.getRequest(url)
|
||||
return User(**data) if "id" in data else Violation(**data)
|
||||
|
||||
def getMyAPIKeys(self) -> List[APIKey] | Violation:
|
||||
url = self.url + "users/me/api-keys"
|
||||
data = self.getRequest(url)
|
||||
return [APIKey(**data) for key in data] if "id" in data else []
|
||||
|
||||
def createAPIKey(self, key: str) -> APIKey:
|
||||
url = self.url + "users/me/api-keys"
|
||||
data = self.postRequest(url, f'"comment":{key}')
|
||||
return APIKey(**data)
|
||||
|
||||
def deleteAPIKey(self, key_id: str) -> None:
|
||||
url = self.url + f"users/me/api-keys/{key_id}"
|
||||
self.deleteRequest(url)
|
||||
|
||||
def getMyAuthenticationActivity(
|
||||
self,
|
||||
unpaged: bool = None,
|
||||
page: int = 0,
|
||||
size: int = 20,
|
||||
sort: List[str] = None,
|
||||
) -> Authentication:
|
||||
raise NotImplementedError
|
||||
url = self.url + "users/me/authentication-activity"
|
||||
params = self.setParams(locals())
|
||||
data = self.getRequest(url, params)
|
||||
print(data)
|
||||
return UserAuthActivity(**data)
|
||||
|
||||
def changeMyPassword(self, password: str) -> None:
|
||||
url = self.url + "users/me/password"
|
||||
self.patchRequest(url, {"password": password})
|
||||
10
src/komgapi/errors.py
Normal file
10
src/komgapi/errors.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class KomgaError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LoginError(KomgaError):
|
||||
pass
|
||||
|
||||
|
||||
class ResultErrror(KomgaError):
|
||||
pass
|
||||
90
src/komgapi/komgapi.py
Normal file
90
src/komgapi/komgapi.py
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
"""Generic API for KOMGA"""
|
||||
|
||||
import requests
|
||||
from .endpoints import *
|
||||
|
||||
|
||||
class KOMGAPI_REST:
|
||||
"""The REST API interface for KOMGA. This class is used to interact with the KOMGA server. It provides methods to get series, books, and more.
|
||||
|
||||
|
||||
Args:
|
||||
----
|
||||
username (str): The username to use for the API.
|
||||
password (str): The password to use for the API.
|
||||
url (str): The URL of the KOMGA server. This should be the base URL of the server, without any paths.
|
||||
timeout (int): The timeout for the requests. Defaults to 20 seconds.
|
||||
|
||||
Example:
|
||||
-------
|
||||
data= KOMGAPI_REST('username', 'password', 'http://localhost:8080/')
|
||||
"""
|
||||
|
||||
def __init__(self, username, password, url, timeout=20) -> None:
|
||||
self._username = username
|
||||
self._password = password
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
if not url.endswith("/"):
|
||||
url += "/"
|
||||
self.common_book_controller = CommonBookController(username, password, url, timeout)
|
||||
self.series_controller = SeriesController(username, password, url, timeout)
|
||||
self.readlist_controller = ReadListController(username, password, url, timeout)
|
||||
self.page_hash_controller = None
|
||||
self.library_controller = LibraryController(username, password, url, timeout)
|
||||
self.series_collection_controller = SeriesCollectionController(username, password, url, timeout)
|
||||
self.book_controler = BookController(username, password, url, timeout)
|
||||
self.announcement_controller = AnnouncementController(username, password, url, timeout)
|
||||
self.user_controller = UserController(username, password, url, timeout)
|
||||
self.transient_books_controller = None
|
||||
self.file_system_controller = None
|
||||
self.claim_controller = ClaimController(username, password, url, timeout)
|
||||
self.settings_controller = SettingsController(username, password, url, timeout)
|
||||
self.referential_controller = ReferentialController(username, password, url, timeout)
|
||||
self.login_controller = None
|
||||
self.historical_events_controller = None
|
||||
self.task_controller = None
|
||||
self.sync_point_controller = None
|
||||
self.oauth2_controller = None
|
||||
|
||||
def notImplemented(self):
|
||||
raise NotImplementedError("Not implemented yet")
|
||||
|
||||
|
||||
def from_env(self):
|
||||
"""Create a KOMGA API object from environment variables.
|
||||
|
||||
Returns:
|
||||
KOMGAPI_REST: The KOMGA API object.
|
||||
"""
|
||||
import os
|
||||
|
||||
return self(
|
||||
os.environ["KOMGA_USERNAME"],
|
||||
os.environ["KOMGA_PASSWORD"],
|
||||
os.environ["KOMGA_URL"],
|
||||
)
|
||||
|
||||
def test_connection(self):
|
||||
"""Test the connection to the KOMGA server.
|
||||
|
||||
Returns:
|
||||
bool: True if the connection is successful, False otherwise.
|
||||
"""
|
||||
try:
|
||||
requests.get(self.url, auth=(self._username, self._password), timeout=self.timeout)
|
||||
return True
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Book controller
|
||||
|
||||
|
||||
|
||||
|
||||
0
src/komgapi/py.typed
Normal file
0
src/komgapi/py.typed
Normal file
10
src/komgapi/schemas/AlternateTitle.py
Normal file
10
src/komgapi/schemas/AlternateTitle.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AlternateTitle(BaseModel):
|
||||
label: str
|
||||
title: str
|
||||
26
src/komgapi/schemas/Announcement.py
Normal file
26
src/komgapi/schemas/Announcement.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Dict
|
||||
|
||||
from pydantic import BaseModel
|
||||
from komgapi.schemas.Author import Author
|
||||
|
||||
|
||||
class AnnouncementItem(BaseModel):
|
||||
id: str
|
||||
url: str
|
||||
title: str
|
||||
summary: str
|
||||
content_html: str
|
||||
date_modified: str
|
||||
author: Author
|
||||
|
||||
|
||||
class Announcement(BaseModel):
|
||||
version: str
|
||||
title: str
|
||||
home_page_url: str
|
||||
description: str
|
||||
items: List[AnnouncementItem]
|
||||
tags: List[str]
|
||||
_komga: Dict[str, str]
|
||||
86
src/komgapi/schemas/Authentication.py
Normal file
86
src/komgapi/schemas/Authentication.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class UserAccess(BaseModel):
|
||||
userId: str
|
||||
email: str
|
||||
apiKeyId: str
|
||||
apiKeyComment: str
|
||||
ip: str
|
||||
userAgent: str
|
||||
success: bool
|
||||
error: str
|
||||
dateTime: str
|
||||
source: str
|
||||
|
||||
|
||||
|
||||
class Sort(BaseModel):
|
||||
empty: bool
|
||||
sorted: bool
|
||||
unsorted: bool
|
||||
|
||||
class Pageable(BaseModel):
|
||||
offset: int
|
||||
sort: Sort
|
||||
pageNumber: int
|
||||
pageSize: int
|
||||
paged: bool
|
||||
unpaged: bool
|
||||
|
||||
|
||||
class Authentication(BaseModel):
|
||||
totalElements: int
|
||||
totalPages: int
|
||||
size: int
|
||||
content: List[UserAccess]
|
||||
number: int
|
||||
sort: Sort
|
||||
first: bool
|
||||
last: bool
|
||||
numberOfElements: int
|
||||
pageable: Pageable
|
||||
empty: bool
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Sort(BaseModel):
|
||||
empty: bool
|
||||
sorted: bool
|
||||
unsorted: bool
|
||||
|
||||
|
||||
class Sort1(BaseModel):
|
||||
empty: bool
|
||||
sorted: bool
|
||||
unsorted: bool
|
||||
|
||||
|
||||
class Pageable(BaseModel):
|
||||
offset: int
|
||||
sort: Sort1
|
||||
pageNumber: int
|
||||
pageSize: int
|
||||
paged: bool
|
||||
unpaged: bool
|
||||
|
||||
|
||||
class UserAuthActivity(BaseModel):
|
||||
totalElements: int
|
||||
totalPages: int
|
||||
size: int
|
||||
content: List[UserAccess]
|
||||
number: int
|
||||
sort: Sort
|
||||
first: bool
|
||||
last: bool
|
||||
numberOfElements: int
|
||||
pageable: Pageable
|
||||
empty: bool
|
||||
38
src/komgapi/schemas/Author.py
Normal file
38
src/komgapi/schemas/Author.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Author(BaseModel):
|
||||
name: str
|
||||
role: str
|
||||
|
||||
|
||||
class Sort(BaseModel):
|
||||
empty: bool
|
||||
sorted: bool
|
||||
unsorted: bool
|
||||
|
||||
class Pageable(BaseModel):
|
||||
offset: int
|
||||
sort: Sort
|
||||
pageNumber: int
|
||||
pageSize: int
|
||||
paged: bool
|
||||
unpaged: bool
|
||||
|
||||
|
||||
class AuthorResponse(BaseModel):
|
||||
totalElements: int
|
||||
totalPages: int
|
||||
size: int
|
||||
content: List[Author]
|
||||
number: int
|
||||
sort: Sort
|
||||
first: bool
|
||||
last: bool
|
||||
numberOfElements: int
|
||||
pageable: Pageable
|
||||
empty: bool
|
||||
31
src/komgapi/schemas/Book.py
Normal file
31
src/komgapi/schemas/Book.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .Author import Author
|
||||
from .BooksMetadata import BookMetadata
|
||||
from .Link import Link
|
||||
from .Media import Media
|
||||
|
||||
|
||||
class Book(BaseModel):
|
||||
id: Optional[str] = None
|
||||
seriesId: Optional[str] = None
|
||||
seriesTitle: Optional[str] = None
|
||||
libraryId: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
number: Optional[int] = None
|
||||
created: Optional[str] = None
|
||||
lastModified: Optional[str] = None
|
||||
fileLastModified: Optional[str] = None
|
||||
sizeBytes: Optional[int] = None
|
||||
size: Optional[str] = None
|
||||
media: Optional[Media] = None
|
||||
metadata: Optional[BookMetadata] = None
|
||||
readProgress: Optional[Any] = None
|
||||
deleted: Optional[bool] = None
|
||||
fileHash: Optional[str] = None
|
||||
oneshot: Optional[bool] = None
|
||||
41
src/komgapi/schemas/BooksMetadata.py
Normal file
41
src/komgapi/schemas/BooksMetadata.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .Author import Author
|
||||
from .Link import Link
|
||||
|
||||
|
||||
class BooksMetadata(BaseModel):
|
||||
authors: Optional[List[Author]] = None
|
||||
tags: Optional[List[str]] = None
|
||||
releaseDate: Optional[str] = None
|
||||
summary: Optional[str] = None
|
||||
summaryNumber: Optional[str] = None
|
||||
created: Optional[str] = None
|
||||
lastModified: Optional[str] = None
|
||||
|
||||
|
||||
class BookMetadata(BaseModel):
|
||||
title: Optional[str] = None
|
||||
titleLock: Optional[bool] = None
|
||||
summary: Optional[str] = None
|
||||
summaryLock: Optional[int] = None
|
||||
number: Optional[str] = None
|
||||
numberLock: Optional[bool] = None
|
||||
numberSort: Optional[float] = None
|
||||
numberSortLock: Optional[bool] = None
|
||||
releaseDate: Optional[str] = None
|
||||
releaseDateLock: Optional[bool] = None
|
||||
authors: List[Author]
|
||||
authorsLock: Optional[bool] = None
|
||||
tags: List
|
||||
tagsLock: Optional[bool] = None
|
||||
isbn: Optional[str] = None
|
||||
isbnLock: Optional[bool] = None
|
||||
links: List[Link]
|
||||
linksLock: Optional[bool] = None
|
||||
created: Optional[str] = None
|
||||
lastModified: Optional[str] = None
|
||||
15
src/komgapi/schemas/Collection.py
Normal file
15
src/komgapi/schemas/Collection.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Collection(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
ordered: bool
|
||||
seriesIds: List[str]
|
||||
createdDate: str
|
||||
lastModifiedDate: str
|
||||
filtered: bool
|
||||
19
src/komgapi/schemas/Duplicate.py
Normal file
19
src/komgapi/schemas/Duplicate.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
from .Sort import Sort
|
||||
from .Pageable import Pageable
|
||||
from .Book import Book
|
||||
|
||||
class Duplicate(BaseModel):
|
||||
totalElements: Optional[int] = None
|
||||
totalPages: Optional[int] = None
|
||||
size: Optional[int] = None
|
||||
content: Optional[List[Book]] = None
|
||||
number: Optional[int] = None
|
||||
sort: Optional[Sort] = None
|
||||
first: Optional[bool] = None
|
||||
last: Optional[bool] = None
|
||||
numberOfElements: Optional[int] = None
|
||||
pageable: Optional[Pageable] = None
|
||||
empty: Optional[bool] = None
|
||||
23
src/komgapi/schemas/Latest.py
Normal file
23
src/komgapi/schemas/Latest.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .Pageable import Pageable
|
||||
from .Series import Series
|
||||
from .Sort import Sort
|
||||
|
||||
|
||||
class LatestSeriesData(BaseModel):
|
||||
totalElements: Optional[int] = None
|
||||
totalPages: Optional[int] = None
|
||||
size: Optional[int] = None
|
||||
content: Optional[List[Series]] = None
|
||||
number: Optional[int] = None
|
||||
sort: Optional[Sort] = None
|
||||
first: Optional[bool] = None
|
||||
last: Optional[bool] = None
|
||||
numberOfElements: Optional[int] = None
|
||||
pageable: Optional[Pageable] = None
|
||||
empty: Optional[bool] = None
|
||||
67
src/komgapi/schemas/Library.py
Normal file
67
src/komgapi/schemas/Library.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Library(BaseModel):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
root: Optional[str] = None
|
||||
importComicInfoBook: Optional[bool] = None
|
||||
importComicInfoSeries: Optional[bool] = None
|
||||
importComicInfoCollection: Optional[bool] = None
|
||||
importComicInfoReadList: Optional[bool] = None
|
||||
importComicInfoSeriesAppendVolume: Optional[bool] = None
|
||||
importEpubBook: Optional[bool] = None
|
||||
importEpubSeries: Optional[bool] = None
|
||||
importMylarSeries: Optional[bool] = None
|
||||
importLocalArtwork: Optional[bool] = None
|
||||
importBarcodeIsbn: Optional[bool] = None
|
||||
scanForceModifiedTime: Optional[bool] = None
|
||||
scanInterval: Optional[str] = None
|
||||
scanOnStartup: Optional[bool] = None
|
||||
scanCbx: Optional[bool] = None
|
||||
scanPdf: Optional[bool] = None
|
||||
scanEpub: Optional[bool] = None
|
||||
scanDirectoryExclusions: Optional[List[str]] = None
|
||||
repairExtensions: Optional[bool] = None
|
||||
convertToCbz: Optional[bool] = None
|
||||
emptyTrashAfterScan: Optional[bool] = None
|
||||
seriesCover: Optional[str] = None
|
||||
hashFiles: Optional[bool] = None
|
||||
hashPages: Optional[bool] = None
|
||||
analyzeDimensions: Optional[bool] = None
|
||||
oneshotsDirectory: Optional[str] = None
|
||||
unavailable: Optional[bool] = None
|
||||
|
||||
class CreateLibrary(BaseModel):
|
||||
name: str
|
||||
root: str
|
||||
importComicInfoBook: bool
|
||||
importComicInfoSeries: bool
|
||||
importComicInfoCollection: bool
|
||||
importComicInfoReadList: bool
|
||||
importComicInfoSeriesAppendVolume: bool
|
||||
importEpubBook: bool
|
||||
importEpubSeries: bool
|
||||
importMylarSeries: bool
|
||||
importLocalArtwork: bool
|
||||
importBarcodeIsbn: bool
|
||||
scanForceModifiedTime: bool
|
||||
scanInterval: str
|
||||
scanOnStartup: bool
|
||||
scanCbx: bool
|
||||
scanPdf: bool
|
||||
scanEpub: bool
|
||||
scanDirectoryExclusions: List[str]
|
||||
repairExtensions: bool
|
||||
convertToCbz: bool
|
||||
emptyTrashAfterScan: bool
|
||||
seriesCover: str
|
||||
hashFiles: bool
|
||||
hashPages: bool
|
||||
analyzeDimensions: bool
|
||||
oneshotsDirectory: str
|
||||
|
||||
10
src/komgapi/schemas/Link.py
Normal file
10
src/komgapi/schemas/Link.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Link(BaseModel):
|
||||
label: str
|
||||
url: str
|
||||
10
src/komgapi/schemas/Locations.py
Normal file
10
src/komgapi/schemas/Locations.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Locations(BaseModel):
|
||||
fragments: Optional[List[str]] = None
|
||||
progression: Optional[int] = None
|
||||
position: Optional[int] = None
|
||||
totalProgression: Optional[int] = None
|
||||
|
||||
139
src/komgapi/schemas/Manifest.py
Normal file
139
src/komgapi/schemas/Manifest.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Link(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class Series(BaseModel):
|
||||
name: Optional[str] = None
|
||||
position: Optional[int] = None
|
||||
links: Optional[List[Link]] = None
|
||||
|
||||
|
||||
class CollectionItem(BaseModel):
|
||||
name: Optional[str] = None
|
||||
position: Optional[int] = None
|
||||
links: Optional[List[Link]] = None
|
||||
|
||||
|
||||
class BelongsTo(BaseModel):
|
||||
series: Optional[List[Series]] = None
|
||||
collection: Optional[List[CollectionItem]] = None
|
||||
|
||||
|
||||
class Rendition(BaseModel):
|
||||
additionalProp1: Dict[str, Any]
|
||||
additionalProp2: Dict[str, Any]
|
||||
additionalProp3: Dict[str, Any]
|
||||
|
||||
|
||||
class Metadata(BaseModel):
|
||||
title: Optional[str] = None
|
||||
identifier: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
conformsTo: Optional[str] = None
|
||||
sortAs: Optional[str] = None
|
||||
subtitle: Optional[str] = None
|
||||
modified: Optional[str] = None
|
||||
published: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
author: Optional[List[str]] = None
|
||||
translator: Optional[List[str]] = None
|
||||
editor: Optional[List[str]] = None
|
||||
artist: Optional[List[str]] = None
|
||||
illustrator: Optional[List[str]] = None
|
||||
letterer: Optional[List[str]] = None
|
||||
penciler: Optional[List[str]] = None
|
||||
colorist: Optional[List[str]] = None
|
||||
inker: Optional[List[str]] = None
|
||||
contributor: Optional[List[str]] = None
|
||||
publisher: Optional[List[str]] = None
|
||||
subject: Optional[List[str]] = None
|
||||
readingProgression: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
numberOfPages: Optional[int] = None
|
||||
belongsTo: Optional[BelongsTo] = None
|
||||
rendition: Optional[Rendition] = None
|
||||
|
||||
|
||||
class Image(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class ReadingOrderItem(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class Resource(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class TocItem(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class Landmark(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class PageListItem(BaseModel):
|
||||
title: Optional[str] = None
|
||||
rel: Optional[str] = None
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
templated: Optional[bool] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
|
||||
|
||||
class Manifest(BaseModel):
|
||||
context: Optional[str] = None
|
||||
metadata: Optional[Metadata] = None
|
||||
links: Optional[List[Link]] = None
|
||||
images: Optional[List[Image]] = None
|
||||
readingOrder: Optional[List[ReadingOrderItem]] = None
|
||||
resources: Optional[List[Resource]] = None
|
||||
toc: Optional[List[TocItem]] = None
|
||||
landmarks: Optional[List[Landmark]] = None
|
||||
pageList: Optional[List[PageListItem]] = None
|
||||
14
src/komgapi/schemas/Media.py
Normal file
14
src/komgapi/schemas/Media.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Media(BaseModel):
|
||||
status: str
|
||||
mediaType: str
|
||||
pagesCount: int
|
||||
comment: str
|
||||
epubDivinaCompatible: bool
|
||||
mediaProfile: str
|
||||
41
src/komgapi/schemas/Metadata.py
Normal file
41
src/komgapi/schemas/Metadata.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .AlternateTitle import AlternateTitle
|
||||
from .Link import Link
|
||||
|
||||
|
||||
class Metadata(BaseModel):
|
||||
status: Optional[str] | None = None
|
||||
statusLock: bool
|
||||
title: Optional[str] | None = None
|
||||
titleLock: bool
|
||||
titleSort: Optional[str] | None = None
|
||||
titleSortLock: bool
|
||||
summary: Optional[str] | None = None
|
||||
summaryLock: bool
|
||||
readingDirection: Optional[str] | None = None
|
||||
readingDirectionLock: bool
|
||||
publisher: Optional[str] | None = None
|
||||
publisherLock: bool
|
||||
ageRating: Optional[int] | None = None
|
||||
ageRatingLock: bool
|
||||
language: Optional[str] | None = None
|
||||
languageLock: bool
|
||||
genres: List[str] | None
|
||||
genresLock: bool
|
||||
tags: List[str] | None = None
|
||||
tagsLock: bool
|
||||
totalBookCount: Optional[int] | None = None
|
||||
totalBookCountLock: bool
|
||||
sharingLabels: List[str | None] = None
|
||||
sharingLabelsLock: bool
|
||||
links: List[Link] | None = None
|
||||
linksLock: bool
|
||||
alternateTitles: List[AlternateTitle] | None = None
|
||||
alternateTitlesLock: bool
|
||||
created: Optional[str] | None = None
|
||||
lastModified: Optional[str] | None = None
|
||||
15
src/komgapi/schemas/Page.py
Normal file
15
src/komgapi/schemas/Page.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Page(BaseModel):
|
||||
number: Optional[int] = None
|
||||
fileName: Optional[str] = None
|
||||
mediaType: Optional[str] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
sizeBytes: Optional[int] = None
|
||||
size: Optional[str] = None
|
||||
16
src/komgapi/schemas/Pageable.py
Normal file
16
src/komgapi/schemas/Pageable.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .Sort import Sort
|
||||
|
||||
|
||||
class Pageable(BaseModel):
|
||||
offset: int
|
||||
sort: Sort
|
||||
pageNumber: int
|
||||
pageSize: int
|
||||
paged: bool
|
||||
unpaged: bool
|
||||
22
src/komgapi/schemas/Position.py
Normal file
22
src/komgapi/schemas/Position.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from .Text import Text
|
||||
from .Locations import Locations
|
||||
|
||||
|
||||
|
||||
|
||||
class Position(BaseModel):
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
locations: Optional[Locations] = None
|
||||
text: Optional[Text] = None
|
||||
|
||||
|
||||
class Position(BaseModel):
|
||||
total: Optional[int] = None
|
||||
positions: Optional[List[Position]] = None
|
||||
26
src/komgapi/schemas/Progress.py
Normal file
26
src/komgapi/schemas/Progress.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from .Text import Text
|
||||
from .Locations import Locations
|
||||
|
||||
|
||||
class Device(BaseModel):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class Locator(BaseModel):
|
||||
href: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
locations: Optional[Locations] = None
|
||||
text: Optional[Text] = None
|
||||
|
||||
|
||||
class Progress(BaseModel):
|
||||
modified: Optional[str] = None
|
||||
device: Optional[Device] = None
|
||||
locator: Optional[Locator] = None
|
||||
12
src/komgapi/schemas/Readlist.py
Normal file
12
src/komgapi/schemas/Readlist.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Readlist(BaseModel):
|
||||
name: str
|
||||
summary: str
|
||||
ordered: bool
|
||||
bookIds: List[str]
|
||||
28
src/komgapi/schemas/Series.py
Normal file
28
src/komgapi/schemas/Series.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .AlternateTitle import AlternateTitle
|
||||
from .BooksMetadata import BooksMetadata
|
||||
from .Link import Link
|
||||
from .Metadata import Metadata
|
||||
|
||||
|
||||
class Series(BaseModel):
|
||||
id: Optional[str] = None
|
||||
libraryId: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
created: Optional[str] = None
|
||||
lastModified: Optional[str] = None
|
||||
fileLastModified: Optional[str] = None
|
||||
booksCount: Optional[int] = None
|
||||
booksReadCount: Optional[int] = None
|
||||
booksUnreadCount: Optional[int] = None
|
||||
booksInProgressCount: Optional[int] = None
|
||||
metadata: Optional[Metadata] = None
|
||||
booksMetadata: Optional[BooksMetadata] = None
|
||||
deleted: Optional[bool] = None
|
||||
oneshot: Optional[bool] = None
|
||||
11
src/komgapi/schemas/Sort.py
Normal file
11
src/komgapi/schemas/Sort.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Sort(BaseModel):
|
||||
empty: bool
|
||||
sorted: bool
|
||||
unsorted: bool
|
||||
8
src/komgapi/schemas/Text.py
Normal file
8
src/komgapi/schemas/Text.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Text(BaseModel):
|
||||
after: Optional[str] = None
|
||||
before: Optional[str] = None
|
||||
highlight: Optional[str] = None
|
||||
27
src/komgapi/schemas/Thumbnail.py
Normal file
27
src/komgapi/schemas/Thumbnail.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Thumbnail(BaseModel):
|
||||
id: str
|
||||
seriesId: str
|
||||
type: str
|
||||
selected: bool
|
||||
mediaType: str
|
||||
fileSize: int
|
||||
width: int
|
||||
height: int
|
||||
|
||||
|
||||
class ReadlistThumbnail(BaseModel):
|
||||
id: str
|
||||
readlistId: str
|
||||
type: str
|
||||
selected: bool
|
||||
mediaType: str
|
||||
fileSize: int
|
||||
width: int
|
||||
height: int
|
||||
26
src/komgapi/schemas/User.py
Normal file
26
src/komgapi/schemas/User.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Dict
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
class User(BaseModel):
|
||||
"""Object representing a User
|
||||
|
||||
Args:
|
||||
BaseModel (Model): Pydantic BaseModel
|
||||
"""
|
||||
id:str
|
||||
email:str
|
||||
roles:List[str]
|
||||
sharedAllLibraries:bool
|
||||
sharedLibrariesIds:List[str]
|
||||
labelsAllow:List[str]
|
||||
labelsExclude:List[str]
|
||||
ageRestriction:Dict[str, int|str] | None
|
||||
|
||||
|
||||
class CreateUser(BaseModel):
|
||||
email:str
|
||||
password:str
|
||||
roles:List[str]
|
||||
30
src/komgapi/schemas/__init__.py
Normal file
30
src/komgapi/schemas/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from .AlternateTitle import AlternateTitle
|
||||
from .Author import Author, AuthorResponse
|
||||
from .Book import Book
|
||||
from .BooksMetadata import BooksMetadata, BookMetadata
|
||||
from .Collection import Collection
|
||||
from .Duplicate import Duplicate
|
||||
from .Latest import LatestSeriesData
|
||||
from .Library import Library, CreateLibrary
|
||||
from .Link import Link
|
||||
from .Locations import Locations
|
||||
from .Manifest import Manifest
|
||||
from .Media import Media
|
||||
from .Metadata import Metadata
|
||||
from .Page import Page
|
||||
from .Pageable import Pageable
|
||||
from .Position import Position
|
||||
from .Progress import Progress
|
||||
from .Readlist import Readlist
|
||||
from .Series import Series
|
||||
from .Sort import Sort
|
||||
from .Text import Text
|
||||
from .Thumbnail import Thumbnail, ReadlistThumbnail
|
||||
from .Announcement import Announcement
|
||||
from .User import User, CreateUser
|
||||
from .Authentication import Authentication, UserAuthActivity
|
||||
from .apikey import APIKey
|
||||
from .settings import Settings
|
||||
|
||||
|
||||
from .violation import Violation
|
||||
22
src/komgapi/schemas/apiSearch.py
Normal file
22
src/komgapi/schemas/apiSearch.py
Normal file
@@ -0,0 +1,22 @@
|
||||
ALLSERIESPARAMS = {
|
||||
"search": str | None,
|
||||
"library_id": list[str] | None,
|
||||
"collection_id": list[str] | None,
|
||||
"status": list[str] | None,
|
||||
"read_status": list[str] | None,
|
||||
"publisher": list[str] | None,
|
||||
"genre": list[str] | None,
|
||||
"language": list[str] | None,
|
||||
"tag": list[str] | None,
|
||||
"age_rating": list[str] | None,
|
||||
"sharing_label": list[str] | None,
|
||||
"deleted": bool | None,
|
||||
"complete": bool | None,
|
||||
"oneshot": bool | None,
|
||||
"unpaged": bool | None,
|
||||
"search_regex": str | None,
|
||||
"page": int | None,
|
||||
"size": int | None,
|
||||
"sort": list[str] | None,
|
||||
"authors": list[str] | None,
|
||||
}
|
||||
12
src/komgapi/schemas/apikey.py
Normal file
12
src/komgapi/schemas/apikey.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class APIKey(BaseModel):
|
||||
id: str
|
||||
userId: str
|
||||
key: str
|
||||
comment: str
|
||||
createdDate: str
|
||||
lastModifiedDate: str
|
||||
34
src/komgapi/schemas/settings.py
Normal file
34
src/komgapi/schemas/settings.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ServerPort(BaseModel):
|
||||
configurationSource: int
|
||||
databaseSource: int
|
||||
effectiveValue: int
|
||||
|
||||
|
||||
class ServerContextPath(BaseModel):
|
||||
configurationSource: str
|
||||
databaseSource: str
|
||||
effectiveValue: str
|
||||
|
||||
|
||||
class KepubifyPath(BaseModel):
|
||||
configurationSource: str
|
||||
databaseSource: str
|
||||
effectiveValue: str
|
||||
|
||||
|
||||
class Settings(BaseModel):
|
||||
deleteEmptyCollections: bool
|
||||
deleteEmptyReadLists: bool
|
||||
rememberMeDurationDays: int
|
||||
thumbnailSize: str
|
||||
taskPoolSize: int
|
||||
serverPort: ServerPort
|
||||
serverContextPath: ServerContextPath
|
||||
koboProxy: bool
|
||||
koboPort: int
|
||||
kepubifyPath: KepubifyPath
|
||||
11
src/komgapi/schemas/violation.py
Normal file
11
src/komgapi/schemas/violation.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Violation(BaseModel):
|
||||
fieldName: str
|
||||
message: str
|
||||
|
||||
|
||||
Reference in New Issue
Block a user