p115client 0.0.5.8.1__py3-none-any.whl → 0.0.5.8.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
p115client/client.py CHANGED
@@ -9,15 +9,14 @@ __all__ = [
9
9
  "normalize_attr_app", "normalize_attr_app2", "P115OpenClient", "P115Client",
10
10
  ]
11
11
 
12
- import errno
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(errno.ENODATA, content) from e
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(errno.EIO, resp)
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(errno.EIO, resp)
363
+ raise LoginError(EIO, resp)
394
364
  # {"state": false, "errno": 911, "error": "请验证账号"}
395
365
  case 911:
396
- raise AuthenticationError(errno.EIO, resp)
366
+ raise AuthenticationError(EIO, resp)
397
367
  # {"state": false, "errno": 20001, "error": "目录名称不能为空"}
398
368
  case 20001:
399
- raise OperationalError(errno.EINVAL, resp)
369
+ raise OperationalError(EINVAL, resp)
400
370
  # {"state": false, "errno": 20004, "error": "该目录名称已存在。"}
401
371
  case 20004:
402
- raise FileExistsError(errno.EEXIST, resp)
372
+ raise FileExistsError(EEXIST, resp)
403
373
  # {"state": false, "errno": 20009, "error": "父目录不存在。"}
404
374
  case 20009:
405
- raise FileNotFoundError(errno.ENOENT, resp)
375
+ raise FileNotFoundError(ENOENT, resp)
406
376
  # {"state": false, "errno": 20018, "error": "文件不存在或已删除。"}
407
377
  # {"state": false, "errno": 50015, "error": "文件不存在或已删除。"}
408
- case 20018 | 50015:
409
- raise FileNotFoundError(errno.ENOENT, resp)
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(errno.ENOTSUP, resp)
383
+ raise OperationalError(ENOTSUP, resp)
413
384
  # {"state": false, "errno": 20021, "error": "后缀名不正确,请重新输入"}
414
385
  case 20021:
415
- raise OperationalError(errno.ENOTSUP, resp)
386
+ raise OperationalError(ENOTSUP, resp)
416
387
  # {"state": false, "errno": 31001, "error": "所预览的文件不存在。"}
417
388
  case 31001:
418
- raise FileNotFoundError(errno.ENOENT, resp)
389
+ raise FileNotFoundError(ENOENT, resp)
419
390
  # {"state": false, "errno": 31004, "error": "文档未上传完整,请上传完成后再进行查看。"}
420
391
  case 31004:
421
- raise FileNotFoundError(errno.ENOENT, resp)
392
+ raise FileNotFoundError(ENOENT, resp)
422
393
  # {"state": false, "errno": 50003, "error": "很抱歉,该文件提取码不存在。"}
423
394
  case 50003:
424
- raise FileNotFoundError(errno.ENOENT, resp)
395
+ raise FileNotFoundError(ENOENT, resp)
425
396
  # {"state": false, "errno": 90008, "error": "文件(夹)不存在或已经删除。"}
426
397
  case 90008:
427
- raise FileNotFoundError(errno.ENOENT, resp)
398
+ raise FileNotFoundError(ENOENT, resp)
428
399
  # {"state": false, "errno": 91002, "error": "不能将文件复制到自身或其子目录下。"}
429
400
  case 91002:
430
- raise NotSupportedError(errno.ENOTSUP, resp)
401
+ raise NotSupportedError(ENOTSUP, resp)
431
402
  # {"state": false, "errno": 91004, "error": "操作的文件(夹)数量超过5万个"}
432
403
  case 91004:
433
- raise NotSupportedError(errno.ENOTSUP, resp)
404
+ raise NotSupportedError(ENOTSUP, resp)
434
405
  # {"state": false, "errno": 91005, "error": "空间不足,复制失败。"}
435
406
  case 91005:
436
- raise OperationalError(errno.ENOSPC, resp)
407
+ raise OperationalError(ENOSPC, resp)
437
408
  # {"state": false, "errno": 231011, "error": "文件已删除,请勿重复操作"}
438
409
  case 231011:
439
- raise FileNotFoundError(errno.ENOENT, resp)
410
+ raise FileNotFoundError(ENOENT, resp)
440
411
  # {"state": false, "errno": 300104, "error": "文件超过200MB,暂不支持播放"}
441
412
  case 300104:
442
- raise P115OSError(errno.EFBIG, resp)
413
+ raise P115OSError(EFBIG, resp)
443
414
  # {"state": false, "errno": 590075, "error": "操作太频繁,请稍候再试"}
444
415
  case 590075:
445
- raise BusyOSError(errno.EBUSY, resp)
416
+ raise BusyOSError(EBUSY, resp)
446
417
  # {"state": false, "errno": 800001, "error": "目录不存在。"}
447
418
  case 800001:
448
- raise FileNotFoundError(errno.ENOENT, resp)
419
+ raise FileNotFoundError(ENOENT, resp)
449
420
  # {"state": false, "errno": 980006, "error": "404 Not Found"}
450
421
  case 980006:
451
- raise NotSupportedError(errno.ENOSYS, resp)
422
+ raise NotSupportedError(ENOSYS, resp)
452
423
  # {"state": false, "errno": 990001, "error": "登陆超时,请重新登陆。"}
453
424
  case 990001:
454
425
  # NOTE: 可能就是被下线了
455
- raise AuthenticationError(errno.EIO, resp)
426
+ raise AuthenticationError(EIO, resp)
456
427
  # {"state": false, "errno": 990002, "error": "参数错误。"}
457
428
  case 990002:
458
- raise P115OSError(errno.EINVAL, resp)
429
+ raise P115OSError(EINVAL, resp)
459
430
  # {"state": false, "errno": 990003, "error": "操作失败。"}
460
431
  case 990003:
461
- raise OperationalError(errno.EIO, resp)
432
+ raise OperationalError(EIO, resp)
462
433
  # {"state": false, "errno": 990005, "error": "你的账号有类似任务正在处理,请稍后再试!"}
463
434
  case 990005:
464
- raise BusyOSError(errno.EBUSY, resp)
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(errno.EBUSY, resp)
441
+ raise BusyOSError(EBUSY, resp)
471
442
  # {"state": false, "errno": 990023, "error": "操作的文件(夹)数量超过5万个"}
472
443
  case 990023:
473
- raise OperationalError(errno.ENOTSUP, resp)
444
+ raise OperationalError(ENOTSUP, resp)
474
445
  # {"state": 0, "errno": 40100000, "error": "参数错误!"}
475
446
  case 40100000:
476
- raise OperationalError(errno.EINVAL, resp)
447
+ raise OperationalError(EINVAL, resp)
477
448
  # {"state": 0, "errno": 40101004, "error": "IP登录异常,请稍候再登录!"}
478
449
  case 40101004:
479
- raise LoginError(errno.EIO, resp)
450
+ raise LoginError(EIO, resp)
480
451
  # {"state": 0, "errno": 40101017, "error": "用户验证失败!"}
481
452
  case 40101017:
482
- raise AuthenticationError(errno.EIO, resp)
453
+ raise AuthenticationError(EIO, resp)
483
454
  # {"state": 0, "errno": 40101032, "error": "请重新登录"}
484
455
  case 40101032:
485
- raise LoginError(errno.EIO, resp)
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(errno.EFBIG, resp)
460
+ raise P115OSError(EFBIG, resp)
490
461
  case 70004:
491
- raise IsADirectoryError(errno.EISDIR, resp)
462
+ raise IsADirectoryError(EISDIR, resp)
492
463
  case 70005 | 70008:
493
- raise FileNotFoundError(errno.ENOENT, resp)
494
- raise P115OSError(errno.EIO, resp)
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(errno.EIO, resp)
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(errno.EIO, "[status=-1] qrcode: expired")
1747
+ raise LoginError(EIO, "[status=-1] qrcode: expired")
1777
1748
  case -2:
1778
- raise LoginError(errno.EIO, "[status=-2] qrcode: canceled")
1749
+ raise LoginError(EIO, "[status=-2] qrcode: canceled")
1779
1750
  case _:
1780
- raise LoginError(errno.EIO, f"qrcode: aborted with {resp!r}")
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(errno.EIO, "[status=-1] qrcode: expired")
1845
+ raise LoginError(EIO, "[status=-1] qrcode: expired")
1875
1846
  case -2:
1876
- raise LoginError(errno.EIO, "[status=-2] qrcode: canceled")
1847
+ raise LoginError(EIO, "[status=-2] qrcode: canceled")
1877
1848
  case _:
1878
- raise LoginError(errno.EIO, f"qrcode: aborted with {resp!r}")
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
- errno.EISDIR,
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
- errno.ENOENT,
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 need_calc_filesha1:
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 multipart_resume_data is None and filesize >= 1 << 20:
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
- token = multipart_resume_data.get("token")
3901
- if not token:
3902
- token = self.upload_token
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=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(errno.EINVAL, resp)
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
@@ -6314,7 +6299,7 @@ class P115Client(P115OpenClient):
6314
6299
  url = info["url"]
6315
6300
  if strict and not url:
6316
6301
  raise IsADirectoryError(
6317
- errno.EISDIR,
6302
+ EISDIR,
6318
6303
  f"{fid} is a directory, with response {resp}",
6319
6304
  )
6320
6305
  return P115URL(
@@ -6328,7 +6313,7 @@ class P115Client(P115OpenClient):
6328
6313
  headers=resp["headers"],
6329
6314
  )
6330
6315
  raise FileNotFoundError(
6331
- errno.ENOENT,
6316
+ ENOENT,
6332
6317
  f"no such pickcode: {pickcode!r}, with response {resp}",
6333
6318
  )
6334
6319
  if async_:
@@ -9245,6 +9230,53 @@ class P115Client(P115OpenClient):
9245
9230
  payload["custom_order"] = 1
9246
9231
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
9247
9232
 
9233
+ @overload
9234
+ def fs_files_blank_document(
9235
+ self,
9236
+ payload: str | dict,
9237
+ /,
9238
+ base_url: bool | str | Callable[[], str] = False,
9239
+ *,
9240
+ async_: Literal[False] = False,
9241
+ **request_kwargs,
9242
+ ) -> dict:
9243
+ ...
9244
+ @overload
9245
+ def fs_files_blank_document(
9246
+ self,
9247
+ payload: str | dict,
9248
+ /,
9249
+ base_url: bool | str | Callable[[], str] = False,
9250
+ *,
9251
+ async_: Literal[True],
9252
+ **request_kwargs,
9253
+ ) -> Coroutine[Any, Any, dict]:
9254
+ ...
9255
+ def fs_files_blank_document(
9256
+ self,
9257
+ payload: str | dict,
9258
+ /,
9259
+ base_url: bool | str | Callable[[], str] = False,
9260
+ *,
9261
+ async_: Literal[False, True] = False,
9262
+ **request_kwargs,
9263
+ ) -> dict | Coroutine[Any, Any, dict]:
9264
+ """新建空白 office 文件
9265
+
9266
+ POST https://webapi.115.com/files/blank_document
9267
+
9268
+ :payload:
9269
+ - file_name: str 💡 文件名,不含后缀
9270
+ - pid: int | str = 0 💡 目录 id
9271
+ - type: 1 | 2 | 3 = 1 💡 1:Word文档(.docx) 2:Excel表格(.xlsx) 3:PPT文稿(.pptx)
9272
+ """
9273
+ api = complete_webapi("/files/blank_document", base_url=base_url)
9274
+ if isinstance(payload, str):
9275
+ payload = {"pid": 0, "type": 1, "file_name": payload}
9276
+ else:
9277
+ payload = {"pid": 0, "type": 1, **payload}
9278
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
9279
+
9248
9280
  @overload
9249
9281
  def fs_files_history(
9250
9282
  self,
@@ -9329,7 +9361,7 @@ class P115Client(P115OpenClient):
9329
9361
  POST https://webapi.115.com/files/history
9330
9362
 
9331
9363
  :payload:
9332
- - pick_code: str 💡 视频的提取码
9364
+ - pick_code: str 💡 文件的提取码
9333
9365
  - op: str = "update" 💡 操作类型,具体有哪些还需要再研究
9334
9366
  - category: int = <default>
9335
9367
  - definition: int = <default> 💡 视频清晰度
@@ -17193,13 +17225,13 @@ class P115Client(P115OpenClient):
17193
17225
  file_id = payload["file_id"]
17194
17226
  if not info:
17195
17227
  raise FileNotFoundError(
17196
- errno.ENOENT,
17228
+ ENOENT,
17197
17229
  f"no such id: {file_id!r}, with response {resp}",
17198
17230
  )
17199
17231
  url = info["url"]
17200
17232
  if strict and not url:
17201
17233
  raise IsADirectoryError(
17202
- errno.EISDIR,
17234
+ EISDIR,
17203
17235
  f"{file_id} is a directory, with response {resp}",
17204
17236
  )
17205
17237
  return P115URL(
@@ -17863,8 +17895,8 @@ class P115Client(P115OpenClient):
17863
17895
 
17864
17896
  @overload
17865
17897
  def share_skip_login_download_url(
17866
- self,
17867
- payload: int | str | dict,
17898
+ self: int | str | dict | P115Client,
17899
+ payload: None | int | str | dict = None,
17868
17900
  /,
17869
17901
  url: str = "",
17870
17902
  strict: bool = True,
@@ -17877,8 +17909,8 @@ class P115Client(P115OpenClient):
17877
17909
  ...
17878
17910
  @overload
17879
17911
  def share_skip_login_download_url(
17880
- self,
17881
- payload: int | str | dict,
17912
+ self: int | str | dict | P115Client,
17913
+ payload: None | int | str | dict = None,
17882
17914
  /,
17883
17915
  url: str = "",
17884
17916
  strict: bool = True,
@@ -17890,8 +17922,8 @@ class P115Client(P115OpenClient):
17890
17922
  ) -> Coroutine[Any, Any, P115URL]:
17891
17923
  ...
17892
17924
  def share_skip_login_download_url(
17893
- self,
17894
- payload: int | str | dict,
17925
+ self: int | str | dict | P115Client,
17926
+ payload: None | int | str | dict = None,
17895
17927
  /,
17896
17928
  url: str = "",
17897
17929
  strict: bool = True,
@@ -17903,6 +17935,9 @@ class P115Client(P115OpenClient):
17903
17935
  ) -> P115URL | Coroutine[Any, Any, P115URL]:
17904
17936
  """获取分享链接中某个文件的下载链接
17905
17937
 
17938
+ .. important::
17939
+ 这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
17940
+
17906
17941
  :param payload: 请求参数,如果为 int 或 str,则视为 `file_id`
17907
17942
 
17908
17943
  - file_id: int | str 💡 文件 id
@@ -17918,6 +17953,12 @@ class P115Client(P115OpenClient):
17918
17953
 
17919
17954
  :return: 下载链接
17920
17955
  """
17956
+ if isinstance(self, P115Client):
17957
+ assert payload is not None
17958
+ inst: P115Client | type[P115Client] = self
17959
+ else:
17960
+ payload = self
17961
+ inst = __class__ # type: ignore
17921
17962
  if isinstance(payload, (int, str)):
17922
17963
  payload = {"file_id": payload}
17923
17964
  else:
@@ -17928,21 +17969,21 @@ class P115Client(P115OpenClient):
17928
17969
  payload["share_code"] = share_payload["share_code"]
17929
17970
  payload["receive_code"] = share_payload["receive_code"] or ""
17930
17971
  if use_web_api:
17931
- resp = self.share_skip_login_download_url_web(payload, async_=async_, **request_kwargs)
17972
+ resp = inst.share_skip_login_download_url_web(payload, async_=async_, **request_kwargs)
17932
17973
  else:
17933
- resp = self.share_skip_login_download_url_app(payload, app=app, async_=async_, **request_kwargs)
17974
+ resp = inst.share_skip_login_download_url_app(payload, app=app, async_=async_, **request_kwargs)
17934
17975
  def get_url(resp: dict, /) -> P115URL:
17935
17976
  info = check_response(resp)["data"]
17936
17977
  file_id = payload["file_id"]
17937
17978
  if not info:
17938
17979
  raise FileNotFoundError(
17939
- errno.ENOENT,
17980
+ ENOENT,
17940
17981
  f"no such id: {file_id!r}, with response {resp}",
17941
17982
  )
17942
17983
  url = info["url"]
17943
17984
  if strict and not url:
17944
17985
  raise IsADirectoryError(
17945
- errno.EISDIR,
17986
+ EISDIR,
17946
17987
  f"{file_id} is a directory, with response {resp}",
17947
17988
  )
17948
17989
  return P115URL(
@@ -17962,8 +18003,8 @@ class P115Client(P115OpenClient):
17962
18003
 
17963
18004
  @overload
17964
18005
  def share_skip_login_download_url_app(
17965
- self,
17966
- payload: dict,
18006
+ self: dict | P115Client,
18007
+ payload: None | dict = None,
17967
18008
  /,
17968
18009
  app: str = "",
17969
18010
  base_url: bool | str | Callable[[], str] = False,
@@ -17974,8 +18015,8 @@ class P115Client(P115OpenClient):
17974
18015
  ...
17975
18016
  @overload
17976
18017
  def share_skip_login_download_url_app(
17977
- self,
17978
- payload: dict,
18018
+ self: dict | P115Client,
18019
+ payload: None | dict = None,
17979
18020
  /,
17980
18021
  app: str = "",
17981
18022
  base_url: bool | str | Callable[[], str] = False,
@@ -17985,8 +18026,8 @@ class P115Client(P115OpenClient):
17985
18026
  ) -> Coroutine[Any, Any, dict]:
17986
18027
  ...
17987
18028
  def share_skip_login_download_url_app(
17988
- self,
17989
- payload: dict,
18029
+ self: dict | P115Client,
18030
+ payload: None | dict = None,
17990
18031
  /,
17991
18032
  app: str = "",
17992
18033
  base_url: bool | str | Callable[[], str] = False,
@@ -17998,14 +18039,20 @@ class P115Client(P115OpenClient):
17998
18039
 
17999
18040
  POST https://proapi.115.com/app/share/skip_login_downurl
18000
18041
 
18042
+ .. important::
18043
+ 这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
18044
+
18001
18045
  :payload:
18002
18046
  - file_id: int | str
18003
18047
  - receive_code: str
18004
18048
  - share_code: str
18005
18049
  """
18050
+ if isinstance(self, dict):
18051
+ payload = self
18052
+ else:
18053
+ assert payload is not None
18006
18054
  if app:
18007
18055
  api = complete_proapi("/2.0/share/skip_login_downurl", base_url, app)
18008
- return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18009
18056
  else:
18010
18057
  api = complete_proapi("/app/share/skip_login_downurl", base_url)
18011
18058
  def parse(resp, content: bytes, /) -> dict:
@@ -18015,12 +18062,20 @@ class P115Client(P115OpenClient):
18015
18062
  return resp
18016
18063
  request_kwargs.setdefault("parse", parse)
18017
18064
  payload = {"data": rsa_encode(dumps(payload)).decode()}
18065
+ if isinstance(self, P115Client):
18018
18066
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18067
+ else:
18068
+ request_kwargs.setdefault("parse", default_parse)
18069
+ request = request_kwargs.pop("request", None)
18070
+ if request is None:
18071
+ return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18072
+ else:
18073
+ return request(url=api, method="POST", data=payload, **request_kwargs)
18019
18074
 
18020
18075
  @overload
18021
18076
  def share_skip_login_download_url_web(
18022
- self,
18023
- payload: dict,
18077
+ self: dict | P115Client,
18078
+ payload: None | dict = None,
18024
18079
  /,
18025
18080
  base_url: bool | str | Callable[[], str] = False,
18026
18081
  *,
@@ -18030,8 +18085,8 @@ class P115Client(P115OpenClient):
18030
18085
  ...
18031
18086
  @overload
18032
18087
  def share_skip_login_download_url_web(
18033
- self,
18034
- payload: dict,
18088
+ self: dict | P115Client,
18089
+ payload: None | dict = None,
18035
18090
  /,
18036
18091
  base_url: bool | str | Callable[[], str] = False,
18037
18092
  *,
@@ -18040,8 +18095,8 @@ class P115Client(P115OpenClient):
18040
18095
  ) -> Coroutine[Any, Any, dict]:
18041
18096
  ...
18042
18097
  def share_skip_login_download_url_web(
18043
- self,
18044
- payload: dict,
18098
+ self: dict | P115Client,
18099
+ payload: None | dict = None,
18045
18100
  /,
18046
18101
  base_url: bool | str | Callable[[], str] = False,
18047
18102
  *,
@@ -18052,13 +18107,28 @@ class P115Client(P115OpenClient):
18052
18107
 
18053
18108
  POST https://webapi.115.com/share/skip_login_downurl
18054
18109
 
18110
+ .. important::
18111
+ 这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
18112
+
18055
18113
  :payload:
18056
18114
  - share_code: str 💡 分享码
18057
18115
  - receive_code: str 💡 接收码(访问密码)
18058
18116
  - file_id: int | str 💡 文件 id
18059
18117
  """
18060
18118
  api = complete_webapi("/share/skip_login_downurl", base_url=base_url)
18061
- return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18119
+ if isinstance(self, dict):
18120
+ payload = self
18121
+ else:
18122
+ assert payload is not None
18123
+ if isinstance(self, P115Client):
18124
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18125
+ else:
18126
+ request_kwargs.setdefault("parse", default_parse)
18127
+ request = request_kwargs.pop("request", None)
18128
+ if request is None:
18129
+ return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18130
+ else:
18131
+ return request(url=api, method="POST", data=payload, **request_kwargs)
18062
18132
 
18063
18133
  @overload
18064
18134
  def share_skip_login_down_first(
@@ -18191,7 +18261,7 @@ class P115Client(P115OpenClient):
18191
18261
  GET https://webapi.115.com/share/snap
18192
18262
 
18193
18263
  .. important::
18194
- 这个函数可以作为 staticmethod 使用,只要 `self` dict 类型,此时不需要登录
18264
+ 这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
18195
18265
 
18196
18266
  否则,就是登录状态,但如果这个分享是你自己的,则可以不提供 receive_code,而且即使还在审核中,也能获取文件列表
18197
18267
 
@@ -18266,7 +18336,7 @@ class P115Client(P115OpenClient):
18266
18336
  GET https://proapi.115.com/android/2.0/share/snap
18267
18337
 
18268
18338
  .. important::
18269
- 这个函数可以作为 staticmethod 使用,只要 `self` dict 类型,此时不需要登录
18339
+ 这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
18270
18340
 
18271
18341
  否则,就是登录状态,但如果这个分享是你自己的,则可以不提供 receive_code,而且即使还在审核中,也能获取文件列表
18272
18342
 
@@ -19128,8 +19198,6 @@ class P115Client(P115OpenClient):
19128
19198
 
19129
19199
  :return: 接口响应
19130
19200
  """
19131
- if filesize >= 1 << 20 and read_range_bytes_or_hash is None:
19132
- raise ValueError("filesize >= 1 MB, thus need pass the `read_range_bytes_or_hash` argument")
19133
19201
  filesha1 = filesha1.upper()
19134
19202
  target = f"U_1_{pid}"
19135
19203
  def gen_step():
@@ -19315,8 +19383,6 @@ class P115Client(P115OpenClient):
19315
19383
  )
19316
19384
  return run_gen_step(gen_step, async_=async_)
19317
19385
 
19318
- # TODO: 当文件 < 1 MB 时,文件不急着打开,需要时再打开
19319
- # TODO: 对于上传空文件,有特别的速度(sha1写死)
19320
19386
  @overload # type: ignore
19321
19387
  def upload_file(
19322
19388
  self,
@@ -19455,11 +19521,9 @@ class P115Client(P115OpenClient):
19455
19521
  async_=async_, # type: ignore
19456
19522
  **request_kwargs,
19457
19523
  )
19458
- need_calc_filesha1 = (
19459
- not filesha1 and
19460
- not upload_directly and
19461
- multipart_resume_data is None
19462
- )
19524
+ if filesize == 0:
19525
+ filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
19526
+ need_calc_filesha1 = not filesha1 and not upload_directly and multipart_resume_data is None
19463
19527
  read_range_bytes_or_hash: None | Callable = None
19464
19528
  try:
19465
19529
  file = getattr(file, "getbuffer")()
@@ -19467,7 +19531,9 @@ class P115Client(P115OpenClient):
19467
19531
  pass
19468
19532
  if isinstance(file, Buffer):
19469
19533
  filesize = buffer_length(file)
19470
- if need_calc_filesha1:
19534
+ if filesize == 0:
19535
+ filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
19536
+ elif need_calc_filesha1:
19471
19537
  filesha1 = sha1(file).hexdigest()
19472
19538
  if not upload_directly and multipart_resume_data is None and filesize >= 1 << 20:
19473
19539
  view = memoryview(file)
@@ -19520,7 +19586,9 @@ class P115Client(P115OpenClient):
19520
19586
  filesize = (yield seek(0, 2)) - curpos
19521
19587
  finally:
19522
19588
  yield seek(curpos)
19523
- if not upload_directly and multipart_resume_data is None and filesize >= 1 << 20:
19589
+ if filesize == 0:
19590
+ filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
19591
+ elif not upload_directly and multipart_resume_data is None and filesize >= 1 << 20:
19524
19592
  read: Callable[[int], Buffer] | Callable[[int], Awaitable[Buffer]]
19525
19593
  if seekable:
19526
19594
  if async_:
@@ -19577,16 +19645,24 @@ class P115Client(P115OpenClient):
19577
19645
  url = cast(str, multipart_resume_data.get("url", ""))
19578
19646
  if not url:
19579
19647
  url = self.upload_endpoint_url(bucket, object)
19580
- token = multipart_resume_data.get("token")
19581
- if not token:
19582
- token = self.upload_token
19648
+ callback_var = loads(multipart_resume_data["callback"]["callback_var"])
19649
+ yield self.upload_resume(
19650
+ {
19651
+ "fileid": object,
19652
+ "filesize": multipart_resume_data["filesize"],
19653
+ "target": callback_var["x:target"],
19654
+ "pickcode": callback_var["x:pick_code"],
19655
+ },
19656
+ async_=async_,
19657
+ **request_kwargs,
19658
+ )
19583
19659
  return oss_multipart_upload(
19584
19660
  self.request,
19585
19661
  file, # type: ignore
19586
19662
  url=url,
19587
19663
  bucket=bucket,
19588
19664
  object=object,
19589
- token=token,
19665
+ token=self.upload_token,
19590
19666
  callback=multipart_resume_data["callback"],
19591
19667
  upload_id=multipart_resume_data["upload_id"],
19592
19668
  partsize=multipart_resume_data["partsize"],
@@ -19631,7 +19707,7 @@ class P115Client(P115OpenClient):
19631
19707
  elif status == 1 and statuscode == 0:
19632
19708
  bucket, object, callback = resp["bucket"], resp["object"], resp["callback"]
19633
19709
  else:
19634
- raise P115OSError(errno.EINVAL, resp)
19710
+ raise P115OSError(EINVAL, resp)
19635
19711
  url = self.upload_endpoint_url(bucket, object)
19636
19712
  token = self.upload_token
19637
19713
  if partsize <= 0:
@@ -19781,11 +19857,10 @@ class P115Client(P115OpenClient):
19781
19857
  return self.request(url=api, async_=async_, **request_kwargs)
19782
19858
 
19783
19859
  @overload # type: ignore
19784
- @staticmethod
19785
19860
  def user_info(
19786
- payload: int | str | dict,
19861
+ self: int | str | dict | P115Client,
19862
+ payload: None | int | str | dict = None,
19787
19863
  /,
19788
- request: None | Callable = None,
19789
19864
  base_url: bool | str | Callable[[], str] = False,
19790
19865
  *,
19791
19866
  async_: Literal[False] = False,
@@ -19793,22 +19868,20 @@ class P115Client(P115OpenClient):
19793
19868
  ) -> dict:
19794
19869
  ...
19795
19870
  @overload
19796
- @staticmethod
19797
19871
  def user_info(
19798
- payload: int | str | dict,
19872
+ self: int | str | dict | P115Client,
19873
+ payload: None | int | str | dict = None,
19799
19874
  /,
19800
- request: None | Callable = None,
19801
19875
  base_url: bool | str | Callable[[], str] = False,
19802
19876
  *,
19803
19877
  async_: Literal[True],
19804
19878
  **request_kwargs,
19805
19879
  ) -> Coroutine[Any, Any, dict]:
19806
19880
  ...
19807
- @staticmethod
19808
19881
  def user_info(
19809
- payload: int | str | dict,
19882
+ self: int | str | dict | P115Client,
19883
+ payload: None | int | str | dict = None,
19810
19884
  /,
19811
- request: None | Callable = None,
19812
19885
  base_url: bool | str | Callable[[], str] = False,
19813
19886
  *,
19814
19887
  async_: Literal[False, True] = False,
@@ -19818,19 +19891,31 @@ class P115Client(P115OpenClient):
19818
19891
 
19819
19892
  GET https://my.115.com/proapi/3.0/index.php?method=user_info
19820
19893
 
19894
+ .. important::
19895
+ 这个函数可以作为 staticmethod 使用,只要 `self` 不是 P115Client 类型,此时不需要登录
19896
+
19821
19897
  :payload:
19822
19898
  - uid: int | str
19823
19899
  """
19824
19900
  api = complete_api("/proapi/3.0/index.php", "my", base_url=base_url)
19901
+ if isinstance(self, P115Client):
19902
+ if payload is None:
19903
+ payload = self.user_id
19904
+ else:
19905
+ payload = self
19825
19906
  if isinstance(payload, (int, str)):
19826
19907
  payload = {"uid": payload, "method": "user_info"}
19827
19908
  else:
19828
19909
  payload = {"method": "user_info", **payload}
19829
- request_kwargs.setdefault("parse", default_parse)
19830
- if request is None:
19831
- return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
19910
+ if isinstance(self, P115Client):
19911
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
19832
19912
  else:
19833
- return request(url=api, params=payload, **request_kwargs)
19913
+ request_kwargs.setdefault("parse", default_parse)
19914
+ request = request_kwargs.pop("request", None)
19915
+ if request is None:
19916
+ return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
19917
+ else:
19918
+ return request(url=api, params=payload, **request_kwargs)
19834
19919
 
19835
19920
  @overload
19836
19921
  def user_my(
@@ -20363,6 +20448,49 @@ class P115Client(P115OpenClient):
20363
20448
  api = complete_proapi("/vip/check_spw", base_url, app)
20364
20449
  return self.request(url=api, async_=async_, **request_kwargs)
20365
20450
 
20451
+ @overload
20452
+ def user_vip_limit(
20453
+ self,
20454
+ payload: int | dict = 2,
20455
+ /,
20456
+ base_url: bool | str | Callable[[], str] = False,
20457
+ *,
20458
+ async_: Literal[False] = False,
20459
+ **request_kwargs,
20460
+ ) -> dict:
20461
+ ...
20462
+ @overload
20463
+ def user_vip_limit(
20464
+ self,
20465
+ payload: int | dict = 2,
20466
+ /,
20467
+ base_url: bool | str | Callable[[], str] = False,
20468
+ *,
20469
+ async_: Literal[True],
20470
+ **request_kwargs,
20471
+ ) -> Coroutine[Any, Any, dict]:
20472
+ ...
20473
+ def user_vip_limit(
20474
+ self,
20475
+ payload: int | dict = 2,
20476
+ /,
20477
+ base_url: bool | str | Callable[[], str] = False,
20478
+ *,
20479
+ async_: Literal[False, True] = False,
20480
+ **request_kwargs,
20481
+ ) -> dict | Coroutine[Any, Any, dict]:
20482
+ """获取 vip 的某些限制
20483
+
20484
+ GET https://webapi.115.com/user/vip_limit
20485
+
20486
+ :payload:
20487
+ - feature: int = 2
20488
+ """
20489
+ api = complete_webapi("/user/vip_limit", base_url=base_url)
20490
+ if isinstance(payload, int):
20491
+ payload = {"feature": payload}
20492
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
20493
+
20366
20494
  ########## User Share API ##########
20367
20495
 
20368
20496
  @overload
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: p115client
3
- Version: 0.0.5.8.1
3
+ Version: 0.0.5.8.3
4
4
  Summary: Python 115 webdisk client.
5
5
  Home-page: https://github.com/ChenyangGao/p115client
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
2
  p115client/__init__.py,sha256=1mx7njuAlqcuEWONTjSiiGnXyyNyqOcJyNX1FMHqQ-4,214
3
3
  p115client/_upload.py,sha256=j2XHz6-hc9qyfiF92aZY-LPJ3UgbB6e4Jy6CNGQ5rwk,29904
4
- p115client/client.py,sha256=BCga9CmcXjPovtNvvVHadTv6E3SztBomqNSidWW-WpY,707805
4
+ p115client/client.py,sha256=-ccBfbe6MxmR-YnKK3LAiqbdh6kKXrm142qgdbPqC14,712993
5
5
  p115client/const.py,sha256=maIZfJAiUuEnXIKc8TMAyW_UboDUJPwYpPS8LjPFp_U,4321
6
6
  p115client/exception.py,sha256=Ugjr__aSlYRDYwoOz7273ngV-gFX2z-ohsJmCba8nnQ,2657
7
7
  p115client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -17,7 +17,7 @@ p115client/tool/request.py,sha256=SWsezW9EYZGS3R-TbZxMG-8bN3YWJ0-GzgvKlvRBSCM,70
17
17
  p115client/tool/upload.py,sha256=qK1OQYxP-Faq2eMDhc5sBXJiSr8m8EZ_gb0O_iA2TrI,15915
18
18
  p115client/tool/xys.py,sha256=n89n9OLBXx6t20L61wJgfrP6V4jW3sHgyaQNBLdUwUQ,3578
19
19
  p115client/type.py,sha256=e4g9URQBE23XN2dGomldj8wC6NlDWBBSVC5Bmd8giBc,5993
20
- p115client-0.0.5.8.1.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
21
- p115client-0.0.5.8.1.dist-info/METADATA,sha256=iJ9MArKcRNHtI3JP6Zbx-cJiyrbqh-r78fuJoji4Wl4,8232
22
- p115client-0.0.5.8.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
- p115client-0.0.5.8.1.dist-info/RECORD,,
20
+ p115client-0.0.5.8.3.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
21
+ p115client-0.0.5.8.3.dist-info/METADATA,sha256=X_28kGMwtVVlShi6LjvxgWsDpVHsDu0QUnhxEjOiId0,8232
22
+ p115client-0.0.5.8.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
+ p115client-0.0.5.8.3.dist-info/RECORD,,