tiebameow 0.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tiebameow/__init__.py +0 -0
- tiebameow/client/__init__.py +4 -0
- tiebameow/client/http_client.py +103 -0
- tiebameow/client/tieba_client.py +517 -0
- tiebameow/models/__init__.py +0 -0
- tiebameow/models/dto.py +391 -0
- tiebameow/models/orm.py +572 -0
- tiebameow/parser/__init__.py +45 -0
- tiebameow/parser/parser.py +362 -0
- tiebameow/parser/rule_parser.py +990 -0
- tiebameow/py.typed +0 -0
- tiebameow/renderer/__init__.py +5 -0
- tiebameow/renderer/config.py +18 -0
- tiebameow/renderer/playwright_core.py +148 -0
- tiebameow/renderer/renderer.py +508 -0
- tiebameow/renderer/static/fonts/NotoSansSC-Regular.woff2 +0 -0
- tiebameow/renderer/style.py +32 -0
- tiebameow/renderer/templates/base.html +270 -0
- tiebameow/renderer/templates/macros.html +100 -0
- tiebameow/renderer/templates/text.html +99 -0
- tiebameow/renderer/templates/text_simple.html +79 -0
- tiebameow/renderer/templates/thread.html +8 -0
- tiebameow/renderer/templates/thread_detail.html +18 -0
- tiebameow/renderer/templates/thread_info.html +35 -0
- tiebameow/schemas/__init__.py +0 -0
- tiebameow/schemas/fragments.py +188 -0
- tiebameow/schemas/rules.py +247 -0
- tiebameow/serializer/__init__.py +15 -0
- tiebameow/serializer/serializer.py +115 -0
- tiebameow/utils/__init__.py +0 -0
- tiebameow/utils/logger.py +129 -0
- tiebameow/utils/time_utils.py +15 -0
- tiebameow-0.2.8.dist-info/METADATA +142 -0
- tiebameow-0.2.8.dist-info/RECORD +36 -0
- tiebameow-0.2.8.dist-info/WHEEL +4 -0
- tiebameow-0.2.8.dist-info/licenses/LICENSE +21 -0
tiebameow/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import ssl
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential_jitter
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import AsyncGenerator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HTTPXClient:
|
|
16
|
+
_client: httpx.AsyncClient | None = None
|
|
17
|
+
_context: ssl.SSLContext | None = None
|
|
18
|
+
_lock: asyncio.Lock | None = None
|
|
19
|
+
|
|
20
|
+
DEFAULT_TIMEOUT: float = 10.0
|
|
21
|
+
|
|
22
|
+
DEFAULT_STOP = stop_after_attempt(3)
|
|
23
|
+
DEFAULT_WAIT = wait_exponential_jitter(initial=0.5, max=3.0)
|
|
24
|
+
DEFAULT_RETRY_CONDITION = retry_if_exception_type((
|
|
25
|
+
httpx.ConnectTimeout,
|
|
26
|
+
httpx.ReadTimeout,
|
|
27
|
+
httpx.RemoteProtocolError,
|
|
28
|
+
httpx.NetworkError,
|
|
29
|
+
))
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def _get_lock(cls) -> asyncio.Lock:
|
|
33
|
+
if cls._lock is None:
|
|
34
|
+
cls._lock = asyncio.Lock()
|
|
35
|
+
return cls._lock
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
@asynccontextmanager
|
|
39
|
+
async def get_client(cls) -> AsyncGenerator[httpx.AsyncClient, None]:
|
|
40
|
+
if cls._client is None or cls._client.is_closed:
|
|
41
|
+
async with cls._get_lock():
|
|
42
|
+
if cls._client is None or cls._client.is_closed:
|
|
43
|
+
if cls._context is None:
|
|
44
|
+
cls._context = ssl.create_default_context()
|
|
45
|
+
cls._context.set_ciphers("DEFAULT")
|
|
46
|
+
cls._client = httpx.AsyncClient(
|
|
47
|
+
timeout=cls.DEFAULT_TIMEOUT,
|
|
48
|
+
verify=cls._context,
|
|
49
|
+
)
|
|
50
|
+
yield cls._client
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
async def close(cls) -> None:
|
|
54
|
+
if cls._client and not cls._client.is_closed:
|
|
55
|
+
await cls._client.aclose()
|
|
56
|
+
cls._client = None
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def configure_defaults(cls, timeout: float = DEFAULT_TIMEOUT, retry_config: dict[str, Any] | None = None) -> None:
|
|
60
|
+
"""
|
|
61
|
+
配置默认的超时和重试策略。
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
timeout: 默认的请求超时时间(秒)。
|
|
65
|
+
retry_config: 重试配置字典,包含 "stop", "wait", "retry" 键。
|
|
66
|
+
"""
|
|
67
|
+
cls.DEFAULT_TIMEOUT = timeout
|
|
68
|
+
|
|
69
|
+
if retry_config:
|
|
70
|
+
if "stop" in retry_config:
|
|
71
|
+
cls.DEFAULT_STOP = retry_config["stop"]
|
|
72
|
+
if "wait" in retry_config:
|
|
73
|
+
cls.DEFAULT_WAIT = retry_config["wait"]
|
|
74
|
+
if "retry" in retry_config:
|
|
75
|
+
cls.DEFAULT_RETRY_CONDITION = retry_config["retry"]
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
async def get(cls, url: str, **kwargs: Any) -> httpx.Response | None:
|
|
79
|
+
@retry(stop=cls.DEFAULT_STOP, wait=cls.DEFAULT_WAIT, retry=cls.DEFAULT_RETRY_CONDITION)
|
|
80
|
+
async def _get() -> httpx.Response:
|
|
81
|
+
async with cls.get_client() as client:
|
|
82
|
+
response = await client.get(url, **kwargs)
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
return response
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
return await _get()
|
|
88
|
+
except httpx.HTTPStatusError:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
async def post(cls, url: str, json: dict[str, Any] | None = None, **kwargs: Any) -> httpx.Response | None:
|
|
93
|
+
@retry(stop=cls.DEFAULT_STOP, wait=cls.DEFAULT_WAIT, retry=cls.DEFAULT_RETRY_CONDITION)
|
|
94
|
+
async def _post() -> httpx.Response:
|
|
95
|
+
async with cls.get_client() as client:
|
|
96
|
+
response = await client.post(url, json=json, **kwargs)
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
return response
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
return await _post()
|
|
102
|
+
except Exception:
|
|
103
|
+
return None
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
import aiotieba as tb
|
|
11
|
+
from aiohttp import ClientError, ServerConnectionError, ServerTimeoutError
|
|
12
|
+
from aiotieba.exception import BoolResponse, HTTPStatusError, IntResponse, StrResponse, TiebaServerError
|
|
13
|
+
from tenacity import (
|
|
14
|
+
AsyncRetrying,
|
|
15
|
+
retry_if_exception_type,
|
|
16
|
+
stop_after_attempt,
|
|
17
|
+
wait_exponential_jitter,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from ..parser import (
|
|
21
|
+
convert_aiotieba_comments,
|
|
22
|
+
convert_aiotieba_posts,
|
|
23
|
+
convert_aiotieba_threads,
|
|
24
|
+
convert_aiotieba_userinfo,
|
|
25
|
+
)
|
|
26
|
+
from ..utils.logger import logger
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import AsyncGenerator
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
|
|
32
|
+
from aiolimiter import AsyncLimiter
|
|
33
|
+
from aiotieba.api.get_bawu_blacklist._classdef import BawuBlacklistUsers
|
|
34
|
+
from aiotieba.api.get_bawu_postlogs._classdef import Postlogs
|
|
35
|
+
from aiotieba.api.get_bawu_userlogs._classdef import Userlogs
|
|
36
|
+
from aiotieba.api.get_follow_forums._classdef import FollowForums
|
|
37
|
+
from aiotieba.api.get_tab_map._classdef import TabMap
|
|
38
|
+
from aiotieba.api.get_user_contents._classdef import UserPostss, UserThreads
|
|
39
|
+
from aiotieba.api.tieba_uid2user_info._classdef import UserInfo_TUid
|
|
40
|
+
from aiotieba.typing import Comments, Posts, Threads, UserInfo
|
|
41
|
+
|
|
42
|
+
from ..models.dto import CommentsDTO, PostsDTO, ThreadsDTO, UserInfoDTO
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AiotiebaError(Exception):
|
|
46
|
+
"""基础 aiotieba API 异常"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, code: int, msg: str):
|
|
49
|
+
self.code = code
|
|
50
|
+
self.msg = msg
|
|
51
|
+
super().__init__(f"[{code}] {msg}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RetriableApiError(AiotiebaError):
|
|
55
|
+
"""aiotieba 返回的可重试的异常"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UnretriableApiError(AiotiebaError):
|
|
59
|
+
"""aiotieba 返回的无法重试的异常"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ErrorHandler:
|
|
63
|
+
RETRIABLE_CODES: set[int] = {
|
|
64
|
+
-65536, # 超时
|
|
65
|
+
11, # 系统繁忙
|
|
66
|
+
77, # 操作失败
|
|
67
|
+
408, # 请求超时
|
|
68
|
+
429, # 过多请求
|
|
69
|
+
4011, # 需要验证码
|
|
70
|
+
110001, # 未知错误
|
|
71
|
+
110004, # tieba_uid2user_info 接口错误
|
|
72
|
+
220034, # 操作过于频繁
|
|
73
|
+
230871, # 发贴/删贴过于频繁
|
|
74
|
+
300000, # 旧版客户端API无法封禁用户名为空用户
|
|
75
|
+
1989005, # 加载数据失败
|
|
76
|
+
2210002, # 系统错误
|
|
77
|
+
28113295,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def check(cls, result: Any) -> None:
|
|
82
|
+
"""解析 aiotieba 返回对象中的 err 字段"""
|
|
83
|
+
err = getattr(result, "err", None)
|
|
84
|
+
if err is None:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
if isinstance(err, (HTTPStatusError, TiebaServerError)):
|
|
88
|
+
code = err.code
|
|
89
|
+
msg = err.msg
|
|
90
|
+
|
|
91
|
+
if code in cls.RETRIABLE_CODES:
|
|
92
|
+
raise RetriableApiError(code, msg)
|
|
93
|
+
|
|
94
|
+
raise UnretriableApiError(code, msg)
|
|
95
|
+
|
|
96
|
+
elif isinstance(err, Exception):
|
|
97
|
+
raise err
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def with_ensure[F: Callable[..., Awaitable[Any]]](func: F) -> F:
|
|
101
|
+
"""装饰器:为 aiotieba.Client 的方法添加重试和限流支持。"""
|
|
102
|
+
|
|
103
|
+
@wraps(func)
|
|
104
|
+
async def wrapper(self: Client, *args: Any, **kwargs: Any) -> Any:
|
|
105
|
+
return await self._request_core(func, *args, **kwargs)
|
|
106
|
+
|
|
107
|
+
return wrapper # type: ignore[return-value]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Client(tb.Client): # type: ignore[misc]
|
|
111
|
+
"""扩展的aiotieba客户端,添加了自定义的请求限流和并发控制功能。
|
|
112
|
+
|
|
113
|
+
该客户端继承自aiotieba.Client,并在其基础上实现了速率限制和并发控制。
|
|
114
|
+
通过装饰器和上下文管理器的方式,为所有API调用提供统一的速率限制和并发控制。
|
|
115
|
+
同时还添加了对特定错误码的重试机制,以提高请求的成功率。
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
*args: Any,
|
|
121
|
+
limiter: AsyncLimiter | None = None,
|
|
122
|
+
semaphore: asyncio.Semaphore | None = None,
|
|
123
|
+
cooldown_429: float = 0.0,
|
|
124
|
+
retry_attempts: int = 3,
|
|
125
|
+
wait_initial: float = 0.5,
|
|
126
|
+
wait_max: float = 5.0,
|
|
127
|
+
**kwargs: Any,
|
|
128
|
+
):
|
|
129
|
+
"""初始化扩展的aiotieba客户端。
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
*args: 传递给父类构造函数的参数。
|
|
133
|
+
limiter: 速率限制器,用于控制每秒请求数。
|
|
134
|
+
semaphore: 信号量,用于控制最大并发数。
|
|
135
|
+
cooldown_seconds: 触发429时的全局冷却秒数。
|
|
136
|
+
**kwargs: 传递给父类构造函数的关键字参数。
|
|
137
|
+
"""
|
|
138
|
+
super().__init__(*args, **kwargs)
|
|
139
|
+
self._limiter = limiter
|
|
140
|
+
self._semaphore = semaphore
|
|
141
|
+
self._cooldown_429 = cooldown_429
|
|
142
|
+
self._cooldown_until: float = 0.0
|
|
143
|
+
self._cooldown_lock = asyncio.Lock()
|
|
144
|
+
self._retry_attempts = retry_attempts
|
|
145
|
+
self._wait_initial = wait_initial
|
|
146
|
+
self._wait_max = wait_max
|
|
147
|
+
|
|
148
|
+
async def __aenter__(self) -> Client:
|
|
149
|
+
await super().__aenter__()
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
async def __aexit__(
|
|
153
|
+
self,
|
|
154
|
+
exc_type: type[BaseException] | None = None,
|
|
155
|
+
exc_val: BaseException | None = None,
|
|
156
|
+
exc_tb: object = None,
|
|
157
|
+
) -> None:
|
|
158
|
+
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def limiter(self) -> AsyncLimiter | None:
|
|
162
|
+
"""获取速率限制器。"""
|
|
163
|
+
return self._limiter
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def semaphore(self) -> asyncio.Semaphore | None:
|
|
167
|
+
"""获取信号量。"""
|
|
168
|
+
return self._semaphore
|
|
169
|
+
|
|
170
|
+
@asynccontextmanager
|
|
171
|
+
async def _with_limits(self) -> AsyncGenerator[None, None]:
|
|
172
|
+
"""内部限流上下文管理器"""
|
|
173
|
+
async with AsyncExitStack() as stack:
|
|
174
|
+
if self._limiter:
|
|
175
|
+
await stack.enter_async_context(self._limiter)
|
|
176
|
+
if self._semaphore:
|
|
177
|
+
await stack.enter_async_context(self._semaphore)
|
|
178
|
+
yield
|
|
179
|
+
|
|
180
|
+
def _retry_strategy(self) -> AsyncRetrying:
|
|
181
|
+
"""为每次请求创建重试器"""
|
|
182
|
+
return AsyncRetrying(
|
|
183
|
+
stop=stop_after_attempt(self._retry_attempts),
|
|
184
|
+
wait=wait_exponential_jitter(initial=self._wait_initial, max=self._wait_max),
|
|
185
|
+
retry=retry_if_exception_type((
|
|
186
|
+
OSError,
|
|
187
|
+
TimeoutError,
|
|
188
|
+
ConnectionError,
|
|
189
|
+
ServerTimeoutError,
|
|
190
|
+
ServerConnectionError,
|
|
191
|
+
ClientError,
|
|
192
|
+
HTTPStatusError,
|
|
193
|
+
TiebaServerError,
|
|
194
|
+
RetriableApiError,
|
|
195
|
+
)),
|
|
196
|
+
reraise=True,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def _update_cooldown_until(self) -> None:
|
|
200
|
+
"""延长全局冷却截止时间"""
|
|
201
|
+
async with self._cooldown_lock:
|
|
202
|
+
new_until = time.monotonic() + self._cooldown_429
|
|
203
|
+
if new_until > self._cooldown_until:
|
|
204
|
+
self._cooldown_until = new_until
|
|
205
|
+
|
|
206
|
+
async def _get_cooldown_wait(self) -> float:
|
|
207
|
+
"""获取当前需要等待的全局冷却时间(秒)"""
|
|
208
|
+
async with self._cooldown_lock:
|
|
209
|
+
now = time.monotonic()
|
|
210
|
+
return max(0.0, self._cooldown_until - now)
|
|
211
|
+
|
|
212
|
+
async def _request_core(self, func: Callable[..., Awaitable[Any]], *args: Any, **kwargs: Any) -> Any:
|
|
213
|
+
"""核心调度逻辑,处理限流、熔断和错误转换"""
|
|
214
|
+
retrying = self._retry_strategy()
|
|
215
|
+
async for attempt in retrying:
|
|
216
|
+
with attempt:
|
|
217
|
+
wait_time = await self._get_cooldown_wait()
|
|
218
|
+
if wait_time > 0:
|
|
219
|
+
logger.debug("Global cooldown active. Waiting for {:.1f}s", wait_time)
|
|
220
|
+
await asyncio.sleep(wait_time)
|
|
221
|
+
|
|
222
|
+
async with self._with_limits():
|
|
223
|
+
result = await func(self, *args, **kwargs)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
ErrorHandler.check(result)
|
|
227
|
+
except RetriableApiError as e:
|
|
228
|
+
if e.code == 429 and self._cooldown_429 > 0:
|
|
229
|
+
await self._update_cooldown_until()
|
|
230
|
+
logger.warning("Retrying {} due to: {}", func.__name__, e)
|
|
231
|
+
raise
|
|
232
|
+
except UnretriableApiError:
|
|
233
|
+
raise
|
|
234
|
+
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
# 以下为直接返回DTO模型的封装方法
|
|
238
|
+
|
|
239
|
+
# 获取贴子内容 #
|
|
240
|
+
|
|
241
|
+
async def get_threads_dto(
|
|
242
|
+
self,
|
|
243
|
+
fname_or_fid: str | int,
|
|
244
|
+
/,
|
|
245
|
+
pn: int = 1,
|
|
246
|
+
*,
|
|
247
|
+
rn: int = 30,
|
|
248
|
+
sort: tb.ThreadSortType = tb.ThreadSortType.REPLY,
|
|
249
|
+
is_good: bool = False,
|
|
250
|
+
) -> ThreadsDTO:
|
|
251
|
+
"""获取指定贴吧的主题列表,并转换为通用DTO模型。"""
|
|
252
|
+
threads = await self.get_threads(fname_or_fid, pn, rn=rn, sort=sort, is_good=is_good)
|
|
253
|
+
return convert_aiotieba_threads(threads)
|
|
254
|
+
|
|
255
|
+
async def get_posts_dto(
|
|
256
|
+
self,
|
|
257
|
+
tid: int,
|
|
258
|
+
/,
|
|
259
|
+
pn: int = 1,
|
|
260
|
+
*,
|
|
261
|
+
rn: int = 30,
|
|
262
|
+
sort: tb.PostSortType = tb.PostSortType.ASC,
|
|
263
|
+
only_thread_author: bool = False,
|
|
264
|
+
with_comments: bool = False,
|
|
265
|
+
comment_sort_by_agree: bool = True,
|
|
266
|
+
comment_rn: int = 4,
|
|
267
|
+
) -> PostsDTO:
|
|
268
|
+
"""获取指定主题贴的回复列表,并转换为通用DTO模型。"""
|
|
269
|
+
posts = await self.get_posts(
|
|
270
|
+
tid,
|
|
271
|
+
pn,
|
|
272
|
+
rn=rn,
|
|
273
|
+
sort=sort,
|
|
274
|
+
only_thread_author=only_thread_author,
|
|
275
|
+
with_comments=with_comments,
|
|
276
|
+
comment_sort_by_agree=comment_sort_by_agree,
|
|
277
|
+
comment_rn=comment_rn,
|
|
278
|
+
)
|
|
279
|
+
return convert_aiotieba_posts(posts)
|
|
280
|
+
|
|
281
|
+
async def get_comments_dto(
|
|
282
|
+
self,
|
|
283
|
+
tid: int,
|
|
284
|
+
pid: int,
|
|
285
|
+
/,
|
|
286
|
+
pn: int = 1,
|
|
287
|
+
*,
|
|
288
|
+
is_comment: bool = False,
|
|
289
|
+
) -> CommentsDTO:
|
|
290
|
+
"""获取指定回复的楼中楼列表,并转换为通用DTO模型。"""
|
|
291
|
+
comments = await self.get_comments(tid, pid, pn, is_comment=is_comment)
|
|
292
|
+
return convert_aiotieba_comments(comments)
|
|
293
|
+
|
|
294
|
+
# 获取用户信息 #
|
|
295
|
+
|
|
296
|
+
async def anyid2user_info_dto(self, uid: int | str, is_tieba_uid: bool = True) -> UserInfoDTO:
|
|
297
|
+
"""
|
|
298
|
+
根据任意用户ID获取完整的用户信息,并转换为通用DTO模型。
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
uid: 用户ID,可以是贴吧ID、user_id、portrait或用户名。
|
|
302
|
+
is_tieba_uid: 指示uid是否为贴吧UID,默认为True。
|
|
303
|
+
"""
|
|
304
|
+
if is_tieba_uid and isinstance(uid, int):
|
|
305
|
+
user_tuid = await self.tieba_uid2user_info(uid)
|
|
306
|
+
user = await self.get_user_info(user_tuid.user_id)
|
|
307
|
+
else:
|
|
308
|
+
user = await self.get_user_info(uid)
|
|
309
|
+
return convert_aiotieba_userinfo(user)
|
|
310
|
+
|
|
311
|
+
async def get_nickname_old(self, user_id: int) -> str:
|
|
312
|
+
user_info = await self.get_user_info(user_id, require=tb.ReqUInfo.BASIC)
|
|
313
|
+
return str(user_info.nick_name_old)
|
|
314
|
+
|
|
315
|
+
# 以下为重写的部分 aiotieba.Client API
|
|
316
|
+
# 添加了 @with_ensure 装饰器以启用重试机制
|
|
317
|
+
# 完全拦截过于魔法,这里仅重写部分常用API
|
|
318
|
+
|
|
319
|
+
# 获取贴子内容 #
|
|
320
|
+
|
|
321
|
+
@with_ensure
|
|
322
|
+
async def get_threads(
|
|
323
|
+
self,
|
|
324
|
+
fname_or_fid: str | int,
|
|
325
|
+
/,
|
|
326
|
+
pn: int = 1,
|
|
327
|
+
*,
|
|
328
|
+
rn: int = 30,
|
|
329
|
+
sort: tb.ThreadSortType = tb.ThreadSortType.REPLY,
|
|
330
|
+
is_good: bool = False,
|
|
331
|
+
) -> Threads:
|
|
332
|
+
return await super().get_threads(fname_or_fid, pn, rn=rn, sort=sort, is_good=is_good)
|
|
333
|
+
|
|
334
|
+
@with_ensure
|
|
335
|
+
async def get_posts(
|
|
336
|
+
self,
|
|
337
|
+
tid: int,
|
|
338
|
+
/,
|
|
339
|
+
pn: int = 1,
|
|
340
|
+
*,
|
|
341
|
+
rn: int = 30,
|
|
342
|
+
sort: tb.PostSortType = tb.PostSortType.ASC,
|
|
343
|
+
only_thread_author: bool = False,
|
|
344
|
+
with_comments: bool = False,
|
|
345
|
+
comment_sort_by_agree: bool = True,
|
|
346
|
+
comment_rn: int = 4,
|
|
347
|
+
) -> Posts:
|
|
348
|
+
return await super().get_posts(
|
|
349
|
+
tid,
|
|
350
|
+
pn,
|
|
351
|
+
rn=rn,
|
|
352
|
+
sort=sort,
|
|
353
|
+
only_thread_author=only_thread_author,
|
|
354
|
+
with_comments=with_comments,
|
|
355
|
+
comment_sort_by_agree=comment_sort_by_agree,
|
|
356
|
+
comment_rn=comment_rn,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
@with_ensure
|
|
360
|
+
async def get_comments(
|
|
361
|
+
self,
|
|
362
|
+
tid: int,
|
|
363
|
+
pid: int,
|
|
364
|
+
/,
|
|
365
|
+
pn: int = 1,
|
|
366
|
+
*,
|
|
367
|
+
is_comment: bool = False,
|
|
368
|
+
) -> Comments:
|
|
369
|
+
return await super().get_comments(tid, pid, pn, is_comment=is_comment)
|
|
370
|
+
|
|
371
|
+
@with_ensure
|
|
372
|
+
async def get_user_threads(
|
|
373
|
+
self,
|
|
374
|
+
id_: str | int | None = None,
|
|
375
|
+
pn: int = 1,
|
|
376
|
+
*,
|
|
377
|
+
public_only: bool = False,
|
|
378
|
+
) -> UserThreads:
|
|
379
|
+
return await super().get_user_threads(id_, pn, public_only=public_only)
|
|
380
|
+
|
|
381
|
+
@with_ensure
|
|
382
|
+
async def get_user_posts(
|
|
383
|
+
self,
|
|
384
|
+
id_: str | int | None = None,
|
|
385
|
+
pn: int = 1,
|
|
386
|
+
*,
|
|
387
|
+
rn: int = 20,
|
|
388
|
+
) -> UserPostss:
|
|
389
|
+
return await super().get_user_posts(id_, pn, rn=rn)
|
|
390
|
+
|
|
391
|
+
# 获取用户信息 #
|
|
392
|
+
|
|
393
|
+
@with_ensure
|
|
394
|
+
async def tieba_uid2user_info(self, tieba_uid: int) -> UserInfo_TUid:
|
|
395
|
+
return await super().tieba_uid2user_info(tieba_uid)
|
|
396
|
+
|
|
397
|
+
@with_ensure
|
|
398
|
+
async def get_user_info(self, id_: str | int, /, require: tb.ReqUInfo = tb.ReqUInfo.ALL) -> UserInfo:
|
|
399
|
+
return await super().get_user_info(id_, require)
|
|
400
|
+
|
|
401
|
+
@with_ensure
|
|
402
|
+
async def get_self_info(self, require: tb.ReqUInfo = tb.ReqUInfo.ALL) -> UserInfo:
|
|
403
|
+
return await super().get_self_info(require)
|
|
404
|
+
|
|
405
|
+
@with_ensure
|
|
406
|
+
async def get_follow_forums(self, id_: str | int, /, pn: int = 1, *, rn: int = 50) -> FollowForums:
|
|
407
|
+
return await super().get_follow_forums(id_, pn, rn=rn)
|
|
408
|
+
|
|
409
|
+
# 获取贴吧信息 #
|
|
410
|
+
|
|
411
|
+
@with_ensure
|
|
412
|
+
async def get_fid(self, fname: str) -> IntResponse:
|
|
413
|
+
return await super().get_fid(fname)
|
|
414
|
+
|
|
415
|
+
@with_ensure
|
|
416
|
+
async def get_fname(self, fid: int) -> StrResponse:
|
|
417
|
+
return await super().get_fname(fid)
|
|
418
|
+
|
|
419
|
+
@with_ensure
|
|
420
|
+
async def get_tab_map(self, fname_or_fid: str | int) -> TabMap:
|
|
421
|
+
return await super().get_tab_map(fname_or_fid)
|
|
422
|
+
|
|
423
|
+
# 吧务查询 #
|
|
424
|
+
|
|
425
|
+
@with_ensure
|
|
426
|
+
async def get_bawu_blacklist(self, fname_or_fid: str | int, /, pn: int = 1) -> BawuBlacklistUsers:
|
|
427
|
+
return await super().get_bawu_blacklist(fname_or_fid, pn)
|
|
428
|
+
|
|
429
|
+
@with_ensure
|
|
430
|
+
async def get_bawu_postlogs(
|
|
431
|
+
self,
|
|
432
|
+
fname_or_fid: str | int,
|
|
433
|
+
/,
|
|
434
|
+
pn: int = 1,
|
|
435
|
+
*,
|
|
436
|
+
search_value: str = "",
|
|
437
|
+
search_type: tb.BawuSearchType = tb.BawuSearchType.USER,
|
|
438
|
+
start_dt: datetime | None = None,
|
|
439
|
+
end_dt: datetime | None = None,
|
|
440
|
+
op_type: int = 0,
|
|
441
|
+
) -> Postlogs:
|
|
442
|
+
return await super().get_bawu_postlogs(
|
|
443
|
+
fname_or_fid,
|
|
444
|
+
pn,
|
|
445
|
+
search_value=search_value,
|
|
446
|
+
search_type=search_type,
|
|
447
|
+
start_dt=start_dt,
|
|
448
|
+
end_dt=end_dt,
|
|
449
|
+
op_type=op_type,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
@with_ensure
|
|
453
|
+
async def get_bawu_userlogs(
|
|
454
|
+
self,
|
|
455
|
+
fname_or_fid: str | int,
|
|
456
|
+
/,
|
|
457
|
+
pn: int = 1,
|
|
458
|
+
*,
|
|
459
|
+
search_value: str = "",
|
|
460
|
+
search_type: tb.BawuSearchType = tb.BawuSearchType.USER,
|
|
461
|
+
start_dt: datetime | None = None,
|
|
462
|
+
end_dt: datetime | None = None,
|
|
463
|
+
op_type: int = 0,
|
|
464
|
+
) -> Userlogs:
|
|
465
|
+
return await super().get_bawu_userlogs(
|
|
466
|
+
fname_or_fid,
|
|
467
|
+
pn,
|
|
468
|
+
search_value=search_value,
|
|
469
|
+
search_type=search_type,
|
|
470
|
+
start_dt=start_dt,
|
|
471
|
+
end_dt=end_dt,
|
|
472
|
+
op_type=op_type,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# 吧务操作 #
|
|
476
|
+
|
|
477
|
+
@with_ensure
|
|
478
|
+
async def del_thread(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse:
|
|
479
|
+
return await super().del_thread(fname_or_fid, tid)
|
|
480
|
+
|
|
481
|
+
@with_ensure
|
|
482
|
+
async def del_post(self, fname_or_fid: str | int, /, tid: int, pid: int) -> BoolResponse:
|
|
483
|
+
return await super().del_post(fname_or_fid, tid, pid)
|
|
484
|
+
|
|
485
|
+
@with_ensure
|
|
486
|
+
async def add_bawu_blacklist(self, fname_or_fid: str | int, /, id_: str | int) -> BoolResponse:
|
|
487
|
+
return await super().add_bawu_blacklist(fname_or_fid, id_)
|
|
488
|
+
|
|
489
|
+
@with_ensure
|
|
490
|
+
async def del_bawu_blacklist(self, fname_or_fid: str | int, /, id_: str | int) -> BoolResponse:
|
|
491
|
+
return await super().del_bawu_blacklist(fname_or_fid, id_)
|
|
492
|
+
|
|
493
|
+
@with_ensure
|
|
494
|
+
async def block(
|
|
495
|
+
self, fname_or_fid: str | int, /, id_: str | int, *, day: int = 1, reason: str = ""
|
|
496
|
+
) -> BoolResponse:
|
|
497
|
+
return await super().block(fname_or_fid, id_, day=day, reason=reason)
|
|
498
|
+
|
|
499
|
+
@with_ensure
|
|
500
|
+
async def unblock(self, fname_or_fid: str | int, /, id_: str | int) -> BoolResponse:
|
|
501
|
+
return await super().unblock(fname_or_fid, id_)
|
|
502
|
+
|
|
503
|
+
@with_ensure
|
|
504
|
+
async def good(self, fname_or_fid: str | int, /, tid: int, *, cname: str = "") -> BoolResponse:
|
|
505
|
+
return await super().good(fname_or_fid, tid, cname=cname)
|
|
506
|
+
|
|
507
|
+
@with_ensure
|
|
508
|
+
async def ungood(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse:
|
|
509
|
+
return await super().ungood(fname_or_fid, tid)
|
|
510
|
+
|
|
511
|
+
@with_ensure
|
|
512
|
+
async def top(self, fname_or_fid: str | int, /, tid: int, *, is_vip: bool = False) -> BoolResponse:
|
|
513
|
+
return await super().top(fname_or_fid, tid, is_vip=is_vip)
|
|
514
|
+
|
|
515
|
+
@with_ensure
|
|
516
|
+
async def untop(self, fname_or_fid: str | int, /, tid: int, *, is_vip: bool = False) -> BoolResponse:
|
|
517
|
+
return await super().untop(fname_or_fid, tid, is_vip=is_vip)
|
|
File without changes
|