DengFengLai's picture
DF.
0a1b571
import hashlib
from enum import Enum
from random import randint
from typing import Any, Optional
from hibiapi.utils.config import APIConfig
from hibiapi.utils.net import catch_network_error
from hibiapi.utils.routing import BaseEndpoint, dont_route
Config = APIConfig("tieba")
class TiebaSignUtils:
salt = b"tiebaclient!!!"
@staticmethod
def random_digit(length: int) -> str:
return "".join(map(str, [randint(0, 9) for _ in range(length)]))
@staticmethod
def construct_content(params: dict[str, Any]) -> bytes:
# NOTE: this function used to construct form content WITHOUT urlencode
# Don't ask me why this is necessary, ask Tieba's programmers instead
return b"&".join(
map(
lambda k, v: (
k.encode()
+ b"="
+ str(v.value if isinstance(v, Enum) else v).encode()
),
params.keys(),
params.values(),
)
)
@classmethod
def sign(cls, params: dict[str, Any]) -> bytes:
params.update(
{
"_client_id": (
"wappc_" + cls.random_digit(13) + "_" + cls.random_digit(3)
),
"_client_type": 2,
"_client_version": "9.9.8.32",
**{
k.upper(): str(v).strip()
for k, v in Config["net"]["params"].as_dict().items()
if v
},
}
)
params = {k: params[k] for k in sorted(params.keys())}
params["sign"] = (
hashlib.md5(cls.construct_content(params).replace(b"&", b"") + cls.salt)
.hexdigest()
.upper()
)
return cls.construct_content(params)
class TiebaEndpoint(BaseEndpoint):
base = "http://c.tieba.baidu.com"
@dont_route
@catch_network_error
async def request(
self, endpoint: str, *, params: Optional[dict[str, Any]] = None
) -> dict[str, Any]:
response = await self.client.post(
url=self._join(self.base, endpoint, {}),
content=TiebaSignUtils.sign(params or {}),
)
response.raise_for_status()
return response.json()
async def post_list(self, *, name: str, page: int = 1, size: int = 50):
return await self.request(
"c/f/frs/page",
params={
"kw": name,
"pn": page,
"rn": size,
},
)
async def post_detail(
self,
*,
tid: int,
page: int = 1,
size: int = 50,
reversed: bool = False,
):
return await self.request(
"c/f/pb/page",
params={
**({"last": 1, "r": 1} if reversed else {}),
"kz": tid,
"pn": page,
"rn": size,
},
)
async def subpost_detail(
self,
*,
tid: int,
pid: int,
page: int = 1,
size: int = 50,
):
return await self.request(
"c/f/pb/floor",
params={
"kz": tid,
"pid": pid,
"pn": page,
"rn": size,
},
)
async def user_profile(self, *, uid: int):
return await self.request(
"c/u/user/profile",
params={
"uid": uid,
"need_post_count": 1,
"has_plist": 1,
},
)
async def user_subscribed(
self, *, uid: int, page: int = 1
): # XXX This API required user login!
return await self.request(
"c/f/forum/like",
params={
"is_guest": 0,
"uid": uid,
"page_no": page,
},
)