p115client 0.0.5.12__tar.gz → 0.0.5.12.1__tar.gz
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-0.0.5.12 → p115client-0.0.5.12.1}/PKG-INFO +1 -1
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/client.py +156 -48
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/const.py +7 -4
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/offline.py +39 -7
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/pyproject.toml +1 -1
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/LICENSE +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/__init__.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/_upload.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/exception.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/py.typed +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/__init__.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/attr.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/auth.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/download.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/edit.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/export_dir.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/fs_files.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/history.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/iterdir.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/life.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/pool.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/request.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/upload.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/util.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/tool/xys.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/p115client/type.py +0 -0
- {p115client-0.0.5.12 → p115client-0.0.5.12.1}/readme.md +0 -0
@@ -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)):
|
@@ -1573,6 +1591,19 @@ class ClientRequestMixin:
|
|
1573
1591
|
"""
|
1574
1592
|
api = complete_api("/open/authorize", base_url=base_url)
|
1575
1593
|
payload = {"response_type": "code", **payload}
|
1594
|
+
def parse(resp, content, /):
|
1595
|
+
if resp.status_code == 302:
|
1596
|
+
return {
|
1597
|
+
"state": True,
|
1598
|
+
"url": resp.headers["location"],
|
1599
|
+
"data": dict(parse_qsl(urlsplit(resp.headers["location"]).query)),
|
1600
|
+
"headers": dict(resp.headers),
|
1601
|
+
}
|
1602
|
+
else:
|
1603
|
+
return json_loads(content)
|
1604
|
+
request_kwargs["parse"] = parse
|
1605
|
+
for key in ("allow_redirects", "follow_redirects", "redirect"):
|
1606
|
+
request_kwargs[key] = False
|
1576
1607
|
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
1577
1608
|
|
1578
1609
|
@overload
|
@@ -2615,7 +2646,6 @@ class ClientRequestMixin:
|
|
2615
2646
|
|
2616
2647
|
:return: 文件的 ed2k 链接
|
2617
2648
|
"""
|
2618
|
-
trantab = dict(zip(b"/|", ("%2F", "%7C")))
|
2619
2649
|
if async_:
|
2620
2650
|
async def request():
|
2621
2651
|
async with self.open(url, headers=headers, async_=True) as file:
|
@@ -3481,6 +3511,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3481
3511
|
- "user_ptime": 创建时间(无效,效果相当于 "user_utime")
|
3482
3512
|
- "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
|
3483
3513
|
|
3514
|
+
- qid: int = <default>
|
3484
3515
|
- r_all: 0 | 1 = <default>
|
3485
3516
|
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
|
3486
3517
|
- scid: int | str = <default>
|
@@ -3861,7 +3892,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3861
3892
|
return self.fs_update(payload, async_=async_, **request_kwargs)
|
3862
3893
|
|
3863
3894
|
@overload
|
3864
|
-
def
|
3895
|
+
def fs_video(
|
3865
3896
|
self,
|
3866
3897
|
payload: str | dict,
|
3867
3898
|
/,
|
@@ -3872,7 +3903,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3872
3903
|
) -> dict:
|
3873
3904
|
...
|
3874
3905
|
@overload
|
3875
|
-
def
|
3906
|
+
def fs_video(
|
3876
3907
|
self,
|
3877
3908
|
payload: str | dict,
|
3878
3909
|
/,
|
@@ -3882,7 +3913,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3882
3913
|
**request_kwargs,
|
3883
3914
|
) -> Coroutine[Any, Any, dict]:
|
3884
3915
|
...
|
3885
|
-
def
|
3916
|
+
def fs_video(
|
3886
3917
|
self,
|
3887
3918
|
payload: str | dict,
|
3888
3919
|
/,
|
@@ -3891,24 +3922,28 @@ class P115OpenClient(ClientRequestMixin):
|
|
3891
3922
|
async_: Literal[False, True] = False,
|
3892
3923
|
**request_kwargs,
|
3893
3924
|
) -> dict | Coroutine[Any, Any, dict]:
|
3894
|
-
"""
|
3895
|
-
|
3896
|
-
GET https://proapi.115.com/open/video/history
|
3925
|
+
"""获取视频在线播放地址(和视频文件相关数据)
|
3897
3926
|
|
3927
|
+
GET https://proapi.115.com/open/video/play
|
3928
|
+
|
3898
3929
|
.. admonition:: Reference
|
3899
3930
|
|
3900
|
-
https://www.yuque.com/115yun/open/
|
3931
|
+
https://www.yuque.com/115yun/open/hqglxv3cedi3p9dz
|
3932
|
+
|
3933
|
+
.. hint::
|
3934
|
+
需切换音轨时,在请求返回的播放地址中增加请求参数 `&audio_track=${index}`,值就是接口响应中 `multitrack_list` 中某个成员的索引,从 0 开始计数
|
3901
3935
|
|
3902
3936
|
:payload:
|
3903
3937
|
- pick_code: str 💡 文件提取码
|
3938
|
+
- share_id: int | str = <default> 💡 共享 id,获取共享文件播放地址所需
|
3904
3939
|
"""
|
3905
|
-
api = complete_proapi("/open/video/
|
3940
|
+
api = complete_proapi("/open/video/play", base_url)
|
3906
3941
|
if isinstance(payload, str):
|
3907
3942
|
payload = {"pick_code": payload}
|
3908
3943
|
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
3909
3944
|
|
3910
3945
|
@overload
|
3911
|
-
def
|
3946
|
+
def fs_video_history(
|
3912
3947
|
self,
|
3913
3948
|
payload: str | dict,
|
3914
3949
|
/,
|
@@ -3919,7 +3954,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3919
3954
|
) -> dict:
|
3920
3955
|
...
|
3921
3956
|
@overload
|
3922
|
-
def
|
3957
|
+
def fs_video_history(
|
3923
3958
|
self,
|
3924
3959
|
payload: str | dict,
|
3925
3960
|
/,
|
@@ -3929,7 +3964,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3929
3964
|
**request_kwargs,
|
3930
3965
|
) -> Coroutine[Any, Any, dict]:
|
3931
3966
|
...
|
3932
|
-
def
|
3967
|
+
def fs_video_history(
|
3933
3968
|
self,
|
3934
3969
|
payload: str | dict,
|
3935
3970
|
/,
|
@@ -3938,26 +3973,24 @@ class P115OpenClient(ClientRequestMixin):
|
|
3938
3973
|
async_: Literal[False, True] = False,
|
3939
3974
|
**request_kwargs,
|
3940
3975
|
) -> dict | Coroutine[Any, Any, dict]:
|
3941
|
-
"""
|
3976
|
+
"""获取视频播放进度
|
3942
3977
|
|
3943
|
-
|
3978
|
+
GET https://proapi.115.com/open/video/history
|
3944
3979
|
|
3945
3980
|
.. admonition:: Reference
|
3946
3981
|
|
3947
|
-
https://www.yuque.com/115yun/open/
|
3982
|
+
https://www.yuque.com/115yun/open/gssqdrsq6vfqigag
|
3948
3983
|
|
3949
3984
|
:payload:
|
3950
3985
|
- pick_code: str 💡 文件提取码
|
3951
|
-
- time: int = <default> 💡 视频播放进度时长 (单位秒)
|
3952
|
-
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
|
3953
3986
|
"""
|
3954
3987
|
api = complete_proapi("/open/video/history", base_url)
|
3955
3988
|
if isinstance(payload, str):
|
3956
3989
|
payload = {"pick_code": payload}
|
3957
|
-
return self.request(url=api,
|
3990
|
+
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
3958
3991
|
|
3959
3992
|
@overload
|
3960
|
-
def
|
3993
|
+
def fs_video_history_set(
|
3961
3994
|
self,
|
3962
3995
|
payload: str | dict,
|
3963
3996
|
/,
|
@@ -3968,7 +4001,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3968
4001
|
) -> dict:
|
3969
4002
|
...
|
3970
4003
|
@overload
|
3971
|
-
def
|
4004
|
+
def fs_video_history_set(
|
3972
4005
|
self,
|
3973
4006
|
payload: str | dict,
|
3974
4007
|
/,
|
@@ -3978,7 +4011,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3978
4011
|
**request_kwargs,
|
3979
4012
|
) -> Coroutine[Any, Any, dict]:
|
3980
4013
|
...
|
3981
|
-
def
|
4014
|
+
def fs_video_history_set(
|
3982
4015
|
self,
|
3983
4016
|
payload: str | dict,
|
3984
4017
|
/,
|
@@ -3987,25 +4020,23 @@ class P115OpenClient(ClientRequestMixin):
|
|
3987
4020
|
async_: Literal[False, True] = False,
|
3988
4021
|
**request_kwargs,
|
3989
4022
|
) -> dict | Coroutine[Any, Any, dict]:
|
3990
|
-
"""
|
4023
|
+
"""记忆视频播放进度
|
3991
4024
|
|
3992
|
-
|
3993
|
-
|
3994
|
-
.. admonition:: Reference
|
4025
|
+
POST https://proapi.115.com/open/video/history
|
3995
4026
|
|
3996
|
-
|
4027
|
+
.. admonition:: Reference
|
3997
4028
|
|
3998
|
-
|
3999
|
-
需切换音轨时,在请求返回的播放地址中增加请求参数 `&audio_track=${index}`,值就是接口响应中 `multitrack_list` 中某个成员的索引,从 0 开始计数
|
4029
|
+
https://www.yuque.com/115yun/open/bshagbxv1gzqglg4
|
4000
4030
|
|
4001
4031
|
:payload:
|
4002
4032
|
- pick_code: str 💡 文件提取码
|
4003
|
-
-
|
4033
|
+
- time: int = <default> 💡 视频播放进度时长 (单位秒)
|
4034
|
+
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
|
4004
4035
|
"""
|
4005
|
-
api = complete_proapi("/open/video/
|
4036
|
+
api = complete_proapi("/open/video/history", base_url)
|
4006
4037
|
if isinstance(payload, str):
|
4007
4038
|
payload = {"pick_code": payload}
|
4008
|
-
return self.request(url=api,
|
4039
|
+
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
4009
4040
|
|
4010
4041
|
@overload
|
4011
4042
|
def fs_video_push(
|
@@ -4048,13 +4079,15 @@ class P115OpenClient(ClientRequestMixin):
|
|
4048
4079
|
|
4049
4080
|
:payload:
|
4050
4081
|
- pick_code: str 💡 文件提取码
|
4051
|
-
- op: str = "vip_push" 💡
|
4082
|
+
- op: str = "vip_push" 💡 提交视频加速转码方式
|
4083
|
+
|
4084
|
+
- "vip_push": 根据;vip 等级加速
|
4085
|
+
- "pay_push": 枫叶加速
|
4052
4086
|
"""
|
4053
4087
|
api = complete_proapi("/open/video/video_push", base_url)
|
4054
4088
|
if isinstance(payload, str):
|
4055
|
-
payload = {"pick_code": payload
|
4056
|
-
|
4057
|
-
payload.setdefault("op", "vip_push")
|
4089
|
+
payload = {"pick_code": payload}
|
4090
|
+
payload.setdefault("op", "vip_push")
|
4058
4091
|
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
4059
4092
|
|
4060
4093
|
@overload
|
@@ -5305,7 +5338,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
5305
5338
|
) -> dict | Coroutine[Any, Any, dict]:
|
5306
5339
|
"""获取产品列表地址(即引导用户扫码购买 115 的 VIP 服务,以获取提成)
|
5307
5340
|
|
5308
|
-
GET https://proapi.115.com/open/
|
5341
|
+
GET https://proapi.115.com/open/vip/qr_url
|
5309
5342
|
|
5310
5343
|
.. admonition:: Reference
|
5311
5344
|
|
@@ -5336,9 +5369,9 @@ class P115OpenClient(ClientRequestMixin):
|
|
5336
5369
|
fs_move_open = fs_move
|
5337
5370
|
fs_search_open = fs_search
|
5338
5371
|
fs_star_set_open = fs_star_set
|
5372
|
+
fs_video_open = fs_video
|
5339
5373
|
fs_video_history_open = fs_video_history
|
5340
5374
|
fs_video_history_set_open = fs_video_history_set
|
5341
|
-
fs_video_play_open = fs_video_play
|
5342
5375
|
fs_video_push_open = fs_video_push
|
5343
5376
|
fs_video_subtitle_open = fs_video_subtitle
|
5344
5377
|
fs_update_open = fs_update
|
@@ -10960,6 +10993,8 @@ class P115Client(P115OpenClient):
|
|
10960
10993
|
payload = {"fetch": "one", **payload}
|
10961
10994
|
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
10962
10995
|
|
10996
|
+
fs_video_history = fs_files_history
|
10997
|
+
|
10963
10998
|
@overload
|
10964
10999
|
def fs_files_history_set(
|
10965
11000
|
self,
|
@@ -11002,6 +11037,7 @@ class P115Client(P115OpenClient):
|
|
11002
11037
|
- definition: int = <default> 💡 视频清晰度
|
11003
11038
|
- share_id: int | str = <default>
|
11004
11039
|
- time: int = <default> 💡 播放时间点(用来向服务器同步播放进度)
|
11040
|
+
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
|
11005
11041
|
- ...(其它未找全的参数)
|
11006
11042
|
"""
|
11007
11043
|
api = complete_webapi("/files/history", base_url=base_url)
|
@@ -11011,6 +11047,8 @@ class P115Client(P115OpenClient):
|
|
11011
11047
|
payload = {"op": "update", **payload}
|
11012
11048
|
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
11013
11049
|
|
11050
|
+
fs_video_history_set = fs_files_history_set
|
11051
|
+
|
11014
11052
|
@overload
|
11015
11053
|
def fs_files_second_type(
|
11016
11054
|
self,
|
@@ -13232,7 +13270,7 @@ class P115Client(P115OpenClient):
|
|
13232
13270
|
GET https://proapi.115.com/android/music/musicplay
|
13233
13271
|
|
13234
13272
|
.. note::
|
13235
|
-
即使文件格式不正确或者过大(超过
|
13273
|
+
即使文件格式不正确或者过大(超过 200 MB),也可返回一些信息(包括 parent_id),但如果是目录则信息匮乏(但由此也可判定一个目录)
|
13236
13274
|
|
13237
13275
|
:payload:
|
13238
13276
|
- pickcode: str 💡 提取码
|
@@ -15374,11 +15412,21 @@ class P115Client(P115OpenClient):
|
|
15374
15412
|
|
15375
15413
|
GET https://webapi.115.com/files/video
|
15376
15414
|
|
15415
|
+
.. caution::
|
15416
|
+
`local` 在有些视频上不起作用,无论如何,都相当于 `local=0`,可能是因为文件超过 200 MB
|
15417
|
+
|
15418
|
+
但如果 `local=1` 有效,则返回仅可得到下载链接,key 为 "download_url"
|
15419
|
+
|
15377
15420
|
.. important::
|
15378
15421
|
仅这几种设备可用:`harmony`, `web`, `desktop`, **wechatmini**, **alipaymini**, **tv**
|
15379
15422
|
|
15380
15423
|
但是如果要获取 m3u8 文件,则要提供 web 设备的 cookies,否则返回空数据
|
15381
15424
|
|
15425
|
+
.. note::
|
15426
|
+
如果返回信息中有 "queue_url",则可用于查询转码状态
|
15427
|
+
|
15428
|
+
如果视频从未被转码过,则会自动推送转码
|
15429
|
+
|
15382
15430
|
:payload:
|
15383
15431
|
- pickcode: str 💡 提取码
|
15384
15432
|
- share_id: int | str = <default> 💡 分享 id
|
@@ -15650,6 +15698,57 @@ class P115Client(P115OpenClient):
|
|
15650
15698
|
payload = {"pickcode": payload}
|
15651
15699
|
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
15652
15700
|
|
15701
|
+
@overload
|
15702
|
+
def fs_video_transcode(
|
15703
|
+
self,
|
15704
|
+
payload: dict | str,
|
15705
|
+
/,
|
15706
|
+
app: str = "web",
|
15707
|
+
base_url: bool | str | Callable[[], str] = False,
|
15708
|
+
method: str = "GET",
|
15709
|
+
*,
|
15710
|
+
async_: Literal[False] = False,
|
15711
|
+
**request_kwargs,
|
15712
|
+
) -> dict:
|
15713
|
+
...
|
15714
|
+
@overload
|
15715
|
+
def fs_video_transcode(
|
15716
|
+
self,
|
15717
|
+
payload: dict | str,
|
15718
|
+
/,
|
15719
|
+
app: str = "web",
|
15720
|
+
base_url: bool | str | Callable[[], str] = False,
|
15721
|
+
method: str = "GET",
|
15722
|
+
*,
|
15723
|
+
async_: Literal[True],
|
15724
|
+
**request_kwargs,
|
15725
|
+
) -> Coroutine[Any, Any, dict]:
|
15726
|
+
...
|
15727
|
+
def fs_video_transcode(
|
15728
|
+
self,
|
15729
|
+
payload: dict | str,
|
15730
|
+
/,
|
15731
|
+
app: str = "web",
|
15732
|
+
base_url: bool | str | Callable[[], str] = False,
|
15733
|
+
method: str = "GET",
|
15734
|
+
*,
|
15735
|
+
async_: Literal[False, True] = False,
|
15736
|
+
**request_kwargs,
|
15737
|
+
) -> dict | Coroutine[Any, Any, dict]:
|
15738
|
+
"""获取视频的转码进度
|
15739
|
+
|
15740
|
+
GET http://transcode.115.com/api/1.0/android/1.0/trans_code/check_transcode_job
|
15741
|
+
|
15742
|
+
:payload:
|
15743
|
+
- sha1: str
|
15744
|
+
- priority: int = 100 💡 优先级
|
15745
|
+
"""
|
15746
|
+
api = complete_api(f"/api/1.0/{app}/1.0/trans_code/check_transcode_job", "transcode", base_url=base_url)
|
15747
|
+
if isinstance(payload, str):
|
15748
|
+
payload = {"sha1": payload}
|
15749
|
+
payload.setdefault("priority", 100)
|
15750
|
+
return self.request(url=api, method=method, params=payload, async_=async_, **request_kwargs)
|
15751
|
+
|
15653
15752
|
########## Life API ##########
|
15654
15753
|
|
15655
15754
|
@overload
|
@@ -22921,13 +23020,22 @@ class P115Client(P115OpenClient):
|
|
22921
23020
|
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
22922
23021
|
|
22923
23022
|
|
22924
|
-
|
22925
|
-
|
22926
|
-
|
22927
|
-
|
22928
|
-
|
22929
|
-
|
23023
|
+
with temp_globals():
|
23024
|
+
CRE_CLIENT_API_search: Final = re_compile(r"^ +((?:GET|POST|PUT|DELETE|PATCH) .*)", MULTILINE).search
|
23025
|
+
for name in dir(P115Client):
|
23026
|
+
method = getattr(P115Client, name)
|
23027
|
+
if not (callable(method) and method.__doc__):
|
23028
|
+
continue
|
23029
|
+
match = CRE_CLIENT_API_search(method.__doc__)
|
23030
|
+
if match is not None:
|
23031
|
+
api = match[1]
|
23032
|
+
name = "P115Client." + name
|
23033
|
+
CLIENT_METHOD_API_MAP[name] = api
|
23034
|
+
try:
|
23035
|
+
CLIENT_API_METHODS_MAP[api].append(name)
|
23036
|
+
except KeyError:
|
23037
|
+
CLIENT_API_METHODS_MAP[api] = [name]
|
23038
|
+
|
22930
23039
|
|
22931
23040
|
# TODO: 提供一个可随时终止和暂停的上传功能,并且可以输出进度条和获取进度
|
22932
23041
|
# TODO: 更新一下,p115client._upload,做更多的封装,至少让断点续传更易于使用
|
22933
|
-
|
@@ -5,8 +5,8 @@ from __future__ import annotations
|
|
5
5
|
|
6
6
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
7
7
|
__all__ = [
|
8
|
-
"AVAILABLE_APPS", "APP_TO_SSOENT", "SSOENT_TO_APP", "
|
9
|
-
"CLASS_TO_TYPE", "SUFFIX_TO_TYPE", "errno",
|
8
|
+
"AVAILABLE_APPS", "APP_TO_SSOENT", "SSOENT_TO_APP", "CLIENT_METHOD_API_MAP",
|
9
|
+
"CLIENT_API_METHODS_MAP", "CLASS_TO_TYPE", "SUFFIX_TO_TYPE", "errno",
|
10
10
|
]
|
11
11
|
|
12
12
|
from enum import IntEnum
|
@@ -69,8 +69,11 @@ SSOENT_TO_APP: Final[dict[str, str]] = {
|
|
69
69
|
"S1": "harmony",
|
70
70
|
}
|
71
71
|
|
72
|
-
#:
|
73
|
-
|
72
|
+
#: 所有已封装的方法名和对应的 115 接口
|
73
|
+
CLIENT_METHOD_API_MAP: Final[dict[str, str]] = {}
|
74
|
+
|
75
|
+
#: 所有已封装的 115 接口和对应的方法名
|
76
|
+
CLIENT_API_METHODS_MAP: Final[dict[str, list[str]]] = {}
|
74
77
|
|
75
78
|
#: 文件的 class 属性对应的所属类型的整数代码
|
76
79
|
CLASS_TO_TYPE: Final[dict[str, int]] = {
|
@@ -7,12 +7,14 @@ __doc__ = "这个模块提供了一些和离线下载有关的函数"
|
|
7
7
|
|
8
8
|
from asyncio import sleep as async_sleep
|
9
9
|
from collections.abc import AsyncIterator, Callable, Iterable, Iterator
|
10
|
+
from errno import EBUSY
|
10
11
|
from itertools import count
|
11
12
|
from time import sleep, time
|
12
13
|
from typing import overload, Literal
|
13
14
|
|
14
15
|
from iterutils import run_gen_step_iter, with_iter_next, Yield, YieldFrom
|
15
16
|
from p115client import check_response, P115Client, P115OpenClient
|
17
|
+
from p115client.exception import BusyOSError
|
16
18
|
|
17
19
|
|
18
20
|
@overload
|
@@ -22,7 +24,8 @@ def offline_iter(
|
|
22
24
|
page_start: int = 1,
|
23
25
|
page_stop: int = -1,
|
24
26
|
cooldown: float = 0,
|
25
|
-
|
27
|
+
raise_for_update: bool = False,
|
28
|
+
use_open_api: bool = False,
|
26
29
|
*,
|
27
30
|
async_: Literal[False] = False,
|
28
31
|
**request_kwargs,
|
@@ -35,6 +38,7 @@ def offline_iter(
|
|
35
38
|
page_start: int = 1,
|
36
39
|
page_stop: int = -1,
|
37
40
|
cooldown: float = 0,
|
41
|
+
raise_for_update: bool = False,
|
38
42
|
use_open_api: bool = False,
|
39
43
|
*,
|
40
44
|
async_: Literal[True],
|
@@ -47,6 +51,7 @@ def offline_iter(
|
|
47
51
|
page_start: int = 1,
|
48
52
|
page_stop: int = -1,
|
49
53
|
cooldown: float = 0,
|
54
|
+
raise_for_update: bool = False,
|
50
55
|
use_open_api: bool = False,
|
51
56
|
*,
|
52
57
|
async_: Literal[False, True] = False,
|
@@ -54,10 +59,18 @@ def offline_iter(
|
|
54
59
|
) -> Iterator[dict] | AsyncIterator[dict]:
|
55
60
|
"""遍历任务列表,获取任务信息
|
56
61
|
|
62
|
+
.. tip::
|
63
|
+
在逐页拉取的间隔期间,任务列表可能发生变化,可能导致重复和遗漏:
|
64
|
+
|
65
|
+
1. 新增任务,特别是状态为进行中
|
66
|
+
2. 删除任务
|
67
|
+
3. 曾经取得的进行中的任务,变为完成
|
68
|
+
|
57
69
|
:param client: 115 客户端或 cookies
|
58
70
|
:param page_start: 开始页数
|
59
71
|
:param page_stop: 结束页数(不含),如果 <= 0,则不限
|
60
72
|
:param cooldown: 接口调用冷却时间,单位:秒
|
73
|
+
:param raise_for_update: 当列表发生更新时,是否报错退出
|
61
74
|
:param use_open_api: 是否使用 open api
|
62
75
|
:param async_: 是否异步
|
63
76
|
:param request_kwargs: 其它请求参数
|
@@ -79,21 +92,40 @@ def offline_iter(
|
|
79
92
|
offline_list = client.offline_list_open
|
80
93
|
else:
|
81
94
|
offline_list = client.offline_list
|
82
|
-
|
95
|
+
may_sleep = cooldown > 0
|
96
|
+
if may_sleep:
|
83
97
|
do_sleep = async_sleep if async_ else sleep
|
84
|
-
|
98
|
+
last_t: float = 0
|
99
|
+
if raise_for_update:
|
100
|
+
count = -1
|
101
|
+
seen: set[str] = set()
|
102
|
+
add_info_hash = seen.add
|
85
103
|
for page in pages:
|
86
|
-
if
|
87
|
-
|
88
|
-
|
104
|
+
if may_sleep:
|
105
|
+
if last_t and (diff := last_t + cooldown - time()) > 0:
|
106
|
+
yield do_sleep(diff)
|
107
|
+
last_t = time()
|
89
108
|
resp = yield offline_list(page, async_=async_, **request_kwargs)
|
90
109
|
check_response(resp)
|
91
110
|
if use_open_api:
|
92
111
|
resp = resp["data"]
|
112
|
+
if raise_for_update:
|
113
|
+
if count < 0:
|
114
|
+
count = resp["count"]
|
115
|
+
elif count != resp["count"]:
|
116
|
+
raise BusyOSError(EBUSY, f"detected count changes: {count} != {resp['count']}")
|
93
117
|
tasks = resp["tasks"]
|
94
118
|
if not tasks:
|
95
119
|
break
|
96
|
-
|
120
|
+
if raise_for_update:
|
121
|
+
for task in tasks:
|
122
|
+
info_hash = task["info_hash"]
|
123
|
+
if info_hash in seen:
|
124
|
+
raise BusyOSError(EBUSY, f"detected duplicate task: info_hash={info_hash!r}")
|
125
|
+
add_info_hash(info_hash)
|
126
|
+
yield Yield(task)
|
127
|
+
else:
|
128
|
+
yield YieldFrom(resp["tasks"])
|
97
129
|
if len(tasks) < 30 or page >= resp["page_count"]:
|
98
130
|
break
|
99
131
|
return run_gen_step_iter(gen_step, async_=async_)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|