DengFengLai's picture
DF.
0a1b571
raw
history blame
5.72 kB
import hashlib
import hmac
from datetime import timedelta
from enum import Enum
from time import time
from typing import Any, Optional, cast
from httpx import URL
from hibiapi.api.bika.constants import BikaConstants
from hibiapi.api.bika.net import NetRequest
from hibiapi.utils.cache import cache_config
from hibiapi.utils.decorators import enum_auto_doc
from hibiapi.utils.net import catch_network_error
from hibiapi.utils.routing import BaseEndpoint, dont_route, request_headers
@enum_auto_doc
class ImageQuality(str, Enum):
"""哔咔API返回的图片质量"""
low = "low"
"""低质量"""
medium = "medium"
"""中等质量"""
high = "high"
"""高质量"""
original = "original"
"""原图"""
@enum_auto_doc
class ResultSort(str, Enum):
"""哔咔API返回的搜索结果排序方式"""
date_descending = "dd"
"""最新发布"""
date_ascending = "da"
"""最早发布"""
like_descending = "ld"
"""最多喜欢"""
views_descending = "vd"
"""最多浏览"""
class BikaEndpoints(BaseEndpoint):
@staticmethod
def _sign(url: URL, timestamp_bytes: bytes, nonce: bytes, method: bytes):
return hmac.new(
BikaConstants.DIGEST_KEY,
(
url.raw_path.lstrip(b"/")
+ timestamp_bytes
+ nonce
+ method
+ BikaConstants.API_KEY
).lower(),
hashlib.sha256,
).hexdigest()
@dont_route
@catch_network_error
async def request(
self,
endpoint: str,
*,
params: Optional[dict[str, Any]] = None,
body: Optional[dict[str, Any]] = None,
no_token: bool = False,
):
net_client = cast(NetRequest, self.client.net_client)
if not no_token:
async with net_client.auth_lock:
if net_client.token is None:
await net_client.login(self)
headers = {
"Authorization": net_client.token or "",
"Time": (current_time := f"{time():.0f}".encode()),
"Image-Quality": request_headers.get().get(
"X-Image-Quality", ImageQuality.medium
),
"Nonce": (nonce := hashlib.md5(current_time).hexdigest().encode()),
"Signature": self._sign(
request_url := self._join(
base=BikaConstants.API_HOST,
endpoint=endpoint,
params=params or {},
),
current_time,
nonce,
b"GET" if body is None else b"POST",
),
}
response = await (
self.client.get(request_url, headers=headers)
if body is None
else self.client.post(request_url, headers=headers, json=body)
)
return response.json()
@cache_config(ttl=timedelta(days=1))
async def collections(self):
return await self.request("collections")
@cache_config(ttl=timedelta(days=3))
async def categories(self):
return await self.request("categories")
@cache_config(ttl=timedelta(days=3))
async def keywords(self):
return await self.request("keywords")
async def advanced_search(
self,
*,
keyword: str,
page: int = 1,
sort: ResultSort = ResultSort.date_descending,
):
return await self.request(
"comics/advanced-search",
body={
"keyword": keyword,
"sort": sort,
},
params={
"page": page,
"s": sort,
},
)
async def category_list(
self,
*,
category: str,
page: int = 1,
sort: ResultSort = ResultSort.date_descending,
):
return await self.request(
"comics",
params={
"page": page,
"c": category,
"s": sort,
},
)
async def author_list(
self,
*,
author: str,
page: int = 1,
sort: ResultSort = ResultSort.date_descending,
):
return await self.request(
"comics",
params={
"page": page,
"a": author,
"s": sort,
},
)
@cache_config(ttl=timedelta(days=3))
async def comic_detail(self, *, id: str):
return await self.request("comics/{id}", params={"id": id})
async def comic_recommendation(self, *, id: str):
return await self.request("comics/{id}/recommendation", params={"id": id})
async def comic_episodes(self, *, id: str, page: int = 1):
return await self.request(
"comics/{id}/eps",
params={
"id": id,
"page": page,
},
)
async def comic_page(self, *, id: str, order: int = 1, page: int = 1):
return await self.request(
"comics/{id}/order/{order}/pages",
params={
"id": id,
"order": order,
"page": page,
},
)
async def comic_comments(self, *, id: str, page: int = 1):
return await self.request(
"comics/{id}/comments",
params={
"id": id,
"page": page,
},
)
async def games(self, *, page: int = 1):
return await self.request("games", params={"page": page})
@cache_config(ttl=timedelta(days=3))
async def game_detail(self, *, id: str):
return await self.request("games/{id}", params={"id": id})