p115client 0.0.5.12__py3-none-any.whl → 0.0.5.12.2__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.
p115client/client.py CHANGED
@@ -15,6 +15,7 @@ from collections.abc import (
15
15
  AsyncGenerator, AsyncIterable, Awaitable, Buffer, Callable, Coroutine, Generator,
16
16
  ItemsView, Iterable, Iterator, Mapping, MutableMapping, Sequence,
17
17
  )
18
+ from contextlib import contextmanager
18
19
  from datetime import date, datetime, timedelta
19
20
  from functools import partial
20
21
  from hashlib import md5, sha1
@@ -30,11 +31,12 @@ from platform import system
30
31
  from posixpath import splitext
31
32
  from re import compile as re_compile, MULTILINE
32
33
  from string import digits
34
+ from sys import _getframe
33
35
  from tempfile import TemporaryFile
34
36
  from threading import Lock
35
37
  from time import time
36
38
  from typing import cast, overload, Any, Final, Literal, Self, Unpack
37
- from urllib.parse import quote, unquote, urlencode, urlsplit, urlunsplit
39
+ from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit
38
40
  from uuid import uuid4
39
41
  from warnings import warn
40
42
 
@@ -63,7 +65,10 @@ from startfile import startfile, startfile_async # type: ignore
63
65
  from undefined import undefined
64
66
  from yarl import URL
65
67
 
66
- from .const import CLASS_TO_TYPE, CLIENT_API_MAP, SSOENT_TO_APP, SUFFIX_TO_TYPE, errno
68
+ from .const import (
69
+ CLASS_TO_TYPE, CLIENT_API_METHODS_MAP, CLIENT_METHOD_API_MAP,
70
+ SSOENT_TO_APP, SUFFIX_TO_TYPE, errno,
71
+ )
67
72
  from .exception import (
68
73
  AccessTokenError, AuthenticationError, BusyOSError, DataError, LoginError,
69
74
  OpenAppAuthLimitExceeded, NotSupportedError, P115OSError, OperationalError,
@@ -74,7 +79,6 @@ from ._upload import buffer_length, make_dataiter, oss_upload, oss_multipart_upl
74
79
 
75
80
 
76
81
  CRE_SET_COOKIE: Final = re_compile(r"[0-9a-f]{32}=[0-9a-f]{32}.*")
77
- CRE_CLIENT_API_search: Final = re_compile(r"^ +((?:GET|POST) .*)", MULTILINE).search
78
82
  CRE_COOKIES_UID_search: Final = re_compile(r"(?<=\bUID=)[^\s;]+").search
79
83
  CRE_API_match: Final = re_compile(r"http://(web|pro)api.115.com(?=/|\?|#|$)").match
80
84
  ED2K_NAME_TRANSTAB: Final = dict(zip(b"/|", ("%2F", "%7C")))
@@ -229,6 +233,20 @@ def try_parse_int(
229
233
  return int(s)
230
234
 
231
235
 
236
+ @contextmanager
237
+ def temp_globals(f_globals: None | dict = None, /, **ns):
238
+ if f_globals is None:
239
+ f_globals = _getframe(2).f_globals
240
+ old_globals = f_globals.copy()
241
+ if ns:
242
+ f_globals.update(ns)
243
+ try:
244
+ yield f_globals
245
+ finally:
246
+ f_globals.clear()
247
+ f_globals.update(old_globals)
248
+
249
+
232
250
  def json_loads(content: Buffer, /):
233
251
  try:
234
252
  if isinstance(content, (bytes, bytearray, memoryview)):
@@ -1058,8 +1076,8 @@ def normalize_attr(
1058
1076
  simple: bool = False,
1059
1077
  keep_raw: bool = False,
1060
1078
  *,
1061
- dict_cls: None,
1062
- ) -> dict[str, Any]:
1079
+ dict_cls: None = None,
1080
+ ) -> AttrDict[str, Any]:
1063
1081
  ...
1064
1082
  @overload
1065
1083
  def normalize_attr[D: dict[str, Any]](
@@ -1068,7 +1086,7 @@ def normalize_attr[D: dict[str, Any]](
1068
1086
  simple: bool = False,
1069
1087
  keep_raw: bool = False,
1070
1088
  *,
1071
- dict_cls: type[D] = AttrDict, # type: ignore
1089
+ dict_cls: type[D],
1072
1090
  ) -> D:
1073
1091
  ...
1074
1092
  def normalize_attr[D: dict[str, Any]](
@@ -1077,8 +1095,8 @@ def normalize_attr[D: dict[str, Any]](
1077
1095
  simple: bool = False,
1078
1096
  keep_raw: bool = False,
1079
1097
  *,
1080
- dict_cls: None | type[D] = AttrDict, # type: ignore
1081
- ) -> dict[str, Any] | D:
1098
+ dict_cls: None | type[D] = None,
1099
+ ) -> AttrDict[str, Any] | D:
1082
1100
  """翻译获取自罗列目录、搜索、获取文件信息等接口的数据,使之便于阅读
1083
1101
 
1084
1102
  :param info: 原始数据
@@ -1088,12 +1106,20 @@ def normalize_attr[D: dict[str, Any]](
1088
1106
 
1089
1107
  :return: 翻译后的 dict 类型数据
1090
1108
  """
1091
- if "fn" in info:
1092
- return normalize_attr_app(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1093
- elif "file_id" in info or "category_id" in info:
1094
- return normalize_attr_app2(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1109
+ if dict_cls is None:
1110
+ if "fn" in info:
1111
+ return normalize_attr_app(info, simple=simple, keep_raw=keep_raw, dict_cls=AttrDict)
1112
+ elif "file_id" in info or "category_id" in info:
1113
+ return normalize_attr_app2(info, simple=simple, keep_raw=keep_raw, dict_cls=AttrDict)
1114
+ else:
1115
+ return normalize_attr_web(info, simple=simple, keep_raw=keep_raw, dict_cls=AttrDict)
1095
1116
  else:
1096
- return normalize_attr_web(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1117
+ if "fn" in info:
1118
+ return normalize_attr_app(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1119
+ elif "file_id" in info or "category_id" in info:
1120
+ return normalize_attr_app2(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1121
+ else:
1122
+ return normalize_attr_web(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1097
1123
 
1098
1124
 
1099
1125
  @overload
@@ -1559,6 +1585,9 @@ class ClientRequestMixin:
1559
1585
  ) -> dict | Coroutine[Any, Any, dict]:
1560
1586
  """授权码方式请求开放接口应用授权
1561
1587
 
1588
+ .. note::
1589
+ 最多同时有 2 个授权登录,如果有新的授权加入,会先踢掉时间较早的那一个
1590
+
1562
1591
  GET https://qrcodeapi.115.com/open/authorize
1563
1592
 
1564
1593
  .. admonition:: Reference
@@ -1573,6 +1602,19 @@ class ClientRequestMixin:
1573
1602
  """
1574
1603
  api = complete_api("/open/authorize", base_url=base_url)
1575
1604
  payload = {"response_type": "code", **payload}
1605
+ def parse(resp, content, /):
1606
+ if resp.status_code == 302:
1607
+ return {
1608
+ "state": True,
1609
+ "url": resp.headers["location"],
1610
+ "data": dict(parse_qsl(urlsplit(resp.headers["location"]).query)),
1611
+ "headers": dict(resp.headers),
1612
+ }
1613
+ else:
1614
+ return json_loads(content)
1615
+ request_kwargs["parse"] = parse
1616
+ for key in ("allow_redirects", "follow_redirects", "redirect"):
1617
+ request_kwargs[key] = False
1576
1618
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
1577
1619
 
1578
1620
  @overload
@@ -2068,6 +2110,9 @@ class ClientRequestMixin:
2068
2110
 
2069
2111
  https://www.yuque.com/115yun/open/shtpzfhewv5nag11#WzRhM
2070
2112
 
2113
+ .. note::
2114
+ 最多同时有 2 个授权登录,如果有新的授权加入,会先踢掉时间较早的那一个
2115
+
2071
2116
  .. note::
2072
2117
  code_challenge 默认用的字符串为 64 个 0,hash 算法为 md5
2073
2118
 
@@ -2370,7 +2415,7 @@ class ClientRequestMixin:
2370
2415
  )
2371
2416
  else:
2372
2417
  return qrcode_token
2373
- return run_gen_step(gen_step, may_call=False, async_=async_)
2418
+ return run_gen_step(gen_step, async_)
2374
2419
 
2375
2420
  @overload
2376
2421
  @classmethod
@@ -2470,7 +2515,7 @@ class ClientRequestMixin:
2470
2515
  async_=async_,
2471
2516
  **request_kwargs,
2472
2517
  )
2473
- return run_gen_step(gen_step, may_call=False, async_=async_)
2518
+ return run_gen_step(gen_step, async_)
2474
2519
 
2475
2520
  ########## Upload API ##########
2476
2521
 
@@ -2615,7 +2660,6 @@ class ClientRequestMixin:
2615
2660
 
2616
2661
  :return: 文件的 ed2k 链接
2617
2662
  """
2618
- trantab = dict(zip(b"/|", ("%2F", "%7C")))
2619
2663
  if async_:
2620
2664
  async def request():
2621
2665
  async with self.open(url, headers=headers, async_=True) as file:
@@ -2853,7 +2897,7 @@ class ClientRequestMixin:
2853
2897
  async_=async_,
2854
2898
  **request_kwargs,
2855
2899
  )
2856
- return run_gen_step(gen_step, may_call=False, async_=async_)
2900
+ return run_gen_step(gen_step, async_)
2857
2901
 
2858
2902
  @overload
2859
2903
  def read_bytes_range(
@@ -2968,7 +3012,7 @@ class ClientRequestMixin:
2968
3012
  async_=async_,
2969
3013
  **request_kwargs,
2970
3014
  )
2971
- return run_gen_step(gen_step, may_call=False, async_=async_)
3015
+ return run_gen_step(gen_step, async_)
2972
3016
 
2973
3017
 
2974
3018
  class P115OpenClient(ClientRequestMixin):
@@ -3064,7 +3108,7 @@ class P115OpenClient(ClientRequestMixin):
3064
3108
  self.refresh_token = data["refresh_token"]
3065
3109
  self.access_token = data["access_token"]
3066
3110
  return self
3067
- return run_gen_step(gen_step, may_call=False, async_=async_)
3111
+ return run_gen_step(gen_step, async_)
3068
3112
 
3069
3113
  @classmethod
3070
3114
  def from_token(cls, /, access_token: str, refresh_token: str) -> P115OpenClient:
@@ -3140,7 +3184,7 @@ class P115OpenClient(ClientRequestMixin):
3140
3184
  self.refresh_token = data["refresh_token"]
3141
3185
  self.access_token = data["access_token"]
3142
3186
  return data
3143
- return run_gen_step(gen_step, may_call=False, async_=async_)
3187
+ return run_gen_step(gen_step, async_)
3144
3188
 
3145
3189
  @overload
3146
3190
  def download_url(
@@ -3481,6 +3525,7 @@ class P115OpenClient(ClientRequestMixin):
3481
3525
  - "user_ptime": 创建时间(无效,效果相当于 "user_utime")
3482
3526
  - "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
3483
3527
 
3528
+ - qid: int = <default>
3484
3529
  - r_all: 0 | 1 = <default>
3485
3530
  - record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
3486
3531
  - scid: int | str = <default>
@@ -3861,7 +3906,7 @@ class P115OpenClient(ClientRequestMixin):
3861
3906
  return self.fs_update(payload, async_=async_, **request_kwargs)
3862
3907
 
3863
3908
  @overload
3864
- def fs_video_history(
3909
+ def fs_video(
3865
3910
  self,
3866
3911
  payload: str | dict,
3867
3912
  /,
@@ -3872,7 +3917,7 @@ class P115OpenClient(ClientRequestMixin):
3872
3917
  ) -> dict:
3873
3918
  ...
3874
3919
  @overload
3875
- def fs_video_history(
3920
+ def fs_video(
3876
3921
  self,
3877
3922
  payload: str | dict,
3878
3923
  /,
@@ -3882,7 +3927,7 @@ class P115OpenClient(ClientRequestMixin):
3882
3927
  **request_kwargs,
3883
3928
  ) -> Coroutine[Any, Any, dict]:
3884
3929
  ...
3885
- def fs_video_history(
3930
+ def fs_video(
3886
3931
  self,
3887
3932
  payload: str | dict,
3888
3933
  /,
@@ -3891,24 +3936,28 @@ class P115OpenClient(ClientRequestMixin):
3891
3936
  async_: Literal[False, True] = False,
3892
3937
  **request_kwargs,
3893
3938
  ) -> dict | Coroutine[Any, Any, dict]:
3894
- """获取视频播放进度
3939
+ """获取视频在线播放地址(和视频文件相关数据)
3895
3940
 
3896
- GET https://proapi.115.com/open/video/history
3941
+ GET https://proapi.115.com/open/video/play
3897
3942
 
3898
3943
  .. admonition:: Reference
3899
3944
 
3900
- https://www.yuque.com/115yun/open/gssqdrsq6vfqigag
3945
+ https://www.yuque.com/115yun/open/hqglxv3cedi3p9dz
3946
+
3947
+ .. hint::
3948
+ 需切换音轨时,在请求返回的播放地址中增加请求参数 `&audio_track=${index}`,值就是接口响应中 `multitrack_list` 中某个成员的索引,从 0 开始计数
3901
3949
 
3902
3950
  :payload:
3903
3951
  - pick_code: str 💡 文件提取码
3952
+ - share_id: int | str = <default> 💡 共享 id,获取共享文件播放地址所需
3904
3953
  """
3905
- api = complete_proapi("/open/video/history", base_url)
3954
+ api = complete_proapi("/open/video/play", base_url)
3906
3955
  if isinstance(payload, str):
3907
3956
  payload = {"pick_code": payload}
3908
3957
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
3909
3958
 
3910
3959
  @overload
3911
- def fs_video_history_set(
3960
+ def fs_video_history(
3912
3961
  self,
3913
3962
  payload: str | dict,
3914
3963
  /,
@@ -3919,7 +3968,7 @@ class P115OpenClient(ClientRequestMixin):
3919
3968
  ) -> dict:
3920
3969
  ...
3921
3970
  @overload
3922
- def fs_video_history_set(
3971
+ def fs_video_history(
3923
3972
  self,
3924
3973
  payload: str | dict,
3925
3974
  /,
@@ -3929,7 +3978,7 @@ class P115OpenClient(ClientRequestMixin):
3929
3978
  **request_kwargs,
3930
3979
  ) -> Coroutine[Any, Any, dict]:
3931
3980
  ...
3932
- def fs_video_history_set(
3981
+ def fs_video_history(
3933
3982
  self,
3934
3983
  payload: str | dict,
3935
3984
  /,
@@ -3938,26 +3987,24 @@ class P115OpenClient(ClientRequestMixin):
3938
3987
  async_: Literal[False, True] = False,
3939
3988
  **request_kwargs,
3940
3989
  ) -> dict | Coroutine[Any, Any, dict]:
3941
- """记忆视频播放进度
3990
+ """获取视频播放进度
3942
3991
 
3943
- POST https://proapi.115.com/open/video/history
3992
+ GET https://proapi.115.com/open/video/history
3944
3993
 
3945
3994
  .. admonition:: Reference
3946
3995
 
3947
- https://www.yuque.com/115yun/open/bshagbxv1gzqglg4
3996
+ https://www.yuque.com/115yun/open/gssqdrsq6vfqigag
3948
3997
 
3949
3998
  :payload:
3950
3999
  - pick_code: str 💡 文件提取码
3951
- - time: int = <default> 💡 视频播放进度时长 (单位秒)
3952
- - watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
3953
4000
  """
3954
4001
  api = complete_proapi("/open/video/history", base_url)
3955
4002
  if isinstance(payload, str):
3956
4003
  payload = {"pick_code": payload}
3957
- return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
4004
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
3958
4005
 
3959
4006
  @overload
3960
- def fs_video_play(
4007
+ def fs_video_history_set(
3961
4008
  self,
3962
4009
  payload: str | dict,
3963
4010
  /,
@@ -3968,7 +4015,7 @@ class P115OpenClient(ClientRequestMixin):
3968
4015
  ) -> dict:
3969
4016
  ...
3970
4017
  @overload
3971
- def fs_video_play(
4018
+ def fs_video_history_set(
3972
4019
  self,
3973
4020
  payload: str | dict,
3974
4021
  /,
@@ -3978,7 +4025,7 @@ class P115OpenClient(ClientRequestMixin):
3978
4025
  **request_kwargs,
3979
4026
  ) -> Coroutine[Any, Any, dict]:
3980
4027
  ...
3981
- def fs_video_play(
4028
+ def fs_video_history_set(
3982
4029
  self,
3983
4030
  payload: str | dict,
3984
4031
  /,
@@ -3987,25 +4034,23 @@ class P115OpenClient(ClientRequestMixin):
3987
4034
  async_: Literal[False, True] = False,
3988
4035
  **request_kwargs,
3989
4036
  ) -> dict | Coroutine[Any, Any, dict]:
3990
- """获取视频在线播放地址(和视频文件相关数据)
4037
+ """记忆视频播放进度
3991
4038
 
3992
- GET https://proapi.115.com/open/video/play
3993
-
3994
- .. admonition:: Reference
4039
+ POST https://proapi.115.com/open/video/history
3995
4040
 
3996
- https://www.yuque.com/115yun/open/hqglxv3cedi3p9dz
4041
+ .. admonition:: Reference
3997
4042
 
3998
- .. hint::
3999
- 需切换音轨时,在请求返回的播放地址中增加请求参数 `&audio_track=${index}`,值就是接口响应中 `multitrack_list` 中某个成员的索引,从 0 开始计数
4043
+ https://www.yuque.com/115yun/open/bshagbxv1gzqglg4
4000
4044
 
4001
4045
  :payload:
4002
4046
  - pick_code: str 💡 文件提取码
4003
- - share_id: int | str = <default> 💡 共享 id,获取共享文件播放地址所需
4047
+ - time: int = <default> 💡 视频播放进度时长 (单位秒)
4048
+ - watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
4004
4049
  """
4005
- api = complete_proapi("/open/video/play", base_url)
4050
+ api = complete_proapi("/open/video/history", base_url)
4006
4051
  if isinstance(payload, str):
4007
4052
  payload = {"pick_code": payload}
4008
- return self.request(url=api, params=payload, async_=async_, **request_kwargs)
4053
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
4009
4054
 
4010
4055
  @overload
4011
4056
  def fs_video_push(
@@ -4048,13 +4093,15 @@ class P115OpenClient(ClientRequestMixin):
4048
4093
 
4049
4094
  :payload:
4050
4095
  - pick_code: str 💡 文件提取码
4051
- - op: str = "vip_push" 💡 提交视频加速转码方式:vip_push:根据;vip 等级加速 pay_push:枫叶加速
4096
+ - op: str = "vip_push" 💡 提交视频加速转码方式
4097
+
4098
+ - "vip_push": 根据;vip 等级加速
4099
+ - "pay_push": 枫叶加速
4052
4100
  """
4053
4101
  api = complete_proapi("/open/video/video_push", base_url)
4054
4102
  if isinstance(payload, str):
4055
- payload = {"pick_code": payload, "op": "vip_push"}
4056
- else:
4057
- payload.setdefault("op", "vip_push")
4103
+ payload = {"pick_code": payload}
4104
+ payload.setdefault("op", "vip_push")
4058
4105
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
4059
4106
 
4060
4107
  @overload
@@ -4886,7 +4933,7 @@ class P115OpenClient(ClientRequestMixin):
4886
4933
  check_response(resp)
4887
4934
  resp["data"] = {**payload, **resp["data"], "sha1": filesha1, "cid": pid}
4888
4935
  return resp
4889
- return run_gen_step(gen_step, may_call=False, async_=async_)
4936
+ return run_gen_step(gen_step, async_)
4890
4937
 
4891
4938
  @overload
4892
4939
  def upload_file(
@@ -5231,7 +5278,7 @@ class P115OpenClient(ClientRequestMixin):
5231
5278
  async_=async_, # type: ignore
5232
5279
  **request_kwargs,
5233
5280
  )
5234
- return run_gen_step(gen_step, may_call=False, async_=async_)
5281
+ return run_gen_step(gen_step, async_)
5235
5282
 
5236
5283
  @overload
5237
5284
  def user_info(
@@ -5305,7 +5352,7 @@ class P115OpenClient(ClientRequestMixin):
5305
5352
  ) -> dict | Coroutine[Any, Any, dict]:
5306
5353
  """获取产品列表地址(即引导用户扫码购买 115 的 VIP 服务,以获取提成)
5307
5354
 
5308
- GET https://proapi.115.com/open/open/vip/qr_url
5355
+ GET https://proapi.115.com/open/vip/qr_url
5309
5356
 
5310
5357
  .. admonition:: Reference
5311
5358
 
@@ -5336,9 +5383,9 @@ class P115OpenClient(ClientRequestMixin):
5336
5383
  fs_move_open = fs_move
5337
5384
  fs_search_open = fs_search
5338
5385
  fs_star_set_open = fs_star_set
5386
+ fs_video_open = fs_video
5339
5387
  fs_video_history_open = fs_video_history
5340
5388
  fs_video_history_set_open = fs_video_history_set
5341
- fs_video_play_open = fs_video_play
5342
5389
  fs_video_push_open = fs_video_push
5343
5390
  fs_video_subtitle_open = fs_video_subtitle
5344
5391
  fs_update_open = fs_update
@@ -5365,7 +5412,9 @@ class P115Client(P115OpenClient):
5365
5412
  """115 的客户端对象
5366
5413
 
5367
5414
  .. note::
5368
- 目前允许 1 个用户同时登录多个开放平台应用(用 AppID 区别),也允许多次登录同 1 个应用
5415
+ 目前允许 1 个用户同时登录多个开放平台应用(用 AppID 区别),也允许多次授权登录同 1 个应用
5416
+
5417
+ 目前最多同时有 2 个授权登录登录,如果有新的授权加入,会先踢掉时间较早的那一个
5369
5418
 
5370
5419
  目前不允许短时间内再次用 `refresh_token` 刷新 `access_token`,但你可以用登录的方式再次授权登录以获取 `access_token`,即可不受频率限制
5371
5420
 
@@ -5667,7 +5716,7 @@ class P115Client(P115OpenClient):
5667
5716
  )
5668
5717
  setattr(self, "check_for_relogin", check_for_relogin)
5669
5718
  return self
5670
- return run_gen_step(gen_step, may_call=False, async_=async_)
5719
+ return run_gen_step(gen_step, async_)
5671
5720
 
5672
5721
  @locked_cacheproperty
5673
5722
  def request_lock(self, /) -> Lock:
@@ -5839,7 +5888,7 @@ class P115Client(P115OpenClient):
5839
5888
  check_response(resp)
5840
5889
  setattr(self, "cookies", resp["data"]["cookie"])
5841
5890
  return self
5842
- return run_gen_step(gen_step, may_call=False, async_=async_)
5891
+ return run_gen_step(gen_step, async_)
5843
5892
 
5844
5893
  @overload
5845
5894
  def login_with_app(
@@ -5971,7 +6020,7 @@ class P115Client(P115OpenClient):
5971
6020
  async_=async_,
5972
6021
  **request_kwargs,
5973
6022
  )
5974
- return run_gen_step(gen_step, may_call=False, async_=async_)
6023
+ return run_gen_step(gen_step, async_)
5975
6024
 
5976
6025
  @overload
5977
6026
  def login_without_app(
@@ -6029,7 +6078,7 @@ class P115Client(P115OpenClient):
6029
6078
  )
6030
6079
  check_response(resp)
6031
6080
  return uid
6032
- return run_gen_step(gen_step, may_call=False, async_=async_)
6081
+ return run_gen_step(gen_step, async_)
6033
6082
 
6034
6083
  @overload
6035
6084
  def login_info_open(
@@ -6078,7 +6127,7 @@ class P115Client(P115OpenClient):
6078
6127
  "name": tip_txt[:-10].removeprefix("\ufeff"),
6079
6128
  "icon": resp["data"]["icon"],
6080
6129
  }
6081
- return run_gen_step(gen_step, may_call=False, async_=async_)
6130
+ return run_gen_step(gen_step, async_)
6082
6131
 
6083
6132
  @overload
6084
6133
  def login_with_open(
@@ -6113,6 +6162,9 @@ class P115Client(P115OpenClient):
6113
6162
  ) -> dict | Coroutine[Any, Any, dict]:
6114
6163
  """登录某个开放接口应用
6115
6164
 
6165
+ .. note::
6166
+ 同一个开放应用 id,最多同时有 2 个登入,如果有新的登录,则自动踢掉较早的那一个
6167
+
6116
6168
  :param app_id: AppID
6117
6169
  :param show_warning: 是否显示提示信息
6118
6170
  :param async_: 是否异步
@@ -6130,7 +6182,7 @@ class P115Client(P115OpenClient):
6130
6182
  resp = yield self.login_qrcode_scan_confirm(login_uid, async_=async_, **request_kwargs)
6131
6183
  check_response(resp)
6132
6184
  return self.login_qrcode_access_token_open(login_uid, async_=async_, **request_kwargs)
6133
- return run_gen_step(gen_step, may_call=False, async_=async_)
6185
+ return run_gen_step(gen_step, async_)
6134
6186
 
6135
6187
  @overload
6136
6188
  def login_another_app(
@@ -6276,7 +6328,7 @@ class P115Client(P115OpenClient):
6276
6328
  if self is not inst and ssoent == inst.login_ssoent:
6277
6329
  warn(f"login with the same ssoent {ssoent!r}, {self!r} will expire within 60 seconds", category=P115Warning)
6278
6330
  return inst
6279
- return run_gen_step(gen_step, may_call=False, async_=async_)
6331
+ return run_gen_step(gen_step, async_)
6280
6332
 
6281
6333
  @overload
6282
6334
  def login_another_open(
@@ -6370,7 +6422,7 @@ class P115Client(P115OpenClient):
6370
6422
  inst.access_token = data["access_token"]
6371
6423
  inst.app_id = app_id
6372
6424
  return inst
6373
- return run_gen_step(gen_step, may_call=False, async_=async_)
6425
+ return run_gen_step(gen_step, async_)
6374
6426
 
6375
6427
  @overload
6376
6428
  @classmethod
@@ -6489,7 +6541,7 @@ class P115Client(P115OpenClient):
6489
6541
  resp = yield cls.login_qrcode_scan_result(uid, app, async_=async_, **request_kwargs)
6490
6542
  cookies = check_response(resp)["data"]["cookie"]
6491
6543
  return cls(cookies, check_for_relogin=check_for_relogin)
6492
- return run_gen_step(gen_step, may_call=False, async_=async_)
6544
+ return run_gen_step(gen_step, async_)
6493
6545
 
6494
6546
  @overload
6495
6547
  def logout(
@@ -6772,7 +6824,7 @@ class P115Client(P115OpenClient):
6772
6824
  if check and isinstance(resp, dict):
6773
6825
  check_response(resp)
6774
6826
  return resp
6775
- return run_gen_step(gen_step, may_call=False, async_=async_)
6827
+ return run_gen_step(gen_step, async_)
6776
6828
 
6777
6829
  def request(
6778
6830
  self,
@@ -7664,7 +7716,7 @@ class P115Client(P115OpenClient):
7664
7716
  async_=async_,
7665
7717
  **request_kwargs,
7666
7718
  )
7667
- return run_gen_step(gen_step, may_call=False, async_=async_)
7719
+ return run_gen_step(gen_step, async_)
7668
7720
 
7669
7721
  ########## Download API ##########
7670
7722
 
@@ -9913,7 +9965,7 @@ class P115Client(P115OpenClient):
9913
9965
  :payload:
9914
9966
  - file_id: int | str
9915
9967
  - file_id[]: int | str
9916
- ...
9968
+ - ...
9917
9969
  - file_id[0]: int | str
9918
9970
  - file_id[1]: int | str
9919
9971
  - ...
@@ -10520,7 +10572,7 @@ class P115Client(P115OpenClient):
10520
10572
 
10521
10573
  .. caution::
10522
10574
  这个接口有些问题,当 custom_order=1 时:
10523
-
10575
+
10524
10576
  1. 如果设定 limit=1 可能会报错
10525
10577
  2. fc_mix 无论怎么设置,都和 fc_mix=0 的效果相同(即目录总是置顶),但设置为 custom_order=2 就好了
10526
10578
 
@@ -10960,6 +11012,8 @@ class P115Client(P115OpenClient):
10960
11012
  payload = {"fetch": "one", **payload}
10961
11013
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
10962
11014
 
11015
+ fs_video_history = fs_files_history
11016
+
10963
11017
  @overload
10964
11018
  def fs_files_history_set(
10965
11019
  self,
@@ -11002,6 +11056,7 @@ class P115Client(P115OpenClient):
11002
11056
  - definition: int = <default> 💡 视频清晰度
11003
11057
  - share_id: int | str = <default>
11004
11058
  - time: int = <default> 💡 播放时间点(用来向服务器同步播放进度)
11059
+ - watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
11005
11060
  - ...(其它未找全的参数)
11006
11061
  """
11007
11062
  api = complete_webapi("/files/history", base_url=base_url)
@@ -11011,6 +11066,8 @@ class P115Client(P115OpenClient):
11011
11066
  payload = {"op": "update", **payload}
11012
11067
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
11013
11068
 
11069
+ fs_video_history_set = fs_files_history_set
11070
+
11014
11071
  @overload
11015
11072
  def fs_files_second_type(
11016
11073
  self,
@@ -12114,7 +12171,7 @@ class P115Client(P115OpenClient):
12114
12171
  - offset: int = 0 💡 索引偏移,索引从 0 开始计算
12115
12172
  - is_asc: 0 | 1 = <default> 💡 是否升序排列
12116
12173
  - next: 0 | 1 = <default>
12117
- - order: str = <default> 💡 用某字段排序
12174
+ - order: str = <default> 💡 用某字段排序
12118
12175
 
12119
12176
  - 文件名:"file_name"
12120
12177
  - 文件大小:"file_size"
@@ -12667,6 +12724,7 @@ class P115Client(P115OpenClient):
12667
12724
  为单个文件或目录,设置一个不存在的标签 id,比如 1,会清空标签,但可产生事件(批量设置时无事件,可能是 bug)
12668
12725
 
12669
12726
  .. code:: python
12727
+
12670
12728
  client.fs_label_set(id, 1)
12671
12729
  """
12672
12730
  return self._fs_edit_set(payload, "file_label", label, async_=async_, **request_kwargs)
@@ -12967,9 +13025,10 @@ class P115Client(P115OpenClient):
12967
13025
  """新建目录
12968
13026
 
12969
13027
  .. todo::
12970
- - name: str 💡 目录名
12971
13028
  待破解
12972
13029
 
13030
+ - name: str 💡 目录名
13031
+
12973
13032
  POST https://proapi.115.com/android/1.0/folder/update
12974
13033
  """
12975
13034
  api = complete_proapi("/folder/update", base_url, app)
@@ -13232,7 +13291,7 @@ class P115Client(P115OpenClient):
13232
13291
  GET https://proapi.115.com/android/music/musicplay
13233
13292
 
13234
13293
  .. note::
13235
- 即使文件格式不正确或者过大(超过 200MB),也可返回一些信息(包括 parent_id),但如果是目录则信息匮乏(但由此也可判定一个目录)
13294
+ 即使文件格式不正确或者过大(超过 200 MB),也可返回一些信息(包括 parent_id),但如果是目录则信息匮乏(但由此也可判定一个目录)
13236
13295
 
13237
13296
  :payload:
13238
13297
  - pickcode: str 💡 提取码
@@ -15374,11 +15433,21 @@ class P115Client(P115OpenClient):
15374
15433
 
15375
15434
  GET https://webapi.115.com/files/video
15376
15435
 
15436
+ .. caution::
15437
+ `local` 在有些视频上不起作用,无论如何,都相当于 `local=0`,可能是因为文件超过 200 MB
15438
+
15439
+ 但如果 `local=1` 有效,则返回仅可得到下载链接,key 为 "download_url"
15440
+
15377
15441
  .. important::
15378
15442
  仅这几种设备可用:`harmony`, `web`, `desktop`, **wechatmini**, **alipaymini**, **tv**
15379
15443
 
15380
15444
  但是如果要获取 m3u8 文件,则要提供 web 设备的 cookies,否则返回空数据
15381
15445
 
15446
+ .. note::
15447
+ 如果返回信息中有 "queue_url",则可用于查询转码状态
15448
+
15449
+ 如果视频从未被转码过,则会自动推送转码
15450
+
15382
15451
  :payload:
15383
15452
  - pickcode: str 💡 提取码
15384
15453
  - share_id: int | str = <default> 💡 分享 id
@@ -15650,6 +15719,57 @@ class P115Client(P115OpenClient):
15650
15719
  payload = {"pickcode": payload}
15651
15720
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
15652
15721
 
15722
+ @overload
15723
+ def fs_video_transcode(
15724
+ self,
15725
+ payload: dict | str,
15726
+ /,
15727
+ app: str = "web",
15728
+ base_url: bool | str | Callable[[], str] = False,
15729
+ method: str = "GET",
15730
+ *,
15731
+ async_: Literal[False] = False,
15732
+ **request_kwargs,
15733
+ ) -> dict:
15734
+ ...
15735
+ @overload
15736
+ def fs_video_transcode(
15737
+ self,
15738
+ payload: dict | str,
15739
+ /,
15740
+ app: str = "web",
15741
+ base_url: bool | str | Callable[[], str] = False,
15742
+ method: str = "GET",
15743
+ *,
15744
+ async_: Literal[True],
15745
+ **request_kwargs,
15746
+ ) -> Coroutine[Any, Any, dict]:
15747
+ ...
15748
+ def fs_video_transcode(
15749
+ self,
15750
+ payload: dict | str,
15751
+ /,
15752
+ app: str = "web",
15753
+ base_url: bool | str | Callable[[], str] = False,
15754
+ method: str = "GET",
15755
+ *,
15756
+ async_: Literal[False, True] = False,
15757
+ **request_kwargs,
15758
+ ) -> dict | Coroutine[Any, Any, dict]:
15759
+ """获取视频的转码进度
15760
+
15761
+ GET http://transcode.115.com/api/1.0/android/1.0/trans_code/check_transcode_job
15762
+
15763
+ :payload:
15764
+ - sha1: str
15765
+ - priority: int = 100 💡 优先级
15766
+ """
15767
+ api = complete_api(f"/api/1.0/{app}/1.0/trans_code/check_transcode_job", "transcode", base_url=base_url)
15768
+ if isinstance(payload, str):
15769
+ payload = {"sha1": payload}
15770
+ payload.setdefault("priority", 100)
15771
+ return self.request(url=api, method=method, params=payload, async_=async_, **request_kwargs)
15772
+
15653
15773
  ########## Life API ##########
15654
15774
 
15655
15775
  @overload
@@ -16299,7 +16419,7 @@ class P115Client(P115OpenClient):
16299
16419
  if device is None:
16300
16420
  return None
16301
16421
  return device["icon"]
16302
- return run_gen_step(gen_step, may_call=False, async_=async_)
16422
+ return run_gen_step(gen_step, async_)
16303
16423
 
16304
16424
  @overload
16305
16425
  def login_open_auth_detail(
@@ -16823,7 +16943,7 @@ class P115Client(P115OpenClient):
16823
16943
  return get_default_request()(url=api, async_=async_, **request_kwargs)
16824
16944
  else:
16825
16945
  return request(url=api, **request_kwargs)
16826
- return run_gen_step(gen_step, may_call=False, async_=async_)
16946
+ return run_gen_step(gen_step, async_)
16827
16947
 
16828
16948
  @overload
16829
16949
  def logout_by_ssoent(
@@ -21020,7 +21140,7 @@ class P115Client(P115OpenClient):
21020
21140
  if resp["state"]:
21021
21141
  self.user_key = resp["data"]["userkey"]
21022
21142
  return resp
21023
- return run_gen_step(gen_step, may_call=False, async_=async_)
21143
+ return run_gen_step(gen_step, async_)
21024
21144
 
21025
21145
  @overload
21026
21146
  def upload_resume(
@@ -21387,7 +21507,7 @@ class P115Client(P115OpenClient):
21387
21507
  "pickcode": resp["pickcode"],
21388
21508
  }
21389
21509
  return resp
21390
- return run_gen_step(gen_step, may_call=False, async_=async_)
21510
+ return run_gen_step(gen_step, async_)
21391
21511
 
21392
21512
  @overload
21393
21513
  def upload_file_sample(
@@ -21523,7 +21643,7 @@ class P115Client(P115OpenClient):
21523
21643
  async_=async_,
21524
21644
  **request_kwargs,
21525
21645
  )
21526
- return run_gen_step(gen_step, may_call=False, async_=async_)
21646
+ return run_gen_step(gen_step, async_)
21527
21647
 
21528
21648
  # TODO: 分块上传时,允许一定次数的重试
21529
21649
  # TODO: 不妨单独为分块上传做一个封装
@@ -21885,7 +22005,7 @@ class P115Client(P115OpenClient):
21885
22005
  async_=async_, # type: ignore
21886
22006
  **request_kwargs,
21887
22007
  )
21888
- return run_gen_step(gen_step, may_call=False, async_=async_)
22008
+ return run_gen_step(gen_step, async_)
21889
22009
 
21890
22010
  ########## User API ##########
21891
22011
 
@@ -22921,13 +23041,22 @@ class P115Client(P115OpenClient):
22921
23041
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
22922
23042
 
22923
23043
 
22924
- for name, method in P115Client.__dict__.items():
22925
- if not (callable(method) and method.__doc__):
22926
- continue
22927
- match = CRE_CLIENT_API_search(method.__doc__)
22928
- if match is not None:
22929
- CLIENT_API_MAP[match[1]] = "P115Client." + name
23044
+ with temp_globals():
23045
+ CRE_CLIENT_API_search: Final = re_compile(r"^ +((?:GET|POST|PUT|DELETE|PATCH) .*)", MULTILINE).search
23046
+ for name in dir(P115Client):
23047
+ method = getattr(P115Client, name)
23048
+ if not (callable(method) and method.__doc__):
23049
+ continue
23050
+ match = CRE_CLIENT_API_search(method.__doc__)
23051
+ if match is not None:
23052
+ api = match[1]
23053
+ name = "P115Client." + name
23054
+ CLIENT_METHOD_API_MAP[name] = api
23055
+ try:
23056
+ CLIENT_API_METHODS_MAP[api].append(name)
23057
+ except KeyError:
23058
+ CLIENT_API_METHODS_MAP[api] = [name]
23059
+
22930
23060
 
22931
23061
  # TODO: 提供一个可随时终止和暂停的上传功能,并且可以输出进度条和获取进度
22932
23062
  # TODO: 更新一下,p115client._upload,做更多的封装,至少让断点续传更易于使用
22933
-