diff --git a/bilibili_api/credential.py b/bilibili_api/credential.py index d9928f3a..8b86cc96 100644 --- a/bilibili_api/credential.py +++ b/bilibili_api/credential.py @@ -33,7 +33,7 @@ class Credential(_Credential): 凭据操作类,用于各种请求操作。 """ - async def chcek_refresh(self) -> bool: + async def check_refresh(self) -> bool: """ 检查是否需要刷新 cookies diff --git a/bilibili_api/data/api/login.json b/bilibili_api/data/api/login.json index d46f6c6a..25fadf2e 100644 --- a/bilibili_api/data/api/login.json +++ b/bilibili_api/data/api/login.json @@ -7,11 +7,12 @@ "comment": "请求二维码及登录密钥" }, "get_events": { - "url": "https://passport.bilibili.com/x/passport-login/web/qrcode/poll?source=main-fe-header", + "url": "https://passport.bilibili.com/x/passport-login/web/qrcode/poll", "method": "GET", "verify": false, "data": { - "qrcode_key": "str: 登陆密钥" + "qrcode_key": "str: 登陆密钥", + "source": "main-fe-header" }, "comment": "获取最新信息" }, diff --git a/bilibili_api/data/api/user.json b/bilibili_api/data/api/user.json index d734d759..6045cf00 100644 --- a/bilibili_api/data/api/user.json +++ b/bilibili_api/data/api/user.json @@ -219,11 +219,13 @@ "comment": "获取用户所有关注(需要用户公开信息)" }, "all_followings2": { - "url": "https://app.biliapi.net/x/v2/relation/followings", + "url": "https://api.bilibili.com/x/relation/followings", "method": "GET", - "verify": false, + "verify": true, "params": { "vmid": "int: uid", + "order": "str: desc 倒序, asc 正序", + "order_type": "str: 按照关注顺序排列:留空 按照最常访问排列:attention", "pn": "int: 页码", "ps":"const int: 100" }, diff --git a/bilibili_api/dynamic.py b/bilibili_api/dynamic.py index 5cb90c66..47b2d91c 100644 --- a/bilibili_api/dynamic.py +++ b/bilibili_api/dynamic.py @@ -179,15 +179,16 @@ def transformPicInfo(image: dict): return data -async def upload_image(image: Picture, credential: Credential) -> dict: +async def upload_image(image: Picture, credential: Credential, data: dict = None) -> dict: """ 上传动态图片 Args: - image (Picture) : 图片流. 有格式要求. + image (Picture) : 图片流. 有格式要求. - credential (Credential): 凭据 + credential (Credential): 凭据 + data (dict): 自定义请求体 Returns: dict: 调用 API 返回的结果 """ @@ -197,7 +198,8 @@ async def upload_image(image: Picture, credential: Credential) -> dict: api = API["send"]["upload_img"] raw = image.content - data = {"biz": "new_dyn", "category": "daily"} + if data is None: + data = {"biz": "new_dyn", "category": "daily"} return_info = await request( "POST", diff --git a/bilibili_api/live.py b/bilibili_api/live.py index 3dde41c7..7f744ef3 100644 --- a/bilibili_api/live.py +++ b/bilibili_api/live.py @@ -920,7 +920,9 @@ async def __main(self) -> None: self.logger.info(f"准备连接直播间 {self.room_display_id}") # 获取真实房间号 self.logger.debug("正在获取真实房间号") - self.__room_real_id = (await room.get_room_play_info())["room_id"] + info = await room.get_room_play_info() + self.__room__uid = info["uid"] + self.__room_real_id = info["room_id"] self.logger.debug(f"获取成功,真实房间号:{self.__room_real_id}") # 获取直播服务器配置 @@ -1064,9 +1066,10 @@ async def __handle_data(self, data) -> None: async def __send_verify_data(self, ws: ClientWebSocketResponse, token: str) -> None: verifyData = { - "uid": 0, + "uid": self.__room__uid, "roomid": self.__room_real_id, "protover": 3, + "buvid": self.credential.buvid3, "platform": "web", "type": 2, "key": token, diff --git a/bilibili_api/login.py b/bilibili_api/login.py index 88cbf3dc..c5fbbd13 100644 --- a/bilibili_api/login.py +++ b/bilibili_api/login.py @@ -14,8 +14,8 @@ import uuid from yarl import URL import webbrowser +import sys -import requests from .exceptions.LoginError import LoginError from .utils.credential import Credential @@ -54,14 +54,40 @@ is_destroy = False id_ = 0 # 事件 id,用于取消 after 绑定 - +def parse_credential_url(events: dict) -> Credential: + url = events["data"]["url"] + cookies_list = url.split("?")[1].split("&") + sessdata = "" + bili_jct = "" + dedeuserid = "" + for cookie in cookies_list: + if cookie[:8] == "SESSDATA": + sessdata = cookie[9:] + if cookie[:8] == "bili_jct": + bili_jct = cookie[9:] + if cookie[:11].upper() == "DEDEUSERID=": + dedeuserid = cookie[11:] + ac_time_value=events["data"]["refresh_token"] + buvid3=get_live_buvid() + return Credential(sessdata=sessdata, + bili_jct=bili_jct, + buvid3=buvid3, + dedeuserid=dedeuserid, + ac_time_value=ac_time_value) + def make_qrcode(url) -> str: qr = qrcode.QRCode() qr.add_data(url) img = qr.make_image() img.save(os.path.join(tempfile.gettempdir(), "qrcode.png")) + print("二维码已保存至", os.path.join(tempfile.gettempdir(), "qrcode.png")) return os.path.join(tempfile.gettempdir(), "qrcode.png") +def update_qrcode_data() -> dict: + api = API["qrcode"]["get_qrcode_and_token"] + qrcode_data = httpx.get(api["url"], follow_redirects=True).json()['data'] + return qrcode_data + def login_with_qrcode(root=None) -> Credential: """ @@ -85,9 +111,11 @@ def login_with_qrcode(root=None) -> Credential: if root == None: root = tkinter.Tk() root.title("扫码登录") - qrcode_image = update_qrcode() + qrcode_data = update_qrcode_data() + login_key = qrcode_data["qrcode_key"] + qrcode_image = make_qrcode(qrcode_data["url"]) photo = PhotoImage(file=qrcode_image) - qrcode_label = tkinter.Label(root, image=photo, width=500, height=500) + qrcode_label = tkinter.Label(root, image=photo, width=600, height=600) qrcode_label.pack() big_font = tkinter.font.Font(root, size=25) log = tkinter.Label(root, text="请扫描二维码↑", font=big_font, fg="red") @@ -95,58 +123,30 @@ def login_with_qrcode(root=None) -> Credential: def update_events(): global id_ - global start, credential, is_destroy - # log.configure(text="点下确认啊!", fg="orange", font=big_font) - events_api = API["qrcode"]["get_events"] - params = {"qrcode_key": login_key} - events = json.loads( - requests.get( - events_api["url"], - params=params, - cookies={"buvid3": str(uuid.uuid1()), "Domain": ".bilibili.com"}, - ).text - ) - # print(events) - # 新的 events["data"] - # {'url': '', 'refresh_token': '', 'timestamp': 0, 'code': 86101, 'message': '未扫码'} - # {'url': '', 'refresh_token': '', 'timestamp': 0, 'code': 86090, 'message': '二维码已扫码未确认'} - # {'url': 'https://passport.biligame.com/x/passport-login/web/crossDomain?DedeUserID=x&DedeUserID__ckMd5=x&Expires=x&SESSDATA=x&bili_jct=x&gourl=x', 'refresh_token': 'x', 'timestamp': 1683903305723, 'code': 0, 'message': ''} - if "code" in events.keys() and events["code"] == -412: - raise LoginError(events["message"]) - if events["data"]["code"] == 86101: - log.configure(text="请扫描二维码↑", fg="red", font=big_font) - elif events["data"]["code"] == 86090: - log.configure(text="点下确认啊!", fg="orange", font=big_font) - elif events["data"]["code"] == 0: - url: str = events["data"]["url"] - cookies_list = url.split("?")[1].split("&") - sessdata = "" - bili_jct = "" - dedeuserid = "" - for cookie in cookies_list: - if cookie[:8] == "SESSDATA": - sessdata = cookie[9:] - if cookie[:8] == "bili_jct": - bili_jct = cookie[9:] - if cookie[:11].upper() == "DEDEUSERID=": - dedeuserid = cookie[11:] - c = Credential( - sessdata=sessdata, - bili_jct=bili_jct, - dedeuserid=dedeuserid, - ac_time_value=events["data"]["refresh_token"], - ) - global credential - credential = c - log.configure(text="成功!", fg="green", font=big_font) - global start - start = time.perf_counter() - root.after(1000, destroy) - id_ = root.after(500, update_events) - # 刷新 - if time.perf_counter() - start > 120: - update_qrcode() - start = time.perf_counter() + global start, credential, is_destroy, login_key + events = login_with_key(login_key) + if "code" in events.keys() and events["code"] == 0: + if events["data"]["code"] == 86101: + log.configure(text="请扫描二维码↑", fg="red", font=big_font) + elif events["data"]["code"] == 86090: + log.configure(text="点下确认啊!", fg="orange", font=big_font) + elif events["data"]["code"] == 86038: + raise LoginError("二维码过期,请扫新二维码!") + elif events["data"]["code"] == 0: + log.configure(text="成功!", fg="green", font=big_font) + credential = parse_credential_url(events) + root.after(1000, destroy) + return 0 + id_ = root.after(500, update_events) + if time.perf_counter() - start > 120: # 刷新 + qrcode_data = update_qrcode_data() + login_key = qrcode_data["qrcode_key"] + qrcode_image = make_qrcode(qrcode_data["url"]) + photo = PhotoImage(file=qrcode_image) + qrcode_label = tkinter.Label(root, image=photo, width=600, height=600) + qrcode_label.pack() + start = time.perf_counter() + root.update() def destroy(): @@ -159,16 +159,63 @@ def destroy(): root.after_cancel(id_) # type: ignore return credential +def login_with_qrcode_term() -> Credential: + """ + 终端扫描二维码登录 -def update_qrcode() -> str: - global login_key, qrcode_image - api = API["qrcode"]["get_qrcode_and_token"] - qrcode_login_data = json.loads(httpx.get(api["url"]).text)["data"] - login_key = qrcode_login_data["qrcode_key"] - qrcode = qrcode_login_data["url"] - qrcode_image = make_qrcode(qrcode) - return qrcode_image + Args: + Returns: + Credential: 凭据 + """ + import qrcode_terminal + qrcode_data = update_qrcode_data() + qrcode_url = qrcode_data["url"] + login_key = qrcode_data["qrcode_key"] + print(qrcode_terminal.qr_terminal_str(qrcode_url) + "\n") + while True: + events = login_with_key(login_key) + if "code" in events.keys() and events["code"] == 0: + if events["data"]["code"] == 86101: + sys.stdout.write('\r 请扫描二维码↑') + sys.stdout.flush() + elif events["data"]["code"] == 86090: + sys.stdout.write('\r 点下确认啊!') + sys.stdout.flush() + elif events["data"]["code"] == 86038: + print("二维码过期,请扫新二维码!") + qrcode_data = update_qrcode_data() + qrcode_url = qrcode_data["url"] + print(qrcode_terminal.qr_terminal_str(qrcode_url) + "\n") + elif events["data"]["code"] == 0: + sys.stdout.write('\r 成功!') + sys.stdout.flush() + return parse_credential_url(events) + elif "code" in events.keys(): + raise LoginError(events["message"]) + time.sleep(0.5) + + +def login_with_key(key: str) -> dict: + params = {"qrcode_key": key, "source": "main-fe-header"} + events_api = API["qrcode"]["get_events"] + events = httpx.get( + events_api["url"], + params=params, + cookies={"buvid3": str(uuid.uuid1()), "Domain": ".bilibili.com"}, + ).json() + return events + + +def get_live_buvid(): + import re + url = "https://api.live.bilibili.com/gift/v3/live/gift_config" + headers = HEADERS.copy() + response = httpx.get(url, headers=headers) + response.raise_for_status() + set_cookie = response.headers.get("Set-Cookie") + live_buvid = re.findall(r"LIVE_BUVID=(AUTO[0-9]+)", set_cookie)[0] + return live_buvid # ---------------------------------------------------------------- # 密码登录 diff --git a/bilibili_api/login_func.py b/bilibili_api/login_func.py index 1b80fa3e..60104abe 100644 --- a/bilibili_api/login_func.py +++ b/bilibili_api/login_func.py @@ -40,7 +40,7 @@ def get_qrcode() -> Tuple[Picture, str]: Returns: Tuple[dir, str]: 第一项是二维码图片地址(本地缓存)和登录密钥。登录密钥需要保存。 """ - img = login.update_qrcode() + img = login.update_qrcode_image() login_key = login.login_key return (Picture.from_file(img), login_key) diff --git a/bilibili_api/session.py b/bilibili_api/session.py index ee3fadb9..aa241e7b 100644 --- a/bilibili_api/session.py +++ b/bilibili_api/session.py @@ -253,8 +253,8 @@ async def send_msg( "msg[msg_type]": int(msg_type), "msg[msg_status]": 0, "msg[content]": real_content, - "msg[dev_id]": "B9A37BF3-AA9D-4076-A4D3-366AC8C4C5DB", - "msg[new_face_version]": "0", + "msg[dev_id]": "A6716E9A-7CE3-47AF-994B-F0B34178D28D", + "msg[new_face_version]": 0, "msg[timestamp]": int(time.time()), "from_filework": 0, "build": 0, diff --git a/bilibili_api/user.py b/bilibili_api/user.py index 202cda13..3ab48f7a 100644 --- a/bilibili_api/user.py +++ b/bilibili_api/user.py @@ -170,6 +170,15 @@ class HistoryBusinessType(Enum): article_list = "article-list" article = "article" +class OrderType(Enum): + """ + 排序字段 + + + desc:倒序 + + asc:正序 + """ + desc = "desc" + asc = "asc" async def name2uid(names: Union[str, List[str]]): """ @@ -234,7 +243,7 @@ async def __get_self_info(self) -> dict: if self.__self_info is not None: return self.__self_info - self.__self_info = await get_self_info(credential=self.credential) + self.__self_info = await self.get_user_info() return self.__self_info def get_uid(self) -> int: @@ -562,7 +571,7 @@ async def get_subscribed_bangumi( ) async def get_followings( - self, pn: int = 1, ps: int = 100, attention: bool = False + self, pn: int = 1, ps: int = 100, attention: bool = False, order: OrderType = OrderType.desc ) -> dict: """ 获取用户关注列表(不是自己只能访问前 5 页) @@ -572,7 +581,9 @@ async def get_followings( ps (int, optional) : 每页的数据量. Defaults to 100. - attention (bool, optional) : 是否采用“最常访问”排序. Defaults to False. + attention (bool, optional) : 是否采用“最常访问”排序,否则为“关注顺序”排序. Defaults to False. + + order (OrderType, optional) : 排序方式. Defaults to OrderType.desc. Returns: dict: 调用接口返回的内容。 @@ -583,6 +594,7 @@ async def get_followings( "ps": ps, "pn": pn, "order_type": "attention" if attention else "", + "order": order.value } return await request( "GET", url=api["url"], params=params, credential=self.credential diff --git a/bilibili_api/utils/network_httpx.py b/bilibili_api/utils/network_httpx.py index dd86622d..cdab45fd 100644 --- a/bilibili_api/utils/network_httpx.py +++ b/bilibili_api/utils/network_httpx.py @@ -16,11 +16,12 @@ from functools import reduce from inspect import iscoroutinefunction as isAsync from typing import Any, Coroutine, Dict, Union +from urllib.parse import urlencode import httpx from .. import settings -from ..exceptions import NetworkException, ResponseCodeException, ResponseException, ApiException +from ..exceptions import ApiException, ResponseCodeException from .credential import Credential from .sync import sync from .utils import get_api @@ -30,24 +31,59 @@ wbi_mixin_key = "" # 使用 Referer 和 UA 请求头以绕过反爬虫机制 -HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.37", - "Referer": "https://www.bilibili.com" - } +HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.37", + "Referer": "https://www.bilibili.com", +} +API = get_api("credential") + + +def retry(times: int = 3): + """ + 重试装饰器 + + Args: + times (int): 最大重试次数 默认 3 次 负数则一直重试直到成功 + + Returns: + Any: 原函数调用结果 + """ + def wrapper(func: Coroutine): + async def inner(*args, **kwargs): + # 这里必须新建一个变量用来计数!!不能直接对 times 操作!!! + nonlocal times + loop = times + while loop != 0: + if loop != times and settings.request_log: + settings.logger.info("第 %d 次重试", times - loop) + loop -= 1 + try: + return await func(*args, **kwargs) + except json.decoder.JSONDecodeError: + # json 解析错误 说明数据获取有误 再给次机会 + continue + except ResponseCodeException as e: + # -403 时尝试重新获取 wbi_mixin_key 可能过期了 + if e.code == -403: + global wbi_mixin_key + wbi_mixin_key = "" + continue + # 不是 -403 错误直接报错 + raise + raise ApiException("重试达到最大次数") + return inner + + if isAsync(times): + # 防呆不防傻 防止有人 @retry() 不打括号 + func = times + times = 3 + return wrapper(func) + + return wrapper @dataclass class Api: - url: str - method: str - comment: str = "" - wbi: bool = False - verify: bool = False - no_csrf: bool = False - json_body: bool = False - ignore_code: bool = False - data: dict = field(default_factory=dict) - params: dict = field(default_factory=dict) - credential: Credential = field(default_factory=Credential) """ 用于请求的 Api 类 @@ -74,6 +110,17 @@ class Api: credential (Credential, optional): 凭据. Defaults to Credential(). """ + url: str + method: str + comment: str = "" + wbi: bool = False + verify: bool = False + no_csrf: bool = False + json_body: bool = False + ignore_code: bool = False + data: dict = field(default_factory=dict) + params: dict = field(default_factory=dict) + credential: Credential = field(default_factory=Credential) def __post_init__(self): self.method = self.method.upper() @@ -105,7 +152,7 @@ async def result(self) -> Union[None, dict]: `self.__result` 用来暂存数据 参数不变时获取结果不变 """ if self.__result is None: - self.__result = await request(self) + self.__result = await self.request() return self.__result @property @@ -161,6 +208,89 @@ def update(self, **kwargs): else: return self.update_data(**kwargs) + @retry(times=3) + async def request(self, **kwargs) -> Any: + """ + 向接口发送请求。 + + Returns: + 接口未返回数据时,返回 None,否则返回该接口提供的 data 或 result 字段的数据。 + """ + # 请求为非 GET 且 no_csrf 不为 True 时要求 bili_jct + if self.method != "GET" and not self.no_csrf: + self.credential.raise_for_no_bili_jct() + + if settings.request_log: + settings.logger.info(self) + + # jsonp + if self.params.get("jsonp") == "jsonp": + self.params["callback"] = "callback" + + if self.wbi: + global wbi_mixin_key + if wbi_mixin_key == "": + wbi_mixin_key = await get_mixin_key() + enc_wbi(self.params, wbi_mixin_key) + + # 自动添加 csrf + if not self.no_csrf and self.method in ["POST", "DELETE", "PATCH"]: + self.data["csrf"] = self.credential.bili_jct + self.data["csrf_token"] = self.credential.bili_jct + + cookies = self.credential.get_cookies() + cookies["buvid3"] = str(uuid.uuid1()) + cookies["Domain"] = ".bilibili.com" + + config = { + "url": self.url, + "method": self.method, + "data": self.data, + "params": self.params, + "cookies": cookies, + "headers": HEADERS.copy(), + } + config.update(kwargs) + + if self.json_body: + config["headers"]["Content-Type"] = "application/json" + config["data"] = json.dumps(config["data"]) + + session = get_session() + resp = await session.request(**config) + + # 检查响应头 Content-Length + content_length = resp.headers.get("content-length") + if content_length and int(content_length) == 0: + return None + + if "callback" in self.params: + # JSONP 请求 + resp_data: dict = json.loads( + re.match("^.*?({.*}).*$", resp.text, re.S).group(1)) + else: + # JSON + resp_data: dict = json.loads(resp.text) + + # 检查 code + if not self.ignore_code: + code = resp_data.get("code") + + if code is None: + raise ResponseCodeException(-1, "API 返回数据未含 code 字段", resp_data) + if code != 0: + msg = resp_data.get("msg") + if msg is None: + msg = resp_data.get("message") + if msg is None: + msg = "接口未返回错误信息" + raise ResponseCodeException(code, msg, resp_data) + + real_data = resp_data.get("data") + if real_data is None: + real_data = resp_data.get("result") + return real_data + @classmethod def from_file(cls, path: str, credential: Union[Credential, None] = None): """ @@ -168,6 +298,7 @@ def from_file(cls, path: str, credential: Union[Credential, None] = None): Args: path (str): 例如 user.info.info + credential (Credential, Optional): 凭据类. Defaults to None. Returns: @@ -204,7 +335,7 @@ async def get_nav(credential: Union[Credential, None] = None): Returns: dict: 账号相关信息 """ - return await Api(credential=credential, **get_api("credential")["info"]["valid"]).result + return await Api(credential=credential, **API["info"]["valid"]).result async def get_mixin_key() -> str: @@ -216,6 +347,8 @@ async def get_mixin_key() -> str: """ data = await get_nav() wbi_img: Dict[str, str] = data["wbi_img"] + # 为什么要把里的 lambda 表达式换成函数 这不是一样的吗 + # split = lambda key: wbi_img.get(key).split("/")[-1].split(".")[0] def split(key): return wbi_img.get(key).split("/")[-1].split(".")[0] ae = split("img_url") + split("sub_url") oe = [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, @@ -233,13 +366,12 @@ def enc_wbi(params: dict, mixin_key: str): mixin_key (str): 混合密钥 """ + params.pop("w_rid", None) # 重试时先把原有 w_rid 去除 params["wts"] = int(time.time()) - keys = sorted(filter(lambda k: k != "w_rid", params.keys())) - Ae = "&".join(f"{key}={params[key]}" for key in keys) - w_rid = hashlib.md5( + Ae = urlencode(sorted(params.items())) + params["w_rid"] = hashlib.md5( (Ae + mixin_key).encode(encoding="utf-8") ).hexdigest() - params["w_rid"] = w_rid @atexit.register @@ -261,7 +393,7 @@ async def __clean_task(): loop.create_task(__clean_task()) -async def request_old( +async def request( method: str, url: str, params: Union[dict, None] = None, @@ -357,8 +489,7 @@ async def request_old( session = get_session() - if True: # try: - resp = await session.request(**config) + resp = await session.request(**config) # except Exception : # raise httpx.ConnectError("连接出错。") @@ -368,7 +499,7 @@ async def request_old( return None # 检查响应头 Content-Type - content_type = resp.headers.get("content-type") + # content_type = resp.headers.get("content-type") # 不是 application/json # if content_type.lower().index("application/json") == -1: @@ -437,146 +568,3 @@ def set_session(session: httpx.AsyncClient) -> None: """ loop = asyncio.get_event_loop() __session_pool[loop] = session - - -def rollback(func): - async def wrapper(*args, **kwargs): - try: - return await func(*args, **kwargs) - except AttributeError: - return await request_old(*args, **kwargs) - return wrapper - - -def retry(times: int = 3): - """ - 重试装饰器 - - Args: - times (int): 最大重试次数 默认 3 次 负数则一直重试直到成功 - - Returns: - Any: 原函数调用结果 - """ - def wrapper(func: Coroutine): - async def inner(*args, **kwargs): - # 这里必须新建一个变量用来计数!!不能直接对 times 操作!!! - nonlocal times - loop = times - while loop != 0: - if loop != times and settings.request_log: - settings.logger.info(f"第 {times - loop} 次重试") - loop -= 1 - try: - return await func(*args, **kwargs) - except json.decoder.JSONDecodeError: - # json 解析错误 说明数据获取有误 再给次机会 - continue - except ResponseCodeException as e: - # -403 时尝试重新获取 wbi_mixin_key 可能过期了 - if e.code == -403: - global wbi_mixin_key - wbi_mixin_key = "" - continue - # 不是 -403 错误直接报错 - raise - raise ApiException("重试达到最大次数") - return inner - - if isAsync(times): - # 防呆不防傻 防止有人 @retry() 不打括号 - func = times - times = 3 - return wrapper(func) - - return wrapper - - -@retry() -@rollback -async def request(api: Api, url: str = "", params: dict = None, **kwargs) -> Any: - """ - 向接口发送请求。 - - Args: - api (Api): 请求 Api 信息。 - - url, params: 这两个参数是为了通过 Conventional Commits 写的,最后使用的时候(指完全取代老的之后)可以去掉。 - - Returns: - 接口未返回数据时,返回 None,否则返回该接口提供的 data 或 result 字段的数据。 - """ - # 请求为非 GET 且 no_csrf 不为 True 时要求 bili_jct - if api.method != "GET" and not api.no_csrf: - api.credential.raise_for_no_bili_jct() - - if settings.request_log: - settings.logger.info(api) - - # jsonp - if api.params.get("jsonp") == "jsonp": - api.params["callback"] = "callback" - - if api.wbi: - global wbi_mixin_key - if wbi_mixin_key == "": - wbi_mixin_key = await get_mixin_key() - enc_wbi(api.params, wbi_mixin_key) - - # 自动添加 csrf - if not api.no_csrf and api.method in ["POST", "DELETE", "PATCH"]: - api.data["csrf"] = api.credential.bili_jct - api.data["csrf_token"] = api.credential.bili_jct - - cookies = api.credential.get_cookies() - cookies["buvid3"] = str(uuid.uuid1()) - cookies["Domain"] = ".bilibili.com" - - config = { - "url": api.url, - "method": api.method, - "data": api.data, - "params": api.params, - "cookies": cookies, - "headers": HEADERS.copy(), - } - config.update(kwargs) - - if api.json_body: - config["headers"]["Content-Type"] = "application/json" - config["data"] = json.dumps(config["data"]) - - session = get_session() - resp = await session.request(**config) - - # 检查响应头 Content-Length - content_length = resp.headers.get("content-length") - if content_length and int(content_length) == 0: - return None - - if "callback" in api.params: - # JSONP 请求 - resp_data: dict = json.loads( - re.match("^.*?({.*}).*$", resp.text, re.S).group(1)) - else: - # JSON - resp_data: dict = json.loads(resp.text) - - # 检查 code - if not api.ignore_code: - code = resp_data.get("code") - - if code is None: - raise ResponseCodeException(-1, "API 返回数据未含 code 字段", resp_data) - if code != 0: - msg = resp_data.get("msg") - if msg is None: - msg = resp_data.get("message") - if msg is None: - msg = "接口未返回错误信息" - raise ResponseCodeException(code, msg, resp_data) - - real_data = resp_data.get("data") - if real_data is None: - real_data = resp_data.get("result") - return real_data diff --git a/bilibili_api/utils/picture.py b/bilibili_api/utils/picture.py index ac464d16..83fe17ea 100644 --- a/bilibili_api/utils/picture.py +++ b/bilibili_api/utils/picture.py @@ -49,7 +49,7 @@ def __set_picture_meta_from_bytes(self, imgtype: str) -> None: with open(img_path, "wb+") as file: file.write(self.content) img = Image.open(img_path) - self.size = img.size + self.size = int(round(os.path.getsize(img_path) / 1024, 0)) self.height = img.height self.width = img.width self.imageType = imgtype @@ -155,7 +155,7 @@ async def upload_file(self, credential: Credential) -> "Picture": """ from ..dynamic import upload_image - res = await upload_image(self, credential) + res = await upload_image(self, credential, data={"biz": "im"}) self.height = res["image_height"] self.width = res["image_width"] self.url = res["image_url"] diff --git a/docs/examples/login.md b/docs/examples/login.md index dba60a5f..2de179b5 100644 --- a/docs/examples/login.md +++ b/docs/examples/login.md @@ -3,7 +3,8 @@ ``` python from bilibili_api import login, user, sync print("请登录:") -credential = login.login_with_qrcode() +credential = login.login_with_qrcode_term() # 在终端扫描二维码登录 +# credential = login.login_with_qrcode() # 使用窗口显示二维码登录 try: credential.raise_for_no_bili_jct() # 判断是否成功 credential.raise_for_no_sessdata() # 判断是否成功 diff --git a/docs/modules/login.md b/docs/modules/login.md index 5ef6ca13..48ee3a52 100644 --- a/docs/modules/login.md +++ b/docs/modules/login.md @@ -9,7 +9,7 @@ from bilibili_api import login --- **注意:** -用 `linux` 的小伙伴在使用 `login_with_qrcode` 时先装一下 `python3-tk` 吧。 +建议 `linux` 的用户使用 `login_with_qrcode_term` 通过终端扫码登录,或者在使用 `login_with_qrcode` 时先装一下 `python3-tk` 吧。 ``` bash $ sudo apt-get install python3-tk @@ -79,6 +79,14 @@ $ sudo apt-get install python3-tk --- +## def login_with_qrcode_term() + +**推荐方式** 扫描终端二维码登录。 + +**Returns:** Credential 凭据类。 + +--- + ## def login_with_qrcode() | name | type | description | @@ -91,6 +99,8 @@ $ sudo apt-get install python3-tk **Returns:** Credential 凭据类。 +--- + ## def login_with_password() | name | type | description | diff --git a/docs/modules/user.md b/docs/modules/user.md index e79f90cc..2e5821a3 100644 --- a/docs/modules/user.md +++ b/docs/modules/user.md @@ -375,7 +375,8 @@ from bilibili_api import user |------|----------------|---------------------------| | pn | int, optional | 页码,从 1 开始. Defaults to 1. | | ps | int, optional | 每页的数据量. Defaults to 100. | -| desc | bool, optional | 倒序排序. Defaults to True. | +| order | OrderType, optional | 排序方式. Defaults to OrderType.desc. | +| attention | bool, optional | 是否采用“最常访问”排序,否则为“关注顺序”排序. Defaults to False. | 获取用户关注列表(不是自己只能访问前5页) diff --git a/requirements.txt b/requirements.txt index 970d76d5..20ac0857 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,8 @@ qrcode~=7.4.2 requests~=2.31.0 APScheduler~=3.10.1 rsa~=4.9 -pillow~=9.5.0 +pillow~=10.0.0 tqdm~=4.65.0 yarl~=1.9.2 pycryptodomex~=3.18.0 +qrcode_terminal~=0.8 \ No newline at end of file