p115client 0.0.5.8.2__py3-none-any.whl → 0.0.5.8.4__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 +75 -51
- p115client/client.py +332 -204
- p115client/tool/download.py +180 -41
- {p115client-0.0.5.8.2.dist-info → p115client-0.0.5.8.4.dist-info}/METADATA +2 -2
- {p115client-0.0.5.8.2.dist-info → p115client-0.0.5.8.4.dist-info}/RECORD +7 -7
- {p115client-0.0.5.8.2.dist-info → p115client-0.0.5.8.4.dist-info}/LICENSE +0 -0
- {p115client-0.0.5.8.2.dist-info → p115client-0.0.5.8.4.dist-info}/WHEEL +0 -0
p115client/client.py
CHANGED
@@ -9,15 +9,14 @@ __all__ = [
|
|
9
9
|
"normalize_attr_app", "normalize_attr_app2", "P115OpenClient", "P115Client",
|
10
10
|
]
|
11
11
|
|
12
|
-
import
|
13
|
-
|
14
|
-
from asyncio import create_task, get_running_loop, run_coroutine_threadsafe, to_thread, Lock as AsyncLock
|
12
|
+
from asyncio import Lock as AsyncLock
|
15
13
|
from base64 import b64encode
|
16
14
|
from collections.abc import (
|
17
15
|
AsyncGenerator, AsyncIterable, Awaitable, Buffer, Callable, Coroutine, Generator,
|
18
16
|
ItemsView, Iterable, Iterator, Mapping, MutableMapping, Sequence,
|
19
17
|
)
|
20
18
|
from datetime import date, datetime, timedelta
|
19
|
+
from errno import EBUSY, EEXIST, EFBIG, EINVAL, EIO, EISDIR, ENODATA, ENOENT, ENOSPC, ENOSYS, ENOTSUP
|
21
20
|
from functools import partial
|
22
21
|
from hashlib import md5, sha1
|
23
22
|
from http.cookiejar import Cookie, CookieJar
|
@@ -230,7 +229,7 @@ def json_loads(content: Buffer, /):
|
|
230
229
|
except Exception as e:
|
231
230
|
if isinstance(content, memoryview):
|
232
231
|
content = content.tobytes()
|
233
|
-
raise DataError(
|
232
|
+
raise DataError(ENODATA, content) from e
|
234
233
|
|
235
234
|
|
236
235
|
def default_parse(resp, content: Buffer, /):
|
@@ -283,35 +282,6 @@ def items(m: Mapping, /) -> ItemsView:
|
|
283
282
|
return ItemsView(m)
|
284
283
|
|
285
284
|
|
286
|
-
def file_close(file, /, async_: bool = False):
|
287
|
-
cls = type(file)
|
288
|
-
if async_:
|
289
|
-
aclose = getattr(file, "aclose", None)
|
290
|
-
if callable(aclose):
|
291
|
-
return aclose()
|
292
|
-
aeixt = getattr(cls, "__aexit__", None)
|
293
|
-
if callable(aeixt):
|
294
|
-
return aeixt(file, *exc_info())
|
295
|
-
close = getattr(file, "close", None)
|
296
|
-
if callable(close):
|
297
|
-
if async_:
|
298
|
-
return ensure_async(close, threaded=True)()
|
299
|
-
else:
|
300
|
-
return close()
|
301
|
-
exit = getattr(cls, "__exit__", None)
|
302
|
-
if callable(exit):
|
303
|
-
if async_:
|
304
|
-
return ensure_async(exit, threaded=True)(file, *exc_info())
|
305
|
-
else:
|
306
|
-
return exit(file, *exc_info())
|
307
|
-
deleter = getattr(cls, "__del__", None)
|
308
|
-
if callable(deleter):
|
309
|
-
if async_:
|
310
|
-
return ensure_async(deleter, threaded=True)(file)
|
311
|
-
else:
|
312
|
-
return deleter(file)
|
313
|
-
|
314
|
-
|
315
285
|
def cookies_equal(cookies1: None | str, cookies2: None | str, /) -> bool:
|
316
286
|
if not (cookies1 and cookies2):
|
317
287
|
return False
|
@@ -380,7 +350,7 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
|
|
380
350
|
"""
|
381
351
|
def check(resp, /) -> dict:
|
382
352
|
if not isinstance(resp, dict):
|
383
|
-
raise P115OSError(
|
353
|
+
raise P115OSError(EIO, resp)
|
384
354
|
if resp.get("state", True):
|
385
355
|
return resp
|
386
356
|
if code := get_first(resp, "errno", "errNo", "errcode", "errCode", "code"):
|
@@ -390,108 +360,109 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
|
|
390
360
|
match code:
|
391
361
|
# {"state": false, "errno": 99, "error": "请重新登录"}
|
392
362
|
case 99:
|
393
|
-
raise LoginError(
|
363
|
+
raise LoginError(EIO, resp)
|
394
364
|
# {"state": false, "errno": 911, "error": "请验证账号"}
|
395
365
|
case 911:
|
396
|
-
raise AuthenticationError(
|
366
|
+
raise AuthenticationError(EIO, resp)
|
397
367
|
# {"state": false, "errno": 20001, "error": "目录名称不能为空"}
|
398
368
|
case 20001:
|
399
|
-
raise OperationalError(
|
369
|
+
raise OperationalError(EINVAL, resp)
|
400
370
|
# {"state": false, "errno": 20004, "error": "该目录名称已存在。"}
|
401
371
|
case 20004:
|
402
|
-
raise FileExistsError(
|
372
|
+
raise FileExistsError(EEXIST, resp)
|
403
373
|
# {"state": false, "errno": 20009, "error": "父目录不存在。"}
|
404
374
|
case 20009:
|
405
|
-
raise FileNotFoundError(
|
375
|
+
raise FileNotFoundError(ENOENT, resp)
|
406
376
|
# {"state": false, "errno": 20018, "error": "文件不存在或已删除。"}
|
407
377
|
# {"state": false, "errno": 50015, "error": "文件不存在或已删除。"}
|
408
|
-
|
409
|
-
|
378
|
+
# {"state": false, "errno": 430004, "error": "文件(夹)不存在或已删除。"}
|
379
|
+
case 20018 | 50015 | 430004:
|
380
|
+
raise FileNotFoundError(ENOENT, resp)
|
410
381
|
# {"state": false, "errno": 20020, "error": "后缀名不正确,请重新输入"}
|
411
382
|
case 20020:
|
412
|
-
raise OperationalError(
|
383
|
+
raise OperationalError(ENOTSUP, resp)
|
413
384
|
# {"state": false, "errno": 20021, "error": "后缀名不正确,请重新输入"}
|
414
385
|
case 20021:
|
415
|
-
raise OperationalError(
|
386
|
+
raise OperationalError(ENOTSUP, resp)
|
416
387
|
# {"state": false, "errno": 31001, "error": "所预览的文件不存在。"}
|
417
388
|
case 31001:
|
418
|
-
raise FileNotFoundError(
|
389
|
+
raise FileNotFoundError(ENOENT, resp)
|
419
390
|
# {"state": false, "errno": 31004, "error": "文档未上传完整,请上传完成后再进行查看。"}
|
420
391
|
case 31004:
|
421
|
-
raise FileNotFoundError(
|
392
|
+
raise FileNotFoundError(ENOENT, resp)
|
422
393
|
# {"state": false, "errno": 50003, "error": "很抱歉,该文件提取码不存在。"}
|
423
394
|
case 50003:
|
424
|
-
raise FileNotFoundError(
|
395
|
+
raise FileNotFoundError(ENOENT, resp)
|
425
396
|
# {"state": false, "errno": 90008, "error": "文件(夹)不存在或已经删除。"}
|
426
397
|
case 90008:
|
427
|
-
raise FileNotFoundError(
|
398
|
+
raise FileNotFoundError(ENOENT, resp)
|
428
399
|
# {"state": false, "errno": 91002, "error": "不能将文件复制到自身或其子目录下。"}
|
429
400
|
case 91002:
|
430
|
-
raise NotSupportedError(
|
401
|
+
raise NotSupportedError(ENOTSUP, resp)
|
431
402
|
# {"state": false, "errno": 91004, "error": "操作的文件(夹)数量超过5万个"}
|
432
403
|
case 91004:
|
433
|
-
raise NotSupportedError(
|
404
|
+
raise NotSupportedError(ENOTSUP, resp)
|
434
405
|
# {"state": false, "errno": 91005, "error": "空间不足,复制失败。"}
|
435
406
|
case 91005:
|
436
|
-
raise OperationalError(
|
407
|
+
raise OperationalError(ENOSPC, resp)
|
437
408
|
# {"state": false, "errno": 231011, "error": "文件已删除,请勿重复操作"}
|
438
409
|
case 231011:
|
439
|
-
raise FileNotFoundError(
|
410
|
+
raise FileNotFoundError(ENOENT, resp)
|
440
411
|
# {"state": false, "errno": 300104, "error": "文件超过200MB,暂不支持播放"}
|
441
412
|
case 300104:
|
442
|
-
raise P115OSError(
|
413
|
+
raise P115OSError(EFBIG, resp)
|
443
414
|
# {"state": false, "errno": 590075, "error": "操作太频繁,请稍候再试"}
|
444
415
|
case 590075:
|
445
|
-
raise BusyOSError(
|
416
|
+
raise BusyOSError(EBUSY, resp)
|
446
417
|
# {"state": false, "errno": 800001, "error": "目录不存在。"}
|
447
418
|
case 800001:
|
448
|
-
raise FileNotFoundError(
|
419
|
+
raise FileNotFoundError(ENOENT, resp)
|
449
420
|
# {"state": false, "errno": 980006, "error": "404 Not Found"}
|
450
421
|
case 980006:
|
451
|
-
raise NotSupportedError(
|
422
|
+
raise NotSupportedError(ENOSYS, resp)
|
452
423
|
# {"state": false, "errno": 990001, "error": "登陆超时,请重新登陆。"}
|
453
424
|
case 990001:
|
454
425
|
# NOTE: 可能就是被下线了
|
455
|
-
raise AuthenticationError(
|
426
|
+
raise AuthenticationError(EIO, resp)
|
456
427
|
# {"state": false, "errno": 990002, "error": "参数错误。"}
|
457
428
|
case 990002:
|
458
|
-
raise P115OSError(
|
429
|
+
raise P115OSError(EINVAL, resp)
|
459
430
|
# {"state": false, "errno": 990003, "error": "操作失败。"}
|
460
431
|
case 990003:
|
461
|
-
raise OperationalError(
|
432
|
+
raise OperationalError(EIO, resp)
|
462
433
|
# {"state": false, "errno": 990005, "error": "你的账号有类似任务正在处理,请稍后再试!"}
|
463
434
|
case 990005:
|
464
|
-
raise BusyOSError(
|
435
|
+
raise BusyOSError(EBUSY, resp)
|
465
436
|
# {"state": false, "errno": 990009, "error": "删除[...]操作尚未执行完成,请稍后再试!"}
|
466
437
|
# {"state": false, "errno": 990009, "error": "还原[...]操作尚未执行完成,请稍后再试!"}
|
467
438
|
# {"state": false, "errno": 990009, "error": "复制[...]操作尚未执行完成,请稍后再试!"}
|
468
439
|
# {"state": false, "errno": 990009, "error": "移动[...]操作尚未执行完成,请稍后再试!"}
|
469
440
|
case 990009:
|
470
|
-
raise BusyOSError(
|
441
|
+
raise BusyOSError(EBUSY, resp)
|
471
442
|
# {"state": false, "errno": 990023, "error": "操作的文件(夹)数量超过5万个"}
|
472
443
|
case 990023:
|
473
|
-
raise OperationalError(
|
444
|
+
raise OperationalError(ENOTSUP, resp)
|
474
445
|
# {"state": 0, "errno": 40100000, "error": "参数错误!"}
|
475
446
|
case 40100000:
|
476
|
-
raise OperationalError(
|
447
|
+
raise OperationalError(EINVAL, resp)
|
477
448
|
# {"state": 0, "errno": 40101004, "error": "IP登录异常,请稍候再登录!"}
|
478
449
|
case 40101004:
|
479
|
-
raise LoginError(
|
450
|
+
raise LoginError(EIO, resp)
|
480
451
|
# {"state": 0, "errno": 40101017, "error": "用户验证失败!"}
|
481
452
|
case 40101017:
|
482
|
-
raise AuthenticationError(
|
453
|
+
raise AuthenticationError(EIO, resp)
|
483
454
|
# {"state": 0, "errno": 40101032, "error": "请重新登录"}
|
484
455
|
case 40101032:
|
485
|
-
raise LoginError(
|
456
|
+
raise LoginError(EIO, resp)
|
486
457
|
elif "msg_code" in resp:
|
487
458
|
match resp["msg_code"]:
|
488
459
|
case 50028:
|
489
|
-
raise P115OSError(
|
460
|
+
raise P115OSError(EFBIG, resp)
|
490
461
|
case 70004:
|
491
|
-
raise IsADirectoryError(
|
462
|
+
raise IsADirectoryError(EISDIR, resp)
|
492
463
|
case 70005 | 70008:
|
493
|
-
raise FileNotFoundError(
|
494
|
-
raise P115OSError(
|
464
|
+
raise FileNotFoundError(ENOENT, resp)
|
465
|
+
raise P115OSError(EIO, resp)
|
495
466
|
if isinstance(resp, dict):
|
496
467
|
return check(resp)
|
497
468
|
elif isawaitable(resp):
|
@@ -499,7 +470,7 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
|
|
499
470
|
return check(await resp)
|
500
471
|
return check_await()
|
501
472
|
else:
|
502
|
-
raise P115OSError(
|
473
|
+
raise P115OSError(EIO, resp)
|
503
474
|
|
504
475
|
|
505
476
|
def normalize_attr_web(
|
@@ -1773,11 +1744,11 @@ class ClientRequestMixin:
|
|
1773
1744
|
print("[status=2] qrcode: signed in")
|
1774
1745
|
break
|
1775
1746
|
case -1:
|
1776
|
-
raise LoginError(
|
1747
|
+
raise LoginError(EIO, "[status=-1] qrcode: expired")
|
1777
1748
|
case -2:
|
1778
|
-
raise LoginError(
|
1749
|
+
raise LoginError(EIO, "[status=-2] qrcode: canceled")
|
1779
1750
|
case _:
|
1780
|
-
raise LoginError(
|
1751
|
+
raise LoginError(EIO, f"qrcode: aborted with {resp!r}")
|
1781
1752
|
if app:
|
1782
1753
|
return cls.login_qrcode_scan_result(
|
1783
1754
|
login_uid,
|
@@ -1871,11 +1842,11 @@ class ClientRequestMixin:
|
|
1871
1842
|
print("[status=2] qrcode: signed in")
|
1872
1843
|
break
|
1873
1844
|
case -1:
|
1874
|
-
raise LoginError(
|
1845
|
+
raise LoginError(EIO, "[status=-1] qrcode: expired")
|
1875
1846
|
case -2:
|
1876
|
-
raise LoginError(
|
1847
|
+
raise LoginError(EIO, "[status=-2] qrcode: canceled")
|
1877
1848
|
case _:
|
1878
|
-
raise LoginError(
|
1849
|
+
raise LoginError(EIO, f"qrcode: aborted with {resp!r}")
|
1879
1850
|
return cls.login_qrcode_access_token_open(
|
1880
1851
|
login_uid,
|
1881
1852
|
async_=async_,
|
@@ -2599,7 +2570,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
2599
2570
|
url = info["url"]
|
2600
2571
|
if strict and not url:
|
2601
2572
|
raise IsADirectoryError(
|
2602
|
-
|
2573
|
+
EISDIR,
|
2603
2574
|
f"{fid} is a directory, with response {resp}",
|
2604
2575
|
)
|
2605
2576
|
return P115URL(
|
@@ -2613,7 +2584,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
2613
2584
|
headers=resp["headers"],
|
2614
2585
|
)
|
2615
2586
|
raise FileNotFoundError(
|
2616
|
-
|
2587
|
+
ENOENT,
|
2617
2588
|
f"no such pickcode: {pickcode!r}, with response {resp}",
|
2618
2589
|
)
|
2619
2590
|
if async_:
|
@@ -3506,44 +3477,6 @@ class P115OpenClient(ClientRequestMixin):
|
|
3506
3477
|
api = complete_proapi("/open/upload/resume", base_url)
|
3507
3478
|
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
3508
3479
|
|
3509
|
-
@overload
|
3510
|
-
def user_info(
|
3511
|
-
self,
|
3512
|
-
/,
|
3513
|
-
base_url: bool | str | Callable[[], str] = False,
|
3514
|
-
*,
|
3515
|
-
async_: Literal[False] = False,
|
3516
|
-
**request_kwargs,
|
3517
|
-
) -> dict:
|
3518
|
-
...
|
3519
|
-
@overload
|
3520
|
-
def user_info(
|
3521
|
-
self,
|
3522
|
-
/,
|
3523
|
-
base_url: bool | str | Callable[[], str] = False,
|
3524
|
-
*,
|
3525
|
-
async_: Literal[True],
|
3526
|
-
**request_kwargs,
|
3527
|
-
) -> Coroutine[Any, Any, dict]:
|
3528
|
-
...
|
3529
|
-
def user_info(
|
3530
|
-
self,
|
3531
|
-
/,
|
3532
|
-
base_url: bool | str | Callable[[], str] = False,
|
3533
|
-
*,
|
3534
|
-
async_: Literal[False, True] = False,
|
3535
|
-
**request_kwargs,
|
3536
|
-
) -> dict | Coroutine[Any, Any, dict]:
|
3537
|
-
"""获取用户信息
|
3538
|
-
|
3539
|
-
GET https://proapi.115.com/open/user/info
|
3540
|
-
|
3541
|
-
.. note::
|
3542
|
-
https://www.yuque.com/115yun/open/ot1litggzxa1czww
|
3543
|
-
"""
|
3544
|
-
api = complete_proapi("/open/user/info", base_url)
|
3545
|
-
return self.request(url=api, async_=async_, **request_kwargs)
|
3546
|
-
|
3547
3480
|
@overload
|
3548
3481
|
def upload_file_init(
|
3549
3482
|
self,
|
@@ -3779,6 +3712,8 @@ class P115OpenClient(ClientRequestMixin):
|
|
3779
3712
|
async_=async_, # type: ignore
|
3780
3713
|
**request_kwargs,
|
3781
3714
|
)
|
3715
|
+
if filesize == 0:
|
3716
|
+
filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
|
3782
3717
|
need_calc_filesha1 = not filesha1 and multipart_resume_data is None
|
3783
3718
|
read_range_bytes_or_hash: None | Callable = None
|
3784
3719
|
try:
|
@@ -3787,7 +3722,9 @@ class P115OpenClient(ClientRequestMixin):
|
|
3787
3722
|
pass
|
3788
3723
|
if isinstance(file, Buffer):
|
3789
3724
|
filesize = buffer_length(file)
|
3790
|
-
if
|
3725
|
+
if filesize == 0:
|
3726
|
+
filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
|
3727
|
+
elif need_calc_filesha1:
|
3791
3728
|
filesha1 = sha1(file).hexdigest()
|
3792
3729
|
if multipart_resume_data is None and filesize >= 1 << 20:
|
3793
3730
|
view = memoryview(file)
|
@@ -3840,7 +3777,9 @@ class P115OpenClient(ClientRequestMixin):
|
|
3840
3777
|
filesize = (yield seek(0, 2)) - curpos
|
3841
3778
|
finally:
|
3842
3779
|
yield seek(curpos)
|
3843
|
-
if
|
3780
|
+
if filesize == 0:
|
3781
|
+
filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
|
3782
|
+
elif multipart_resume_data is None and filesize >= 1 << 20:
|
3844
3783
|
read: Callable[[int], Buffer] | Callable[[int], Awaitable[Buffer]]
|
3845
3784
|
if seekable:
|
3846
3785
|
if async_:
|
@@ -3897,16 +3836,24 @@ class P115OpenClient(ClientRequestMixin):
|
|
3897
3836
|
url = cast(str, multipart_resume_data.get("url", ""))
|
3898
3837
|
if not url:
|
3899
3838
|
url = self.upload_endpoint_url(bucket, object)
|
3900
|
-
|
3901
|
-
|
3902
|
-
|
3839
|
+
callback_var = loads(multipart_resume_data["callback"]["callback_var"])
|
3840
|
+
yield self.upload_resume_open(
|
3841
|
+
{
|
3842
|
+
"fileid": object,
|
3843
|
+
"file_size": multipart_resume_data["filesize"],
|
3844
|
+
"target": callback_var["x:target"],
|
3845
|
+
"pick_code": callback_var["x:pick_code"],
|
3846
|
+
},
|
3847
|
+
async_=async_,
|
3848
|
+
**request_kwargs,
|
3849
|
+
)
|
3903
3850
|
return oss_multipart_upload(
|
3904
3851
|
self.request,
|
3905
3852
|
file, # type: ignore
|
3906
3853
|
url=url,
|
3907
3854
|
bucket=bucket,
|
3908
3855
|
object=object,
|
3909
|
-
token=
|
3856
|
+
token=self.upload_token,
|
3910
3857
|
callback=multipart_resume_data["callback"],
|
3911
3858
|
upload_id=multipart_resume_data["upload_id"],
|
3912
3859
|
partsize=multipart_resume_data["partsize"],
|
@@ -3941,7 +3888,7 @@ class P115OpenClient(ClientRequestMixin):
|
|
3941
3888
|
case 1:
|
3942
3889
|
bucket, object, callback = data["bucket"], data["object"], data["callback"]
|
3943
3890
|
case _:
|
3944
|
-
raise P115OSError(
|
3891
|
+
raise P115OSError(EINVAL, resp)
|
3945
3892
|
url = self.upload_endpoint_url(bucket, object)
|
3946
3893
|
token = self.upload_token
|
3947
3894
|
if partsize <= 0:
|
@@ -3976,6 +3923,44 @@ class P115OpenClient(ClientRequestMixin):
|
|
3976
3923
|
)
|
3977
3924
|
return run_gen_step(gen_step, async_=async_)
|
3978
3925
|
|
3926
|
+
@overload
|
3927
|
+
def user_info(
|
3928
|
+
self,
|
3929
|
+
/,
|
3930
|
+
base_url: bool | str | Callable[[], str] = False,
|
3931
|
+
*,
|
3932
|
+
async_: Literal[False] = False,
|
3933
|
+
**request_kwargs,
|
3934
|
+
) -> dict:
|
3935
|
+
...
|
3936
|
+
@overload
|
3937
|
+
def user_info(
|
3938
|
+
self,
|
3939
|
+
/,
|
3940
|
+
base_url: bool | str | Callable[[], str] = False,
|
3941
|
+
*,
|
3942
|
+
async_: Literal[True],
|
3943
|
+
**request_kwargs,
|
3944
|
+
) -> Coroutine[Any, Any, dict]:
|
3945
|
+
...
|
3946
|
+
def user_info(
|
3947
|
+
self,
|
3948
|
+
/,
|
3949
|
+
base_url: bool | str | Callable[[], str] = False,
|
3950
|
+
*,
|
3951
|
+
async_: Literal[False, True] = False,
|
3952
|
+
**request_kwargs,
|
3953
|
+
) -> dict | Coroutine[Any, Any, dict]:
|
3954
|
+
"""获取用户信息
|
3955
|
+
|
3956
|
+
GET https://proapi.115.com/open/user/info
|
3957
|
+
|
3958
|
+
.. note::
|
3959
|
+
https://www.yuque.com/115yun/open/ot1litggzxa1czww
|
3960
|
+
"""
|
3961
|
+
api = complete_proapi("/open/user/info", base_url)
|
3962
|
+
return self.request(url=api, async_=async_, **request_kwargs)
|
3963
|
+
|
3979
3964
|
download_url_open = download_url
|
3980
3965
|
download_url_info_open = download_url_info
|
3981
3966
|
fs_copy_open = fs_copy
|
@@ -4000,6 +3985,13 @@ class P115OpenClient(ClientRequestMixin):
|
|
4000
3985
|
class P115Client(P115OpenClient):
|
4001
3986
|
"""115 的客户端对象
|
4002
3987
|
|
3988
|
+
.. note::
|
3989
|
+
目前允许 1 个用户同时登录多个开放平台应用(用 AppID 区别),但如果多次登录同 1 个应用,则只有最近登录的有效
|
3990
|
+
|
3991
|
+
目前不允许短时间内再次用 `refresh_token` 刷新 `access_token`,但你可以用登录的方式再次授权登录以获取 `access_token`,即可不受频率限制
|
3992
|
+
|
3993
|
+
1 个 `refresh_token` 只能使用 1 次,可获取新的 `refresh_token` 和 `access_token`,如果请求刷新时,发送成功但读取失败,可能导致 `refresh_token` 报废,这时需要重新授权登录
|
3994
|
+
|
4003
3995
|
:param cookies: 115 的 cookies,要包含 `UID`、`CID`、`KID` 和 `SEID` 等
|
4004
3996
|
|
4005
3997
|
- 如果是 None,则会要求人工扫二维码登录
|
@@ -4925,14 +4917,14 @@ class P115Client(P115OpenClient):
|
|
4925
4917
|
data = resp["data"]
|
4926
4918
|
if replace is False:
|
4927
4919
|
inst: P115OpenClient | Self = P115OpenClient.from_token(data["access_token"], data["refresh_token"])
|
4928
|
-
inst.app_id = app_id
|
4929
4920
|
else:
|
4930
4921
|
if replace is True:
|
4931
4922
|
inst = self
|
4932
4923
|
else:
|
4933
4924
|
inst = replace
|
4934
4925
|
inst.refresh_token = data["refresh_token"]
|
4935
|
-
|
4926
|
+
inst.access_token = data["access_token"]
|
4927
|
+
inst.app_id = app_id
|
4936
4928
|
return inst
|
4937
4929
|
return run_gen_step(gen_step, async_=async_)
|
4938
4930
|
|
@@ -5210,10 +5202,14 @@ class P115Client(P115OpenClient):
|
|
5210
5202
|
elif data is not None:
|
5211
5203
|
request_kwargs["data"] = data
|
5212
5204
|
request_kwargs.setdefault("parse", default_parse)
|
5213
|
-
|
5205
|
+
use_cookies = not url.startswith("https://proapi.115.com/open/")
|
5206
|
+
if not use_cookies:
|
5214
5207
|
headers["cookie"] = ""
|
5215
|
-
return request(url=url, method=method, **request_kwargs)
|
5216
5208
|
def gen_step():
|
5209
|
+
if async_:
|
5210
|
+
lock: Lock | AsyncLock = self.request_alock
|
5211
|
+
else:
|
5212
|
+
lock = self.request_lock
|
5217
5213
|
check_for_relogin = self.check_for_relogin
|
5218
5214
|
cant_relogin = not callable(check_for_relogin)
|
5219
5215
|
if get_cookies is not None:
|
@@ -5223,59 +5219,86 @@ class P115Client(P115OpenClient):
|
|
5223
5219
|
for i in count(0):
|
5224
5220
|
exc = None
|
5225
5221
|
try:
|
5226
|
-
if
|
5227
|
-
if
|
5228
|
-
|
5229
|
-
|
5230
|
-
if get_cookies_need_arg:
|
5231
|
-
cookies_ = yield get_cookies(async_)
|
5222
|
+
if use_cookies:
|
5223
|
+
if get_cookies is None:
|
5224
|
+
if need_set_cookies:
|
5225
|
+
cookies_old = headers["cookie"] = self.cookies_str
|
5232
5226
|
else:
|
5233
|
-
|
5234
|
-
|
5235
|
-
|
5236
|
-
|
5237
|
-
|
5227
|
+
if get_cookies_need_arg:
|
5228
|
+
cookies_ = yield get_cookies(async_)
|
5229
|
+
else:
|
5230
|
+
cookies_ = yield get_cookies()
|
5231
|
+
if not cookies_:
|
5232
|
+
raise ValueError("can't get new cookies")
|
5233
|
+
headers["cookie"] = cookies_
|
5234
|
+
resp = yield partial(request, url=url, method=method, **request_kwargs)
|
5235
|
+
return resp
|
5238
5236
|
except BaseException as e:
|
5239
5237
|
exc = e
|
5240
|
-
if cant_relogin or not need_set_cookies:
|
5238
|
+
if cant_relogin or use_cookies and not need_set_cookies:
|
5241
5239
|
raise
|
5242
5240
|
if isinstance(e, (AuthenticationError, LoginError)):
|
5243
|
-
if
|
5241
|
+
if use_cookies and (
|
5242
|
+
get_cookies is not None or
|
5243
|
+
cookies_old != self.cookies_str or
|
5244
|
+
cookies_old != self._read_cookies()
|
5245
|
+
):
|
5244
5246
|
continue
|
5245
5247
|
raise
|
5246
5248
|
res = yield partial(cast(Callable, check_for_relogin), e)
|
5247
5249
|
if not res if isinstance(res, bool) else res != 405:
|
5248
5250
|
raise
|
5249
|
-
if
|
5250
|
-
|
5251
|
-
|
5252
|
-
|
5253
|
-
|
5254
|
-
|
5255
|
-
|
5256
|
-
lock
|
5257
|
-
|
5251
|
+
if use_cookies:
|
5252
|
+
if get_cookies is not None:
|
5253
|
+
continue
|
5254
|
+
cookies = self.cookies_str
|
5255
|
+
if not cookies_equal(cookies, cookies_old):
|
5256
|
+
continue
|
5257
|
+
cookies_mtime = getattr(self, "cookies_mtime", 0)
|
5258
|
+
yield lock.acquire
|
5259
|
+
try:
|
5260
|
+
cookies_new = self.cookies_str
|
5261
|
+
cookies_mtime_new = getattr(self, "cookies_mtime", 0)
|
5262
|
+
if cookies_equal(cookies, cookies_new):
|
5263
|
+
m = CRE_COOKIES_UID_search(cookies)
|
5264
|
+
uid = "" if m is None else m[0]
|
5265
|
+
need_read_cookies = cookies_mtime_new > cookies_mtime
|
5266
|
+
if need_read_cookies:
|
5267
|
+
cookies_new = self._read_cookies()
|
5268
|
+
if i and cookies_equal(cookies_old, cookies_new):
|
5269
|
+
raise
|
5270
|
+
if not (need_read_cookies and cookies_new):
|
5271
|
+
warn(f"relogin to refresh cookies: UID={uid!r} app={self.login_app()!r}", category=P115Warning)
|
5272
|
+
yield self.login_another_app(
|
5273
|
+
replace=True,
|
5274
|
+
async_=async_, # type: ignore
|
5275
|
+
)
|
5276
|
+
finally:
|
5277
|
+
lock.release()
|
5258
5278
|
else:
|
5259
|
-
|
5260
|
-
lock.acquire
|
5261
|
-
|
5262
|
-
|
5263
|
-
|
5264
|
-
|
5265
|
-
|
5266
|
-
|
5267
|
-
|
5268
|
-
|
5269
|
-
|
5270
|
-
|
5271
|
-
|
5272
|
-
|
5273
|
-
|
5274
|
-
|
5275
|
-
|
5276
|
-
|
5279
|
+
access_token = self.access_token
|
5280
|
+
yield lock.acquire
|
5281
|
+
try:
|
5282
|
+
if access_token != self.access_token:
|
5283
|
+
continue
|
5284
|
+
if hasattr(self, "app_id"):
|
5285
|
+
app_id = self.app_id
|
5286
|
+
yield self.login_another_open(
|
5287
|
+
app_id,
|
5288
|
+
replace=True,
|
5289
|
+
async_=async_, # type: ignore
|
5290
|
+
)
|
5291
|
+
warn(f"relogin to refresh token: {app_id=}", category=P115Warning)
|
5292
|
+
else:
|
5293
|
+
resp = yield self.refresh_access_token(
|
5294
|
+
async_=async_, # type: ignore
|
5295
|
+
)
|
5296
|
+
check_response(resp)
|
5297
|
+
warn("relogin to refresh token (using refresh_token)", category=P115Warning)
|
5298
|
+
finally:
|
5299
|
+
lock.release()
|
5277
5300
|
finally:
|
5278
|
-
if (cookies_ and
|
5301
|
+
if (use_cookies and cookies_ and
|
5279
5302
|
get_cookies is not None and
|
5280
5303
|
revert_cookies is not None and (
|
5281
5304
|
not exc or not (
|
@@ -6314,7 +6337,7 @@ class P115Client(P115OpenClient):
|
|
6314
6337
|
url = info["url"]
|
6315
6338
|
if strict and not url:
|
6316
6339
|
raise IsADirectoryError(
|
6317
|
-
|
6340
|
+
EISDIR,
|
6318
6341
|
f"{fid} is a directory, with response {resp}",
|
6319
6342
|
)
|
6320
6343
|
return P115URL(
|
@@ -6328,7 +6351,7 @@ class P115Client(P115OpenClient):
|
|
6328
6351
|
headers=resp["headers"],
|
6329
6352
|
)
|
6330
6353
|
raise FileNotFoundError(
|
6331
|
-
|
6354
|
+
ENOENT,
|
6332
6355
|
f"no such pickcode: {pickcode!r}, with response {resp}",
|
6333
6356
|
)
|
6334
6357
|
if async_:
|
@@ -9245,6 +9268,53 @@ class P115Client(P115OpenClient):
|
|
9245
9268
|
payload["custom_order"] = 1
|
9246
9269
|
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
9247
9270
|
|
9271
|
+
@overload
|
9272
|
+
def fs_files_blank_document(
|
9273
|
+
self,
|
9274
|
+
payload: str | dict,
|
9275
|
+
/,
|
9276
|
+
base_url: bool | str | Callable[[], str] = False,
|
9277
|
+
*,
|
9278
|
+
async_: Literal[False] = False,
|
9279
|
+
**request_kwargs,
|
9280
|
+
) -> dict:
|
9281
|
+
...
|
9282
|
+
@overload
|
9283
|
+
def fs_files_blank_document(
|
9284
|
+
self,
|
9285
|
+
payload: str | dict,
|
9286
|
+
/,
|
9287
|
+
base_url: bool | str | Callable[[], str] = False,
|
9288
|
+
*,
|
9289
|
+
async_: Literal[True],
|
9290
|
+
**request_kwargs,
|
9291
|
+
) -> Coroutine[Any, Any, dict]:
|
9292
|
+
...
|
9293
|
+
def fs_files_blank_document(
|
9294
|
+
self,
|
9295
|
+
payload: str | dict,
|
9296
|
+
/,
|
9297
|
+
base_url: bool | str | Callable[[], str] = False,
|
9298
|
+
*,
|
9299
|
+
async_: Literal[False, True] = False,
|
9300
|
+
**request_kwargs,
|
9301
|
+
) -> dict | Coroutine[Any, Any, dict]:
|
9302
|
+
"""新建空白 office 文件
|
9303
|
+
|
9304
|
+
POST https://webapi.115.com/files/blank_document
|
9305
|
+
|
9306
|
+
:payload:
|
9307
|
+
- file_name: str 💡 文件名,不含后缀
|
9308
|
+
- pid: int | str = 0 💡 目录 id
|
9309
|
+
- type: 1 | 2 | 3 = 1 💡 1:Word文档(.docx) 2:Excel表格(.xlsx) 3:PPT文稿(.pptx)
|
9310
|
+
"""
|
9311
|
+
api = complete_webapi("/files/blank_document", base_url=base_url)
|
9312
|
+
if isinstance(payload, str):
|
9313
|
+
payload = {"pid": 0, "type": 1, "file_name": payload}
|
9314
|
+
else:
|
9315
|
+
payload = {"pid": 0, "type": 1, **payload}
|
9316
|
+
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
9317
|
+
|
9248
9318
|
@overload
|
9249
9319
|
def fs_files_history(
|
9250
9320
|
self,
|
@@ -9329,7 +9399,7 @@ class P115Client(P115OpenClient):
|
|
9329
9399
|
POST https://webapi.115.com/files/history
|
9330
9400
|
|
9331
9401
|
:payload:
|
9332
|
-
- pick_code: str
|
9402
|
+
- pick_code: str 💡 文件的提取码
|
9333
9403
|
- op: str = "update" 💡 操作类型,具体有哪些还需要再研究
|
9334
9404
|
- category: int = <default>
|
9335
9405
|
- definition: int = <default> 💡 视频清晰度
|
@@ -17193,13 +17263,13 @@ class P115Client(P115OpenClient):
|
|
17193
17263
|
file_id = payload["file_id"]
|
17194
17264
|
if not info:
|
17195
17265
|
raise FileNotFoundError(
|
17196
|
-
|
17266
|
+
ENOENT,
|
17197
17267
|
f"no such id: {file_id!r}, with response {resp}",
|
17198
17268
|
)
|
17199
17269
|
url = info["url"]
|
17200
17270
|
if strict and not url:
|
17201
17271
|
raise IsADirectoryError(
|
17202
|
-
|
17272
|
+
EISDIR,
|
17203
17273
|
f"{file_id} is a directory, with response {resp}",
|
17204
17274
|
)
|
17205
17275
|
return P115URL(
|
@@ -17945,13 +18015,13 @@ class P115Client(P115OpenClient):
|
|
17945
18015
|
file_id = payload["file_id"]
|
17946
18016
|
if not info:
|
17947
18017
|
raise FileNotFoundError(
|
17948
|
-
|
18018
|
+
ENOENT,
|
17949
18019
|
f"no such id: {file_id!r}, with response {resp}",
|
17950
18020
|
)
|
17951
18021
|
url = info["url"]
|
17952
18022
|
if strict and not url:
|
17953
18023
|
raise IsADirectoryError(
|
17954
|
-
|
18024
|
+
EISDIR,
|
17955
18025
|
f"{file_id} is a directory, with response {resp}",
|
17956
18026
|
)
|
17957
18027
|
return P115URL(
|
@@ -19166,8 +19236,6 @@ class P115Client(P115OpenClient):
|
|
19166
19236
|
|
19167
19237
|
:return: 接口响应
|
19168
19238
|
"""
|
19169
|
-
if filesize >= 1 << 20 and read_range_bytes_or_hash is None:
|
19170
|
-
raise ValueError("filesize >= 1 MB, thus need pass the `read_range_bytes_or_hash` argument")
|
19171
19239
|
filesha1 = filesha1.upper()
|
19172
19240
|
target = f"U_1_{pid}"
|
19173
19241
|
def gen_step():
|
@@ -19353,8 +19421,6 @@ class P115Client(P115OpenClient):
|
|
19353
19421
|
)
|
19354
19422
|
return run_gen_step(gen_step, async_=async_)
|
19355
19423
|
|
19356
|
-
# TODO: 当文件 < 1 MB 时,文件不急着打开,需要时再打开
|
19357
|
-
# TODO: 对于上传空文件,有特别的速度(sha1写死)
|
19358
19424
|
@overload # type: ignore
|
19359
19425
|
def upload_file(
|
19360
19426
|
self,
|
@@ -19493,11 +19559,9 @@ class P115Client(P115OpenClient):
|
|
19493
19559
|
async_=async_, # type: ignore
|
19494
19560
|
**request_kwargs,
|
19495
19561
|
)
|
19496
|
-
|
19497
|
-
|
19498
|
-
|
19499
|
-
multipart_resume_data is None
|
19500
|
-
)
|
19562
|
+
if filesize == 0:
|
19563
|
+
filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
|
19564
|
+
need_calc_filesha1 = not filesha1 and not upload_directly and multipart_resume_data is None
|
19501
19565
|
read_range_bytes_or_hash: None | Callable = None
|
19502
19566
|
try:
|
19503
19567
|
file = getattr(file, "getbuffer")()
|
@@ -19505,7 +19569,9 @@ class P115Client(P115OpenClient):
|
|
19505
19569
|
pass
|
19506
19570
|
if isinstance(file, Buffer):
|
19507
19571
|
filesize = buffer_length(file)
|
19508
|
-
if
|
19572
|
+
if filesize == 0:
|
19573
|
+
filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
|
19574
|
+
elif need_calc_filesha1:
|
19509
19575
|
filesha1 = sha1(file).hexdigest()
|
19510
19576
|
if not upload_directly and multipart_resume_data is None and filesize >= 1 << 20:
|
19511
19577
|
view = memoryview(file)
|
@@ -19558,7 +19624,9 @@ class P115Client(P115OpenClient):
|
|
19558
19624
|
filesize = (yield seek(0, 2)) - curpos
|
19559
19625
|
finally:
|
19560
19626
|
yield seek(curpos)
|
19561
|
-
if
|
19627
|
+
if filesize == 0:
|
19628
|
+
filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
|
19629
|
+
elif not upload_directly and multipart_resume_data is None and filesize >= 1 << 20:
|
19562
19630
|
read: Callable[[int], Buffer] | Callable[[int], Awaitable[Buffer]]
|
19563
19631
|
if seekable:
|
19564
19632
|
if async_:
|
@@ -19615,16 +19683,24 @@ class P115Client(P115OpenClient):
|
|
19615
19683
|
url = cast(str, multipart_resume_data.get("url", ""))
|
19616
19684
|
if not url:
|
19617
19685
|
url = self.upload_endpoint_url(bucket, object)
|
19618
|
-
|
19619
|
-
|
19620
|
-
|
19686
|
+
callback_var = loads(multipart_resume_data["callback"]["callback_var"])
|
19687
|
+
yield self.upload_resume(
|
19688
|
+
{
|
19689
|
+
"fileid": object,
|
19690
|
+
"filesize": multipart_resume_data["filesize"],
|
19691
|
+
"target": callback_var["x:target"],
|
19692
|
+
"pickcode": callback_var["x:pick_code"],
|
19693
|
+
},
|
19694
|
+
async_=async_,
|
19695
|
+
**request_kwargs,
|
19696
|
+
)
|
19621
19697
|
return oss_multipart_upload(
|
19622
19698
|
self.request,
|
19623
19699
|
file, # type: ignore
|
19624
19700
|
url=url,
|
19625
19701
|
bucket=bucket,
|
19626
19702
|
object=object,
|
19627
|
-
token=
|
19703
|
+
token=self.upload_token,
|
19628
19704
|
callback=multipart_resume_data["callback"],
|
19629
19705
|
upload_id=multipart_resume_data["upload_id"],
|
19630
19706
|
partsize=multipart_resume_data["partsize"],
|
@@ -19669,7 +19745,7 @@ class P115Client(P115OpenClient):
|
|
19669
19745
|
elif status == 1 and statuscode == 0:
|
19670
19746
|
bucket, object, callback = resp["bucket"], resp["object"], resp["callback"]
|
19671
19747
|
else:
|
19672
|
-
raise P115OSError(
|
19748
|
+
raise P115OSError(EINVAL, resp)
|
19673
19749
|
url = self.upload_endpoint_url(bucket, object)
|
19674
19750
|
token = self.upload_token
|
19675
19751
|
if partsize <= 0:
|
@@ -19819,11 +19895,10 @@ class P115Client(P115OpenClient):
|
|
19819
19895
|
return self.request(url=api, async_=async_, **request_kwargs)
|
19820
19896
|
|
19821
19897
|
@overload # type: ignore
|
19822
|
-
@staticmethod
|
19823
19898
|
def user_info(
|
19824
|
-
|
19899
|
+
self: int | str | dict | P115Client,
|
19900
|
+
payload: None | int | str | dict = None,
|
19825
19901
|
/,
|
19826
|
-
request: None | Callable = None,
|
19827
19902
|
base_url: bool | str | Callable[[], str] = False,
|
19828
19903
|
*,
|
19829
19904
|
async_: Literal[False] = False,
|
@@ -19831,22 +19906,20 @@ class P115Client(P115OpenClient):
|
|
19831
19906
|
) -> dict:
|
19832
19907
|
...
|
19833
19908
|
@overload
|
19834
|
-
@staticmethod
|
19835
19909
|
def user_info(
|
19836
|
-
|
19910
|
+
self: int | str | dict | P115Client,
|
19911
|
+
payload: None | int | str | dict = None,
|
19837
19912
|
/,
|
19838
|
-
request: None | Callable = None,
|
19839
19913
|
base_url: bool | str | Callable[[], str] = False,
|
19840
19914
|
*,
|
19841
19915
|
async_: Literal[True],
|
19842
19916
|
**request_kwargs,
|
19843
19917
|
) -> Coroutine[Any, Any, dict]:
|
19844
19918
|
...
|
19845
|
-
@staticmethod
|
19846
19919
|
def user_info(
|
19847
|
-
|
19920
|
+
self: int | str | dict | P115Client,
|
19921
|
+
payload: None | int | str | dict = None,
|
19848
19922
|
/,
|
19849
|
-
request: None | Callable = None,
|
19850
19923
|
base_url: bool | str | Callable[[], str] = False,
|
19851
19924
|
*,
|
19852
19925
|
async_: Literal[False, True] = False,
|
@@ -19856,19 +19929,31 @@ class P115Client(P115OpenClient):
|
|
19856
19929
|
|
19857
19930
|
GET https://my.115.com/proapi/3.0/index.php?method=user_info
|
19858
19931
|
|
19932
|
+
.. important::
|
19933
|
+
这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
|
19934
|
+
|
19859
19935
|
:payload:
|
19860
19936
|
- uid: int | str
|
19861
19937
|
"""
|
19862
19938
|
api = complete_api("/proapi/3.0/index.php", "my", base_url=base_url)
|
19939
|
+
if isinstance(self, P115Client):
|
19940
|
+
if payload is None:
|
19941
|
+
payload = self.user_id
|
19942
|
+
else:
|
19943
|
+
payload = self
|
19863
19944
|
if isinstance(payload, (int, str)):
|
19864
19945
|
payload = {"uid": payload, "method": "user_info"}
|
19865
19946
|
else:
|
19866
19947
|
payload = {"method": "user_info", **payload}
|
19867
|
-
|
19868
|
-
|
19869
|
-
return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
|
19948
|
+
if isinstance(self, P115Client):
|
19949
|
+
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
19870
19950
|
else:
|
19871
|
-
|
19951
|
+
request_kwargs.setdefault("parse", default_parse)
|
19952
|
+
request = request_kwargs.pop("request", None)
|
19953
|
+
if request is None:
|
19954
|
+
return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
|
19955
|
+
else:
|
19956
|
+
return request(url=api, params=payload, **request_kwargs)
|
19872
19957
|
|
19873
19958
|
@overload
|
19874
19959
|
def user_my(
|
@@ -20401,6 +20486,49 @@ class P115Client(P115OpenClient):
|
|
20401
20486
|
api = complete_proapi("/vip/check_spw", base_url, app)
|
20402
20487
|
return self.request(url=api, async_=async_, **request_kwargs)
|
20403
20488
|
|
20489
|
+
@overload
|
20490
|
+
def user_vip_limit(
|
20491
|
+
self,
|
20492
|
+
payload: int | dict = 2,
|
20493
|
+
/,
|
20494
|
+
base_url: bool | str | Callable[[], str] = False,
|
20495
|
+
*,
|
20496
|
+
async_: Literal[False] = False,
|
20497
|
+
**request_kwargs,
|
20498
|
+
) -> dict:
|
20499
|
+
...
|
20500
|
+
@overload
|
20501
|
+
def user_vip_limit(
|
20502
|
+
self,
|
20503
|
+
payload: int | dict = 2,
|
20504
|
+
/,
|
20505
|
+
base_url: bool | str | Callable[[], str] = False,
|
20506
|
+
*,
|
20507
|
+
async_: Literal[True],
|
20508
|
+
**request_kwargs,
|
20509
|
+
) -> Coroutine[Any, Any, dict]:
|
20510
|
+
...
|
20511
|
+
def user_vip_limit(
|
20512
|
+
self,
|
20513
|
+
payload: int | dict = 2,
|
20514
|
+
/,
|
20515
|
+
base_url: bool | str | Callable[[], str] = False,
|
20516
|
+
*,
|
20517
|
+
async_: Literal[False, True] = False,
|
20518
|
+
**request_kwargs,
|
20519
|
+
) -> dict | Coroutine[Any, Any, dict]:
|
20520
|
+
"""获取 vip 的某些限制
|
20521
|
+
|
20522
|
+
GET https://webapi.115.com/user/vip_limit
|
20523
|
+
|
20524
|
+
:payload:
|
20525
|
+
- feature: int = 2
|
20526
|
+
"""
|
20527
|
+
api = complete_webapi("/user/vip_limit", base_url=base_url)
|
20528
|
+
if isinstance(payload, int):
|
20529
|
+
payload = {"feature": payload}
|
20530
|
+
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
|
20531
|
+
|
20404
20532
|
########## User Share API ##########
|
20405
20533
|
|
20406
20534
|
@overload
|