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/_upload.py +3 -3
- p115client/client.py +216 -87
- p115client/const.py +7 -4
- p115client/tool/attr.py +1 -1
- p115client/tool/auth.py +1 -1
- p115client/tool/download.py +15 -23
- p115client/tool/edit.py +2 -2
- p115client/tool/export_dir.py +6 -6
- p115client/tool/fs_files.py +52 -36
- p115client/tool/history.py +2 -2
- p115client/tool/iterdir.py +37 -42
- p115client/tool/life.py +4 -4
- p115client/tool/offline.py +41 -9
- p115client/tool/pool.py +5 -5
- p115client/tool/upload.py +9 -9
- p115client/tool/xys.py +5 -5
- {p115client-0.0.5.12.dist-info → p115client-0.0.5.12.2.dist-info}/METADATA +2 -2
- p115client-0.0.5.12.2.dist-info/RECORD +28 -0
- p115client-0.0.5.12.dist-info/RECORD +0 -28
- {p115client-0.0.5.12.dist-info → p115client-0.0.5.12.2.dist-info}/LICENSE +0 -0
- {p115client-0.0.5.12.dist-info → p115client-0.0.5.12.2.dist-info}/WHEEL +0 -0
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
|
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
|
-
) ->
|
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]
|
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] =
|
1081
|
-
) ->
|
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
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
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
|
-
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
|
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
|
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
|
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/
|
3941
|
+
GET https://proapi.115.com/open/video/play
|
3897
3942
|
|
3898
3943
|
.. admonition:: Reference
|
3899
3944
|
|
3900
|
-
https://www.yuque.com/115yun/open/
|
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/
|
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
|
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
|
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
|
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
|
-
|
3992
|
+
GET https://proapi.115.com/open/video/history
|
3944
3993
|
|
3945
3994
|
.. admonition:: Reference
|
3946
3995
|
|
3947
|
-
https://www.yuque.com/115yun/open/
|
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,
|
4004
|
+
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
3958
4005
|
|
3959
4006
|
@overload
|
3960
|
-
def
|
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
|
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
|
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
|
-
|
3993
|
-
|
3994
|
-
.. admonition:: Reference
|
4039
|
+
POST https://proapi.115.com/open/video/history
|
3995
4040
|
|
3996
|
-
|
4041
|
+
.. admonition:: Reference
|
3997
4042
|
|
3998
|
-
|
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
|
-
-
|
4047
|
+
- time: int = <default> 💡 视频播放进度时长 (单位秒)
|
4048
|
+
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
|
4004
4049
|
"""
|
4005
|
-
api = complete_proapi("/open/video/
|
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,
|
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" 💡
|
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
|
4056
|
-
|
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,
|
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,
|
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/
|
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
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
|
-
即使文件格式不正确或者过大(超过
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
|
-
|
22925
|
-
|
22926
|
-
|
22927
|
-
|
22928
|
-
|
22929
|
-
|
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
|
-
|