p115client 0.0.5.10.4__py3-none-any.whl → 0.0.5.11__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
@@ -65,8 +65,8 @@ from yarl import URL
65
65
 
66
66
  from .const import CLASS_TO_TYPE, CLIENT_API_MAP, SSOENT_TO_APP, SUFFIX_TO_TYPE, errno
67
67
  from .exception import (
68
- AuthenticationError, BusyOSError, DataError, LoginError, NotSupportedError,
69
- P115OSError, OperationalError, P115Warning, P115FileExistsError,
68
+ AuthenticationError, BusyOSError, DataError, LoginError, OpenAppAuthLimitExceeded,
69
+ NotSupportedError, P115OSError, OperationalError, P115Warning, P115FileExistsError,
70
70
  P115FileNotFoundError, P115IsADirectoryError,
71
71
  )
72
72
  from .type import RequestKeywords, MultipartResumeData, P115Cookies, P115URL
@@ -181,7 +181,7 @@ def complete_proapi(
181
181
  ) -> str:
182
182
  if path and not path.startswith("/"):
183
183
  path = "/" + path
184
- if app == "open":
184
+ if app in ("aps", "desktop", "open", "web"):
185
185
  app = "android"
186
186
  if app and not app.startswith("/"):
187
187
  app = "/" + app
@@ -266,7 +266,7 @@ def get_default_request():
266
266
  return _httpx_request
267
267
 
268
268
 
269
- def parse_upload_init_response(resp, content: bytes, /) -> dict:
269
+ def parse_upload_init_response(_, content: bytes, /) -> dict:
270
270
  data = ecdh_aes_decode(content, decompress=True)
271
271
  if not isinstance(data, (bytes, bytearray, memoryview)):
272
272
  data = memoryview(data)
@@ -339,6 +339,10 @@ def get_first(m: Mapping, /, *keys, default=None):
339
339
  return default
340
340
 
341
341
 
342
+ def contains_any(m: Mapping, /, *keys):
343
+ return any(k in m for k in keys)
344
+
345
+
342
346
  @overload
343
347
  def check_response(resp: dict, /) -> dict:
344
348
  ...
@@ -525,7 +529,7 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
525
529
  raise OperationalError(errno.EINVAL, resp)
526
530
  # {"state": 0, "errno": 40140122, "error": "超出授权应用个数上限"}
527
531
  case 40140122:
528
- raise OperationalError(errno.EINVAL, resp)
532
+ raise OpenAppAuthLimitExceeded(errno.EDQUOT, resp)
529
533
  # {"state": 0, "errno": 40140123, "error": "access_token 格式错误(防篡改)"}
530
534
  case 40140123:
531
535
  raise OperationalError(errno.EINVAL, resp)
@@ -538,6 +542,36 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
538
542
  # {"state": 0, "errno": 40140126, "error": "access_token 校验失败(防篡改)"}
539
543
  case 40140126:
540
544
  raise OperationalError(errno.EINVAL, resp)
545
+ # {"state": 0, "errno": 40140127, "error": "response_type 错误"}
546
+ case 40140127:
547
+ raise OperationalError(errno.EINVAL, resp)
548
+ # {"state": 0, "errno": 40140128, "error": "redirect_uri 缺少协议"}
549
+ case 40140128:
550
+ raise OperationalError(errno.EINVAL, resp)
551
+ # {"state": 0, "errno": 40140129, "error": "redirect_uri 缺少域名"}
552
+ case 40140129:
553
+ raise OperationalError(errno.EINVAL, resp)
554
+ # {"state": 0, "errno": 40140130, "error": "没有配置重定向域名"}
555
+ case 40140130:
556
+ raise OperationalError(errno.EINVAL, resp)
557
+ # {"state": 0, "errno": 40140131, "error": "redirect_uri 非法域名"}
558
+ case 40140131:
559
+ raise OperationalError(errno.EINVAL, resp)
560
+ # {"state": 0, "errno": 40140132, "error": "grant_type 错误"}
561
+ case 40140132:
562
+ raise OperationalError(errno.EINVAL, resp)
563
+ # {"state": 0, "errno": 40140133, "error": "client_secret 验证失败"}
564
+ case 40140133:
565
+ raise OperationalError(errno.EINVAL, resp)
566
+ # {"state": 0, "errno": 40140134, "error": "授权码 code 验证失败"}
567
+ case 40140134:
568
+ raise OperationalError(errno.EINVAL, resp)
569
+ # {"state": 0, "errno": 40140135, "error": "client_id 验证失败"}
570
+ case 40140135:
571
+ raise OperationalError(errno.EINVAL, resp)
572
+ # {"state": 0, "errno": 40140136, "error": "redirect_uri 验证失败(防MITM)"}
573
+ case 40140136:
574
+ raise OperationalError(errno.EINVAL, resp)
541
575
  elif "msg_code" in resp:
542
576
  match resp["msg_code"]:
543
577
  case 50028:
@@ -612,9 +646,6 @@ def normalize_attr_web(
612
646
  if "te" in info:
613
647
  attr["mtime"] = int(info["te"])
614
648
  else:
615
- attr["labels"] = info["fl"]
616
- if "score" in info:
617
- attr["score"] = int(info.get("score") or 0)
618
649
  attr["ico"] = info.get("ico", "folder" if is_directory else "")
619
650
  if "te" in info:
620
651
  attr["mtime"] = attr["user_utime"] = int(info["te"])
@@ -647,12 +678,14 @@ def normalize_attr_web(
647
678
  ("c", "violated"),
648
679
  ("c", "is_collect"),
649
680
  ("sh", "is_share"),
681
+ ("score", "score"),
650
682
  #("d", "has_desc"),
651
683
  #("p", "has_pass"),
652
684
  ):
653
685
  if key in info:
654
686
  attr[name] = int(info[key] or 0)
655
687
  for key, name in (
688
+ ("fl", "labels"),
656
689
  ("dp", "dir_path"),
657
690
  ("style", "style"),
658
691
  ("ns", "name_show"),
@@ -740,10 +773,7 @@ def normalize_attr_app(
740
773
  if "upt" in info:
741
774
  attr["mtime"] = int(info["upt"])
742
775
  else:
743
- attr["labels"] = info["fl"]
744
776
  attr["ico"] = info.get("ico", "folder" if attr["is_dir"] else "")
745
- if "ftype" in info:
746
- attr["file_type"] = int(info["ftype"] or 0)
747
777
  if "thumb" in info:
748
778
  thumb = info["thumb"]
749
779
  if thumb.startswith("?"):
@@ -759,6 +789,7 @@ def normalize_attr_app(
759
789
  ("aid", "area_id"),
760
790
  ("fatr", "audio_play_long"),
761
791
  ("fta", "status"),
792
+ ("ftype", "file_type"),
762
793
  ("ism", "star"),
763
794
  ("ism", "is_mark"),
764
795
  ("is_top", "is_top"),
@@ -780,6 +811,7 @@ def normalize_attr_app(
780
811
  ("fco", "cover"),
781
812
  ("fco", "folder_cover"),
782
813
  ("fdesc", "desc"),
814
+ ("fl", "labels"),
783
815
  ("flabel", "fflabel"),
784
816
  ("multitrack", "multitrack"),
785
817
  ("play_long", "play_long"),
@@ -834,18 +866,28 @@ def normalize_attr_app2(
834
866
  else:
835
867
  dict_cls = AttrDict
836
868
  attr: dict[str, Any] = dict_cls()
837
- is_directory = attr["is_dir"] = "file_id" not in info
838
- if not simple:
839
- attr["is_directory"] = is_directory
840
- if is_directory:
869
+ if "file_id" in info and "parent_id" in info:
870
+ if "file_category" in info:
871
+ is_directory = not int(info["file_category"])
872
+ else:
873
+ is_directory = bool(info.get("sha1") or info.get("file_sha1"))
841
874
  attr["id"] = int(info["file_id"])
842
- attr["parent_id"] = int(info["category_id"])
875
+ attr["parent_id"] = int(info["parent_id"])
843
876
  attr["name"] = info["file_name"]
844
877
  else:
845
- attr["id"] = int(info["category_id"])
846
- attr["parent_id"] = int(info["parent_id"])
847
- attr["name"] = info["category_name"]
848
- attr["sha1"] = info.get("sha1") or ""
878
+ is_directory = "file_id" not in info
879
+ if is_directory:
880
+ attr["id"] = int(info["file_id"])
881
+ attr["parent_id"] = int(info["category_id"])
882
+ attr["name"] = info["file_name"]
883
+ else:
884
+ attr["id"] = int(info["category_id"])
885
+ attr["parent_id"] = int(info["parent_id"])
886
+ attr["name"] = info["category_name"]
887
+ attr["is_dir"] = is_directory
888
+ if not simple:
889
+ attr["is_directory"] = is_directory
890
+ attr["sha1"] = info.get("sha1") or info.get("file_sha1") or ""
849
891
  attr["size"] = int(info.get("file_size") or 0)
850
892
  if "pick_code" in info:
851
893
  attr["pickcode"] = info["pick_code"]
@@ -886,7 +928,8 @@ def normalize_attr_app2(
886
928
  if "utime" in info:
887
929
  attr["utime"] = int(info["utime"])
888
930
  attr["ico"] = info.get("ico", "folder" if attr["is_dir"] else "")
889
- attr["labels"] = info["fl"]
931
+ if "fl" in info:
932
+ attr["labels"] = info["fl"]
890
933
  for key, name in (
891
934
  ("area_id", "area_id"),
892
935
  ("has_desc", "has_desc"),
@@ -1154,13 +1197,17 @@ class ClientRequestMixin:
1154
1197
  "user-agent": "Mozilla/5.0 AppleWebKit/600 Safari/600 Chrome/124.0.0.0",
1155
1198
  })
1156
1199
 
1200
+ @locked_cacheproperty
1201
+ def request_kwargs(self, /) -> dict:
1202
+ return {}
1203
+
1157
1204
  def close(self, /) -> None:
1158
1205
  """删除 session 和 async_session 属性,如果它们未被引用,则应该会被自动清理
1159
1206
  """
1160
1207
  self.__dict__.pop("session", None)
1161
1208
  self.__dict__.pop("async_session", None)
1162
1209
 
1163
- def request(
1210
+ def _request(
1164
1211
  self,
1165
1212
  /,
1166
1213
  url: str,
@@ -1278,14 +1325,203 @@ class ClientRequestMixin:
1278
1325
  request_kwargs.setdefault("parse", default_parse)
1279
1326
  return request(url=url, method=method, **request_kwargs)
1280
1327
 
1328
+ def request(
1329
+ self,
1330
+ /,
1331
+ url: str,
1332
+ method: str = "GET",
1333
+ params = None,
1334
+ data = None,
1335
+ *,
1336
+ async_: Literal[False, True] = False,
1337
+ **request_kwargs,
1338
+ ):
1339
+ """帮助函数:可执行同步和异步的网络请求
1340
+
1341
+ :param url: HTTP 的请求链接
1342
+ :param method: HTTP 的请求方法
1343
+ :param params: 查询参数
1344
+ :param ecdh_encrypt: 使用 ecdh 算法进行加密(返回值也要解密)
1345
+ :param async_: 说明 `request` 是同步调用还是异步调用
1346
+ :param request: HTTP 请求调用,如果为 None,则默认用 httpx 执行请求
1347
+ 如果传入调用,则必须至少能接受以下几个关键词参数:
1348
+
1349
+ - url: HTTP 的请求链接
1350
+ - method: HTTP 的请求方法
1351
+ - headers: HTTP 的请求头
1352
+ - data: HTTP 的请求体
1353
+ - parse: 解析 HTTP 响应的方法,默认会构建一个 Callable,会把响应的字节数据视为 JSON 进行反序列化解析
1354
+
1355
+ - 如果为 None,则直接把响应对象返回
1356
+ - 如果为 ...(Ellipsis),则把响应对象关闭后将其返回
1357
+ - 如果为 True,则根据响应头来确定把响应得到的字节数据解析成何种格式(反序列化),请求也会被自动关闭
1358
+ - 如果为 False,则直接返回响应得到的字节数据,请求也会被自动关闭
1359
+ - 如果为 Callable,则使用此调用来解析数据,接受 1-2 个位置参数,并把解析结果返回给 `request` 的调用者,请求也会被自动关闭
1360
+ - 如果只接受 1 个位置参数,则把响应对象传给它
1361
+ - 如果能接受 2 个位置参数,则把响应对象和响应得到的字节数据(响应体)传给它
1362
+
1363
+ :param request_kwargs: 其余的请求参数,会被传给 `request`
1364
+
1365
+ :return: 直接返回 `request` 执行请求后的返回值
1366
+
1367
+ .. note::
1368
+ `request` 可以由不同的请求库来提供,下面是封装了一些模块
1369
+
1370
+ 1. `httpx_request <https://pypi.org/project/httpx_request/>`_,由 `httpx <https://pypi.org/project/httpx/>`_ 封装,支持同步和异步调用,本模块默认用的就是这个封装
1371
+
1372
+ .. code:: python
1373
+
1374
+ from httpx_request import request
1375
+
1376
+ 2. `python-urlopen <https://pypi.org/project/python-urlopen/>`_,由 `urllib.request.urlopen <https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen>`_ 封装,支持同步调用,性能相对最差
1377
+
1378
+ .. code:: python
1379
+
1380
+ from urlopen import request
1381
+
1382
+ 3. `urllib3_request <https://pypi.org/project/urllib3_request/>`_,由 `urllib3 <https://pypi.org/project/urllib3/>`_ 封装,支持同步调用,性能相对较好,推荐使用
1383
+
1384
+ .. code:: python
1385
+
1386
+ from urllib3_request import request
1387
+
1388
+ 4. `requests_request <https://pypi.org/project/requests_request/>`_,由 `requests <https://pypi.org/project/requests/>`_ 封装,支持同步调用
1389
+
1390
+ .. code:: python
1391
+
1392
+ from requests_request import request
1393
+
1394
+ 5. `aiohttp_client_request <https://pypi.org/project/aiohttp_client_request/>`_,由 `aiohttp <https://pypi.org/project/aiohttp/>`_ 封装,支持异步调用,异步并发能力最强,推荐使用
1395
+
1396
+ .. code:: python
1397
+
1398
+ from aiohttp_client_request import request
1399
+
1400
+ 6. `blacksheep_client_request <https://pypi.org/project/blacksheep_client_request/>`_,由 `blacksheep <https://pypi.org/project/blacksheep/>`_ 封装,支持异步调用
1401
+
1402
+ .. code:: python
1403
+
1404
+ from blacksheep_client_request import request
1405
+ """
1406
+ kwargs = {**self.request_kwargs, **request_kwargs}
1407
+ return self._request(url, method, params, data, async_=async_, **kwargs)
1408
+
1281
1409
  ########## Qrcode API ##########
1282
1410
 
1411
+ @overload
1412
+ def login_authorize_open(
1413
+ self,
1414
+ payload: dict,
1415
+ /,
1416
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1417
+ *,
1418
+ async_: Literal[False] = False,
1419
+ **request_kwargs,
1420
+ ) -> dict:
1421
+ ...
1422
+ @overload
1423
+ def login_authorize_open(
1424
+ self,
1425
+ payload: dict,
1426
+ /,
1427
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1428
+ *,
1429
+ async_: Literal[True],
1430
+ **request_kwargs,
1431
+ ) -> Coroutine[Any, Any, dict]:
1432
+ ...
1433
+ def login_authorize_open(
1434
+ self,
1435
+ payload: dict,
1436
+ /,
1437
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1438
+ *,
1439
+ async_: Literal[False, True] = False,
1440
+ **request_kwargs,
1441
+ ) -> dict | Coroutine[Any, Any, dict]:
1442
+ """授权码方式请求开放接口应用授权
1443
+
1444
+ GET https://qrcodeapi.115.com/open/authorize
1445
+
1446
+ .. admonition:: Reference
1447
+
1448
+ https://www.yuque.com/115yun/open/okr2cq0wywelscpe#EiOrD
1449
+
1450
+ :payload:
1451
+ - client_id: int | str 💡 AppID
1452
+ - redirect_uri: str 💡 授权成功后重定向到指定的地址并附上授权码 code,需要先到 https://open.115.com/ 应用管理应用域名设置
1453
+ - response_type: str = "code" 💡 授权模式,固定为 code,表示授权码模式
1454
+ - state: int | str = <default> 💡 随机值,会通过 redirect_uri 原样返回,可用于验证以防 MITM 和 CSRF
1455
+ """
1456
+ api = complete_api("/open/authorize", base_url=base_url)
1457
+ payload = {"response_type": "code", **payload}
1458
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
1459
+
1460
+ @overload
1461
+ @staticmethod
1462
+ def login_authorize_access_token_open(
1463
+ payload: dict,
1464
+ /,
1465
+ request: None | Callable = None,
1466
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1467
+ *,
1468
+ async_: Literal[False] = False,
1469
+ **request_kwargs,
1470
+ ) -> dict:
1471
+ ...
1472
+ @overload
1473
+ @staticmethod
1474
+ def login_authorize_access_token_open(
1475
+ payload: dict,
1476
+ /,
1477
+ request: None | Callable = None,
1478
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1479
+ *,
1480
+ async_: Literal[True],
1481
+ **request_kwargs,
1482
+ ) -> Coroutine[Any, Any, dict]:
1483
+ ...
1484
+ @staticmethod
1485
+ def login_authorize_access_token_open(
1486
+ payload: dict,
1487
+ /,
1488
+ request: None | Callable = None,
1489
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1490
+ *,
1491
+ async_: Literal[False, True] = False,
1492
+ **request_kwargs,
1493
+ ) -> dict | Coroutine[Any, Any, dict]:
1494
+ """用授权码获取开放接口应用的 access_token
1495
+
1496
+ POST https://qrcodeapi.115.com/open/authCodeToToken
1497
+
1498
+ .. admonition:: Reference
1499
+
1500
+ https://www.yuque.com/115yun/open/okr2cq0wywelscpe#JnDgl
1501
+
1502
+ :payload:
1503
+ - client_id: int | str 💡 AppID
1504
+ - client_secret: str 💡 AppSecret
1505
+ - code: str 💡 授权码,/open/authCodeToToken 重定向地址里面
1506
+ - redirect_uri: str 💡 与 /open/authCodeToToken 传的 redirect_uri 一致,可用于验证以防 MITM 和 CSRF
1507
+ - grant_type: str = "authorization_code" 💡 授权类型,固定为 authorization_code,表示授权码类型
1508
+ """
1509
+ api = complete_api("/open/authCodeToToken", base_url=base_url)
1510
+ payload = {"grant_type": "authorization_code", **payload}
1511
+ request_kwargs.setdefault("parse", default_parse)
1512
+ if request is None:
1513
+ return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
1514
+ else:
1515
+ return request(url=api, method="POST", data=payload, **request_kwargs)
1516
+
1283
1517
  @overload
1284
1518
  @staticmethod
1285
1519
  def login_qrcode(
1286
1520
  payload: str | dict,
1287
1521
  /,
1288
1522
  request: None | Callable = None,
1523
+ app: str = "web",
1524
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1289
1525
  *,
1290
1526
  async_: Literal[False] = False,
1291
1527
  **request_kwargs,
@@ -1297,6 +1533,8 @@ class ClientRequestMixin:
1297
1533
  payload: str | dict,
1298
1534
  /,
1299
1535
  request: None | Callable = None,
1536
+ app: str = "web",
1537
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1300
1538
  *,
1301
1539
  async_: Literal[True],
1302
1540
  **request_kwargs,
@@ -1307,6 +1545,8 @@ class ClientRequestMixin:
1307
1545
  payload: str | dict,
1308
1546
  /,
1309
1547
  request: None | Callable = None,
1548
+ app: str = "web",
1549
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1310
1550
  *,
1311
1551
  async_: Literal[False, True] = False,
1312
1552
  **request_kwargs,
@@ -1319,7 +1559,7 @@ class ClientRequestMixin:
1319
1559
 
1320
1560
  :return: 图片的二进制数据(PNG 图片)
1321
1561
  """
1322
- api = "https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode"
1562
+ api = complete_api(f"/api/1.0/{app}/1.0/qrcode", base_url=base_url)
1323
1563
  if isinstance(payload, str):
1324
1564
  payload = {"uid": payload}
1325
1565
  request_kwargs.setdefault("parse", False)
@@ -1334,6 +1574,7 @@ class ClientRequestMixin:
1334
1574
  payload: str | dict,
1335
1575
  /,
1336
1576
  request: None | Callable = None,
1577
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1337
1578
  *,
1338
1579
  async_: Literal[False] = False,
1339
1580
  **request_kwargs,
@@ -1345,6 +1586,7 @@ class ClientRequestMixin:
1345
1586
  payload: str | dict,
1346
1587
  /,
1347
1588
  request: None | Callable = None,
1589
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1348
1590
  *,
1349
1591
  async_: Literal[True],
1350
1592
  **request_kwargs,
@@ -1355,6 +1597,7 @@ class ClientRequestMixin:
1355
1597
  payload: str | dict,
1356
1598
  /,
1357
1599
  request: None | Callable = None,
1600
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1358
1601
  *,
1359
1602
  async_: Literal[False, True] = False,
1360
1603
  **request_kwargs,
@@ -1364,13 +1607,14 @@ class ClientRequestMixin:
1364
1607
  POST https://qrcodeapi.115.com/open/deviceCodeToToken
1365
1608
 
1366
1609
  .. admonition:: Reference
1610
+
1367
1611
  https://www.yuque.com/115yun/open/shtpzfhewv5nag11#QCCVQ
1368
1612
 
1369
1613
  :payload:
1370
1614
  - uid: str
1371
1615
  - code_verifier: str = <default> 💡 默认字符串是 64 个 "0"
1372
1616
  """
1373
- api = "https://qrcodeapi.115.com/open/deviceCodeToToken"
1617
+ api = complete_api("/open/deviceCodeToToken", base_url=base_url)
1374
1618
  if isinstance(payload, str):
1375
1619
  payload = {"uid": payload, "code_verifier": _default_code_verifier}
1376
1620
  request_kwargs.setdefault("parse", default_parse)
@@ -1380,138 +1624,175 @@ class ClientRequestMixin:
1380
1624
  return request(url=api, method="POST", data=payload, **request_kwargs)
1381
1625
 
1382
1626
  @overload
1383
- @staticmethod
1384
- def login_qrcode_refresh_token_open(
1627
+ def login_qrcode_scan(
1628
+ self,
1385
1629
  payload: str | dict,
1386
1630
  /,
1387
- request: None | Callable = None,
1631
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1388
1632
  *,
1389
1633
  async_: Literal[False] = False,
1390
1634
  **request_kwargs,
1391
1635
  ) -> dict:
1392
1636
  ...
1393
1637
  @overload
1394
- @staticmethod
1395
- def login_qrcode_refresh_token_open(
1638
+ def login_qrcode_scan(
1639
+ self,
1396
1640
  payload: str | dict,
1397
1641
  /,
1398
- request: None | Callable = None,
1642
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1399
1643
  *,
1400
1644
  async_: Literal[True],
1401
1645
  **request_kwargs,
1402
1646
  ) -> Coroutine[Any, Any, dict]:
1403
1647
  ...
1404
- @staticmethod
1405
- def login_qrcode_refresh_token_open(
1648
+ def login_qrcode_scan(
1649
+ self,
1406
1650
  payload: str | dict,
1407
1651
  /,
1408
- request: None | Callable = None,
1652
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1409
1653
  *,
1410
1654
  async_: Literal[False, True] = False,
1411
1655
  **request_kwargs,
1412
1656
  ) -> dict | Coroutine[Any, Any, dict]:
1413
- """用一个 refresh_token 去获取新的 access_token 和 refresh_token,然后原来的 refresh_token 作废
1414
-
1415
- POST https://qrcodeapi.115.com/open/refreshToken
1657
+ """扫描二维码,payload 数据取自 `login_qrcode_token` 接口响应
1416
1658
 
1417
- .. admonition:: Reference
1418
- https://www.yuque.com/115yun/open/shtpzfhewv5nag11#ve54x
1659
+ GET https://qrcodeapi.115.com/api/2.0/prompt.php
1419
1660
 
1420
1661
  :payload:
1421
- - refresh_token: str
1662
+ - uid: str
1422
1663
  """
1423
- api = "https://qrcodeapi.115.com/open/refreshToken"
1664
+ api = complete_api("/api/2.0/prompt.php", base_url=base_url)
1424
1665
  if isinstance(payload, str):
1425
- payload = {"refresh_token": payload}
1426
- request_kwargs.setdefault("parse", default_parse)
1427
- if request is None:
1428
- return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
1429
- else:
1430
- return request(url=api, method="POST", data=payload, **request_kwargs)
1666
+ payload = {"uid": payload}
1667
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
1431
1668
 
1432
1669
  @overload
1433
- @staticmethod
1434
1670
  def login_qrcode_scan_cancel(
1671
+ self,
1435
1672
  payload: str | dict,
1436
1673
  /,
1437
- request: None | Callable = None,
1674
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1438
1675
  *,
1439
1676
  async_: Literal[False] = False,
1440
1677
  **request_kwargs,
1441
1678
  ) -> dict:
1442
1679
  ...
1443
1680
  @overload
1444
- @staticmethod
1445
1681
  def login_qrcode_scan_cancel(
1682
+ self,
1446
1683
  payload: str | dict,
1447
1684
  /,
1448
- request: None | Callable = None,
1685
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1449
1686
  *,
1450
1687
  async_: Literal[True],
1451
1688
  **request_kwargs,
1452
1689
  ) -> Coroutine[Any, Any, dict]:
1453
1690
  ...
1454
- @staticmethod
1455
1691
  def login_qrcode_scan_cancel(
1692
+ self,
1456
1693
  payload: str | dict,
1457
1694
  /,
1458
- request: None | Callable = None,
1695
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1459
1696
  *,
1460
1697
  async_: Literal[False, True] = False,
1461
1698
  **request_kwargs,
1462
1699
  ) -> dict | Coroutine[Any, Any, dict]:
1463
1700
  """取消扫描二维码,payload 数据取自 `login_qrcode_scan` 接口响应
1464
1701
 
1465
- GET https://hnqrcodeapi.115.com/api/2.0/cancel.php
1702
+ GET https://qrcodeapi.115.com/api/2.0/cancel.php
1466
1703
 
1467
1704
  :payload:
1468
1705
  - key: str
1469
1706
  - uid: str
1470
1707
  - client: int = 0
1471
1708
  """
1472
- api = "https://hnqrcodeapi.115.com/api/2.0/cancel.php"
1709
+ api = complete_api("/api/2.0/cancel.php", base_url=base_url)
1473
1710
  if isinstance(payload, str):
1474
1711
  payload = {"key": payload, "uid": payload, "client": 0}
1475
- request_kwargs.setdefault("parse", default_parse)
1476
- if request is None:
1477
- return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
1478
- else:
1479
- return request(url=api, params=payload, **request_kwargs)
1712
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
1480
1713
 
1481
1714
  @overload
1482
- @staticmethod
1483
- def login_qrcode_scan_result(
1484
- uid: str,
1485
- app: str = "alipaymini",
1486
- request: None | Callable = None,
1715
+ def login_qrcode_scan_confirm(
1716
+ self,
1717
+ payload: str | dict,
1718
+ /,
1719
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1487
1720
  *,
1488
1721
  async_: Literal[False] = False,
1489
1722
  **request_kwargs,
1490
1723
  ) -> dict:
1491
1724
  ...
1492
1725
  @overload
1493
- @staticmethod
1494
- def login_qrcode_scan_result(
1495
- uid: str,
1496
- app: str = "alipaymini",
1497
- request: None | Callable = None,
1726
+ def login_qrcode_scan_confirm(
1727
+ self,
1728
+ payload: str | dict,
1729
+ /,
1730
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1498
1731
  *,
1499
1732
  async_: Literal[True],
1500
1733
  **request_kwargs,
1501
1734
  ) -> Coroutine[Any, Any, dict]:
1502
1735
  ...
1503
- @staticmethod
1504
- def login_qrcode_scan_result(
1505
- uid: str,
1506
- app: str = "alipaymini",
1507
- request: None | Callable = None,
1508
- *,
1509
- async_: Literal[False, True] = False,
1510
- **request_kwargs,
1736
+ def login_qrcode_scan_confirm(
1737
+ self,
1738
+ payload: str | dict,
1739
+ /,
1740
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1741
+ *,
1742
+ async_: Literal[False, True] = False,
1743
+ **request_kwargs,
1744
+ ) -> dict | Coroutine[Any, Any, dict]:
1745
+ """确认扫描二维码,payload 数据取自 `login_qrcode_scan` 接口响应
1746
+
1747
+ GET https://qrcodeapi.115.com/api/2.0/slogin.php
1748
+
1749
+ :payload:
1750
+ - key: str
1751
+ - uid: str
1752
+ - client: int = 0
1753
+ """
1754
+ api = complete_api("/api/2.0/slogin.php", base_url=base_url)
1755
+ if isinstance(payload, str):
1756
+ payload = {"key": payload, "uid": payload, "client": 0}
1757
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
1758
+
1759
+ @overload
1760
+ @staticmethod
1761
+ def login_qrcode_scan_result(
1762
+ uid: str,
1763
+ app: str = "alipaymini",
1764
+ request: None | Callable = None,
1765
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1766
+ *,
1767
+ async_: Literal[False] = False,
1768
+ **request_kwargs,
1769
+ ) -> dict:
1770
+ ...
1771
+ @overload
1772
+ @staticmethod
1773
+ def login_qrcode_scan_result(
1774
+ uid: str,
1775
+ app: str = "alipaymini",
1776
+ request: None | Callable = None,
1777
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1778
+ *,
1779
+ async_: Literal[True],
1780
+ **request_kwargs,
1781
+ ) -> Coroutine[Any, Any, dict]:
1782
+ ...
1783
+ @staticmethod
1784
+ def login_qrcode_scan_result(
1785
+ uid: str,
1786
+ app: str = "alipaymini",
1787
+ request: None | Callable = None,
1788
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1789
+ *,
1790
+ async_: Literal[False, True] = False,
1791
+ **request_kwargs,
1511
1792
  ) -> dict | Coroutine[Any, Any, dict]:
1512
1793
  """获取扫码登录的结果,包含 cookie
1513
1794
 
1514
- POST https://passportapi.115.com/app/1.0/{app}/1.0/login/qrcode/
1795
+ POST https://qrcodeapi.115.com/app/1.0/{app}/1.0/login/qrcode/
1515
1796
 
1516
1797
  :param uid: 扫码的 uid
1517
1798
  :param app: 绑定的 app
@@ -1523,7 +1804,7 @@ class ClientRequestMixin:
1523
1804
  """
1524
1805
  if app == "desktop":
1525
1806
  app = "web"
1526
- api = f"http://passportapi.115.com/app/1.0/{app}/1.0/login/qrcode/"
1807
+ api = complete_api(f"/app/1.0/{app}/1.0/login/qrcode/", base_url=base_url)
1527
1808
  payload = {"account": uid}
1528
1809
  request_kwargs.setdefault("parse", default_parse)
1529
1810
  if request is None:
@@ -1537,6 +1818,7 @@ class ClientRequestMixin:
1537
1818
  payload: dict,
1538
1819
  /,
1539
1820
  request: None | Callable = None,
1821
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1540
1822
  *,
1541
1823
  async_: Literal[False] = False,
1542
1824
  **request_kwargs,
@@ -1548,6 +1830,7 @@ class ClientRequestMixin:
1548
1830
  payload: dict,
1549
1831
  /,
1550
1832
  request: None | Callable = None,
1833
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1551
1834
  *,
1552
1835
  async_: Literal[True],
1553
1836
  **request_kwargs,
@@ -1558,6 +1841,7 @@ class ClientRequestMixin:
1558
1841
  payload: dict,
1559
1842
  /,
1560
1843
  request: None | Callable = None,
1844
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1561
1845
  *,
1562
1846
  async_: Literal[False, True] = False,
1563
1847
  **request_kwargs,
@@ -1567,6 +1851,7 @@ class ClientRequestMixin:
1567
1851
  GET https://qrcodeapi.115.com/get/status/
1568
1852
 
1569
1853
  .. admonition:: Reference
1854
+
1570
1855
  https://www.yuque.com/115yun/open/shtpzfhewv5nag11#lAsp2
1571
1856
 
1572
1857
  :payload:
@@ -1574,7 +1859,7 @@ class ClientRequestMixin:
1574
1859
  - time: int
1575
1860
  - sign: str
1576
1861
  """
1577
- api = "https://qrcodeapi.115.com/get/status/"
1862
+ api = complete_api("/get/status/", base_url=base_url)
1578
1863
  request_kwargs.setdefault("parse", default_parse)
1579
1864
  if request is None:
1580
1865
  return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
@@ -1585,6 +1870,8 @@ class ClientRequestMixin:
1585
1870
  @staticmethod
1586
1871
  def login_qrcode_token(
1587
1872
  request: None | Callable = None,
1873
+ app: str = "web",
1874
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1588
1875
  *,
1589
1876
  async_: Literal[False] = False,
1590
1877
  **request_kwargs,
@@ -1594,6 +1881,8 @@ class ClientRequestMixin:
1594
1881
  @staticmethod
1595
1882
  def login_qrcode_token(
1596
1883
  request: None | Callable = None,
1884
+ app: str = "web",
1885
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1597
1886
  *,
1598
1887
  async_: Literal[True],
1599
1888
  **request_kwargs,
@@ -1602,6 +1891,8 @@ class ClientRequestMixin:
1602
1891
  @staticmethod
1603
1892
  def login_qrcode_token(
1604
1893
  request: None | Callable = None,
1894
+ app: str = "web",
1895
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1605
1896
  *,
1606
1897
  async_: Literal[False, True] = False,
1607
1898
  **request_kwargs,
@@ -1610,7 +1901,7 @@ class ClientRequestMixin:
1610
1901
 
1611
1902
  GET https://qrcodeapi.115.com/api/1.0/web/1.0/token/
1612
1903
  """
1613
- api = "https://qrcodeapi.115.com/api/1.0/web/1.0/token/"
1904
+ api = complete_api(f"/api/1.0/{app}/1.0/token/", base_url=base_url)
1614
1905
  request_kwargs.setdefault("parse", default_parse)
1615
1906
  if request is None:
1616
1907
  return get_default_request()(url=api, async_=async_, **request_kwargs)
@@ -1623,6 +1914,7 @@ class ClientRequestMixin:
1623
1914
  payload: int | str | dict,
1624
1915
  /,
1625
1916
  request: None | Callable = None,
1917
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1626
1918
  *,
1627
1919
  async_: Literal[False] = False,
1628
1920
  **request_kwargs,
@@ -1634,6 +1926,7 @@ class ClientRequestMixin:
1634
1926
  payload: int | str | dict,
1635
1927
  /,
1636
1928
  request: None | Callable = None,
1929
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1637
1930
  *,
1638
1931
  async_: Literal[True],
1639
1932
  **request_kwargs,
@@ -1644,15 +1937,17 @@ class ClientRequestMixin:
1644
1937
  payload: int | str | dict,
1645
1938
  /,
1646
1939
  request: None | Callable = None,
1940
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1647
1941
  *,
1648
1942
  async_: Literal[False, True] = False,
1649
1943
  **request_kwargs,
1650
1944
  ) -> dict | Coroutine[Any, Any, dict]:
1651
- """获取开放平台的登录二维码,扫码可用
1945
+ """获取开放平台的登录二维码,扫码可用,采用 PKCE (Proof Key for Code Exchange)
1652
1946
 
1653
1947
  POST https://qrcodeapi.115.com/open/authDeviceCode
1654
1948
 
1655
1949
  .. admonition:: Reference
1950
+
1656
1951
  https://www.yuque.com/115yun/open/shtpzfhewv5nag11#WzRhM
1657
1952
 
1658
1953
  .. note::
@@ -1674,7 +1969,7 @@ class ClientRequestMixin:
1674
1969
 
1675
1970
  - code_challenge_method: str = <default> 💡 计算 `code_challenge` 的 hash 算法,支持 "md5", "sha1", "sha256"
1676
1971
  """
1677
- api = "https://qrcodeapi.115.com/open/authDeviceCode"
1972
+ api = complete_api("/open/authDeviceCode", base_url=base_url)
1678
1973
  if isinstance(payload, (int, str)):
1679
1974
  payload = {
1680
1975
  "client_id": payload,
@@ -1687,6 +1982,62 @@ class ClientRequestMixin:
1687
1982
  else:
1688
1983
  return request(url=api, method="POST", data=payload, **request_kwargs)
1689
1984
 
1985
+ @overload
1986
+ @staticmethod
1987
+ def login_refresh_token_open(
1988
+ payload: str | dict,
1989
+ /,
1990
+ request: None | Callable = None,
1991
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1992
+ *,
1993
+ async_: Literal[False] = False,
1994
+ **request_kwargs,
1995
+ ) -> dict:
1996
+ ...
1997
+ @overload
1998
+ @staticmethod
1999
+ def login_refresh_token_open(
2000
+ payload: str | dict,
2001
+ /,
2002
+ request: None | Callable = None,
2003
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
2004
+ *,
2005
+ async_: Literal[True],
2006
+ **request_kwargs,
2007
+ ) -> Coroutine[Any, Any, dict]:
2008
+ ...
2009
+ @staticmethod
2010
+ def login_refresh_token_open(
2011
+ payload: str | dict,
2012
+ /,
2013
+ request: None | Callable = None,
2014
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
2015
+ *,
2016
+ async_: Literal[False, True] = False,
2017
+ **request_kwargs,
2018
+ ) -> dict | Coroutine[Any, Any, dict]:
2019
+ """用一个 refresh_token 去获取新的 access_token 和 refresh_token,然后原来的 refresh_token 作废
2020
+
2021
+ POST https://qrcodeapi.115.com/open/refreshToken
2022
+
2023
+ .. admonition:: Reference
2024
+
2025
+ https://www.yuque.com/115yun/open/shtpzfhewv5nag11#ve54x
2026
+
2027
+ https://www.yuque.com/115yun/open/opnx8yezo4at2be6
2028
+
2029
+ :payload:
2030
+ - refresh_token: str
2031
+ """
2032
+ api = complete_api("/open/refreshToken", base_url=base_url)
2033
+ if isinstance(payload, str):
2034
+ payload = {"refresh_token": payload}
2035
+ request_kwargs.setdefault("parse", default_parse)
2036
+ if request is None:
2037
+ return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
2038
+ else:
2039
+ return request(url=api, method="POST", data=payload, **request_kwargs)
2040
+
1690
2041
  @overload
1691
2042
  @classmethod
1692
2043
  def login_with_qrcode(
@@ -1694,6 +2045,7 @@ class ClientRequestMixin:
1694
2045
  /,
1695
2046
  app: None | str = "",
1696
2047
  console_qrcode: bool = True,
2048
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1697
2049
  *,
1698
2050
  async_: Literal[False] = False,
1699
2051
  **request_kwargs,
@@ -1706,6 +2058,7 @@ class ClientRequestMixin:
1706
2058
  /,
1707
2059
  app: None | str = "",
1708
2060
  console_qrcode: bool = True,
2061
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1709
2062
  *,
1710
2063
  async_: Literal[True],
1711
2064
  **request_kwargs,
@@ -1717,6 +2070,7 @@ class ClientRequestMixin:
1717
2070
  /,
1718
2071
  app: None | str = "",
1719
2072
  console_qrcode: bool = True,
2073
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1720
2074
  *,
1721
2075
  async_: Literal[False, True] = False,
1722
2076
  **request_kwargs,
@@ -1817,6 +2171,7 @@ class ClientRequestMixin:
1817
2171
  def gen_step():
1818
2172
  resp = yield cls.login_qrcode_token(
1819
2173
  async_=async_,
2174
+ base_url=base_url,
1820
2175
  **request_kwargs,
1821
2176
  )
1822
2177
  qrcode_token = resp["data"]
@@ -1830,15 +2185,16 @@ class ClientRequestMixin:
1830
2185
  qr.add_data(qrcode)
1831
2186
  qr.print_ascii(tty=isatty(1))
1832
2187
  else:
1833
- url = "https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode?uid=" + login_uid
2188
+ url = complete_api(f"/api/1.0/web/1.0/qrcode?uid={login_uid}", base_url=base_url)
1834
2189
  if async_:
1835
- yield partial(startfile_async, url)
2190
+ yield startfile_async(url)
1836
2191
  else:
1837
2192
  startfile(url)
1838
2193
  while True:
1839
2194
  try:
1840
2195
  resp = yield cls.login_qrcode_scan_status(
1841
2196
  qrcode_token,
2197
+ base_url=base_url,
1842
2198
  async_=async_,
1843
2199
  **request_kwargs,
1844
2200
  )
@@ -1862,12 +2218,13 @@ class ClientRequestMixin:
1862
2218
  return cls.login_qrcode_scan_result(
1863
2219
  login_uid,
1864
2220
  app,
2221
+ base_url=base_url,
1865
2222
  async_=async_,
1866
2223
  **request_kwargs,
1867
2224
  )
1868
2225
  else:
1869
2226
  return qrcode_token
1870
- return run_gen_step(gen_step, async_=async_)
2227
+ return run_gen_step(gen_step, simple=True, async_=async_)
1871
2228
 
1872
2229
  @overload
1873
2230
  @classmethod
@@ -1876,6 +2233,7 @@ class ClientRequestMixin:
1876
2233
  /,
1877
2234
  app_id: int | str = 100195993,
1878
2235
  console_qrcode: bool = True,
2236
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1879
2237
  *,
1880
2238
  async_: Literal[False] = False,
1881
2239
  **request_kwargs,
@@ -1888,6 +2246,7 @@ class ClientRequestMixin:
1888
2246
  /,
1889
2247
  app_id: int | str = 100195993,
1890
2248
  console_qrcode: bool = True,
2249
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1891
2250
  *,
1892
2251
  async_: Literal[True],
1893
2252
  **request_kwargs,
@@ -1899,6 +2258,7 @@ class ClientRequestMixin:
1899
2258
  /,
1900
2259
  app_id: int | str = 100195993,
1901
2260
  console_qrcode: bool = True,
2261
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1902
2262
  *,
1903
2263
  async_: Literal[False, True] = False,
1904
2264
  **request_kwargs,
@@ -1914,6 +2274,7 @@ class ClientRequestMixin:
1914
2274
  def gen_step():
1915
2275
  resp = yield cls.login_qrcode_token_open(
1916
2276
  app_id,
2277
+ base_url=base_url,
1917
2278
  async_=async_,
1918
2279
  **request_kwargs,
1919
2280
  )
@@ -1928,15 +2289,16 @@ class ClientRequestMixin:
1928
2289
  qr.add_data(qrcode)
1929
2290
  qr.print_ascii(tty=isatty(1))
1930
2291
  else:
1931
- url = "https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode?uid=" + login_uid
2292
+ url = complete_api(f"/api/1.0/web/1.0/qrcode?uid={login_uid}", base_url=base_url)
1932
2293
  if async_:
1933
- yield partial(startfile_async, url)
2294
+ yield startfile_async(url)
1934
2295
  else:
1935
2296
  startfile(url)
1936
2297
  while True:
1937
2298
  try:
1938
2299
  resp = yield cls.login_qrcode_scan_status(
1939
2300
  qrcode_token,
2301
+ base_url=base_url,
1940
2302
  async_=async_,
1941
2303
  **request_kwargs,
1942
2304
  )
@@ -1958,10 +2320,13 @@ class ClientRequestMixin:
1958
2320
  raise LoginError(errno.EAUTH, f"qrcode: aborted with {resp!r}")
1959
2321
  return cls.login_qrcode_access_token_open(
1960
2322
  login_uid,
2323
+ base_url=base_url,
1961
2324
  async_=async_,
1962
2325
  **request_kwargs,
1963
2326
  )
1964
- return run_gen_step(gen_step, async_=async_)
2327
+ return run_gen_step(gen_step, simple=True, async_=async_)
2328
+
2329
+ ########## Upload API ##########
1965
2330
 
1966
2331
  upload_endpoint = "http://oss-cn-shenzhen.aliyuncs.com"
1967
2332
 
@@ -2342,7 +2707,7 @@ class ClientRequestMixin:
2342
2707
  async_=async_,
2343
2708
  **request_kwargs,
2344
2709
  )
2345
- return run_gen_step(gen_step, async_=async_)
2710
+ return run_gen_step(gen_step, simple=True, async_=async_)
2346
2711
 
2347
2712
  @overload
2348
2713
  def read_bytes_range(
@@ -2457,13 +2822,14 @@ class ClientRequestMixin:
2457
2822
  async_=async_,
2458
2823
  **request_kwargs,
2459
2824
  )
2460
- return run_gen_step(gen_step, async_=async_)
2825
+ return run_gen_step(gen_step, simple=True, async_=async_)
2461
2826
 
2462
2827
 
2463
2828
  class P115OpenClient(ClientRequestMixin):
2464
2829
  """115 的客户端对象
2465
2830
 
2466
2831
  .. admonition:: Reference
2832
+
2467
2833
  https://www.yuque.com/115yun/open
2468
2834
 
2469
2835
  :param app_id_or_refresh_token: 申请到的 AppID 或 refresh_token
@@ -2534,7 +2900,7 @@ class P115OpenClient(ClientRequestMixin):
2534
2900
  app_id_or_refresh_token.startswith("0") or
2535
2901
  app_id_or_refresh_token.strip(digits)
2536
2902
  ):
2537
- resp = yield self.login_qrcode_refresh_token_open(
2903
+ resp = yield self.login_refresh_token_open(
2538
2904
  app_id_or_refresh_token,
2539
2905
  async_=async_,
2540
2906
  **request_kwargs,
@@ -2552,7 +2918,7 @@ class P115OpenClient(ClientRequestMixin):
2552
2918
  self.refresh_token = data["refresh_token"]
2553
2919
  self.access_token = data["access_token"]
2554
2920
  return self
2555
- return run_gen_step(gen_step, async_=async_)
2921
+ return run_gen_step(gen_step, simple=True, async_=async_)
2556
2922
 
2557
2923
  @classmethod
2558
2924
  def from_token(cls, /, access_token: str, refresh_token: str) -> P115OpenClient:
@@ -2609,7 +2975,7 @@ class P115OpenClient(ClientRequestMixin):
2609
2975
  """更新 access_token 和 refresh_token (⚠️ 目前是 7200 秒内就要求刷新一次)
2610
2976
  """
2611
2977
  def gen_step():
2612
- resp = yield self.login_qrcode_refresh_token_open(
2978
+ resp = yield self.login_refresh_token_open(
2613
2979
  self.refresh_token,
2614
2980
  async_=async_,
2615
2981
  **request_kwargs,
@@ -2619,7 +2985,7 @@ class P115OpenClient(ClientRequestMixin):
2619
2985
  self.refresh_token = data["refresh_token"]
2620
2986
  access_token = self.access_token = data["access_token"]
2621
2987
  return access_token
2622
- return run_gen_step(gen_step, async_=async_)
2988
+ return run_gen_step(gen_step, simple=True, async_=async_)
2623
2989
 
2624
2990
  @overload
2625
2991
  def download_url(
@@ -2747,6 +3113,7 @@ class P115OpenClient(ClientRequestMixin):
2747
3113
  相当于 `P115Client.download_url_app(app="chrome")`
2748
3114
 
2749
3115
  .. admonition:: Reference
3116
+
2750
3117
  https://www.yuque.com/115yun/open/um8whr91bxb5997o
2751
3118
 
2752
3119
  :payload:
@@ -2810,6 +3177,7 @@ class P115OpenClient(ClientRequestMixin):
2810
3177
  POST https://proapi.115.com/open/ufile/copy
2811
3178
 
2812
3179
  .. admonition:: Reference
3180
+
2813
3181
  https://www.yuque.com/115yun/open/lvas49ar94n47bbk
2814
3182
 
2815
3183
  :payload:
@@ -2866,6 +3234,7 @@ class P115OpenClient(ClientRequestMixin):
2866
3234
  POST https://proapi.115.com/open/ufile/delete
2867
3235
 
2868
3236
  .. admonition:: Reference
3237
+
2869
3238
  https://www.yuque.com/115yun/open/kt04fu8vcchd2fnb
2870
3239
 
2871
3240
  :payload:
@@ -2909,7 +3278,7 @@ class P115OpenClient(ClientRequestMixin):
2909
3278
  async_: Literal[False, True] = False,
2910
3279
  **request_kwargs,
2911
3280
  ) -> dict | Coroutine[Any, Any, dict]:
2912
- """更新文件或目录
3281
+ """获取目录中的文件列表和基本信息
2913
3282
 
2914
3283
  GET https://proapi.115.com/open/ufile/files
2915
3284
 
@@ -2917,6 +3286,7 @@ class P115OpenClient(ClientRequestMixin):
2917
3286
  相当于 `P115Client.fs_files_app`
2918
3287
 
2919
3288
  .. admonition:: Reference
3289
+
2920
3290
  https://www.yuque.com/115yun/open/kz9ft9a7s57ep868
2921
3291
 
2922
3292
  :payload:
@@ -3036,6 +3406,7 @@ class P115OpenClient(ClientRequestMixin):
3036
3406
  相当于 `P115Client.fs_category_get_app`
3037
3407
 
3038
3408
  .. admonition:: Reference
3409
+
3039
3410
  https://www.yuque.com/115yun/open/rl8zrhe2nag21dfw
3040
3411
 
3041
3412
  :payload:
@@ -3085,6 +3456,7 @@ class P115OpenClient(ClientRequestMixin):
3085
3456
  POST https://proapi.115.com/open/folder/add
3086
3457
 
3087
3458
  .. admonition:: Reference
3459
+
3088
3460
  https://www.yuque.com/115yun/open/qur839kyx9cgxpxi
3089
3461
 
3090
3462
  :payload:
@@ -3137,6 +3509,7 @@ class P115OpenClient(ClientRequestMixin):
3137
3509
  POST https://proapi.115.com/open/ufile/move
3138
3510
 
3139
3511
  .. admonition:: Reference
3512
+
3140
3513
  https://www.yuque.com/115yun/open/vc6fhi2mrkenmav2
3141
3514
 
3142
3515
  :payload:
@@ -3157,9 +3530,663 @@ class P115OpenClient(ClientRequestMixin):
3157
3530
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
3158
3531
 
3159
3532
  @overload
3160
- def fs_search(
3533
+ def fs_search(
3534
+ self,
3535
+ payload: str | dict = ".",
3536
+ /,
3537
+ base_url: bool | str | Callable[[], str] = False,
3538
+ *,
3539
+ async_: Literal[False] = False,
3540
+ **request_kwargs,
3541
+ ) -> dict:
3542
+ ...
3543
+ @overload
3544
+ def fs_search(
3545
+ self,
3546
+ payload: str | dict = ".",
3547
+ /,
3548
+ base_url: bool | str | Callable[[], str] = False,
3549
+ *,
3550
+ async_: Literal[True],
3551
+ **request_kwargs,
3552
+ ) -> Coroutine[Any, Any, dict]:
3553
+ ...
3554
+ def fs_search(
3555
+ self,
3556
+ payload: str | dict = ".",
3557
+ /,
3558
+ base_url: bool | str | Callable[[], str] = False,
3559
+ *,
3560
+ async_: Literal[False, True] = False,
3561
+ **request_kwargs,
3562
+ ) -> dict | Coroutine[Any, Any, dict]:
3563
+ """搜索文件或目录
3564
+
3565
+ GET https://proapi.115.com/open/ufile/search
3566
+
3567
+ .. hint::
3568
+ 相当于 `P115Client.fs_search_app2`
3569
+
3570
+ .. admonition:: Reference
3571
+
3572
+ https://www.yuque.com/115yun/open/ft2yelxzopusus38
3573
+
3574
+ :payload:
3575
+ - aid: int | str = 1 💡 area_id。1:正常文件 7:回收站文件 12:瞬间文件 120:彻底删除文件、简历附件
3576
+ - asc: 0 | 1 = <default> 💡 是否升序排列
3577
+ - cid: int | str = 0 💡 目录 id。cid=-1 时,表示不返回列表任何内容
3578
+ - count_folders: 0 | 1 = <default>
3579
+ - date: str = <default> 💡 筛选日期
3580
+ - fc: 0 | 1 = <default> 💡 只显示文件或目录。1:只显示目录 2:只显示文件
3581
+ - fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
3582
+ - file_label: int | str = <default> 💡 标签 id
3583
+ - format: str = "json" 💡 输出格式(不用管)
3584
+ - gte_day: str 💡 搜索结果匹配的开始时间;格式:YYYY-MM-DD
3585
+ - limit: int = 32 💡 一页大小,意思就是 page_size
3586
+ - lte_day: str 💡 搜索结果匹配的结束时间;格式:YYYY-MM-DD
3587
+ - o: str = <default> 💡 用某字段排序
3588
+
3589
+ - "file_name": 文件名
3590
+ - "file_size": 文件大小
3591
+ - "file_type": 文件种类
3592
+ - "user_utime": 修改时间
3593
+ - "user_ptime": 创建时间
3594
+ - "user_otime": 上一次打开时间
3595
+
3596
+ - offset: int = 0 💡 索引偏移,索引从 0 开始计算
3597
+ - pick_code: str = <default> 💡 是否查询提取码,如果该值为 1 则查询提取码为 `search_value` 的文件
3598
+ - search_value: str = "." 💡 搜索文本,可以是 sha1
3599
+ - show_dir: 0 | 1 = 1
3600
+ - source: str = <default>
3601
+ - star: 0 | 1 = <default>
3602
+ - suffix: str = <default>
3603
+ - type: int = <default> 💡 文件类型
3604
+
3605
+ - 0: 全部(仅当前目录)
3606
+ - 1: 文档
3607
+ - 2: 图片
3608
+ - 3: 音频
3609
+ - 4: 视频
3610
+ - 5: 压缩包
3611
+ - 6: 软件/应用
3612
+ - 7: 书籍
3613
+ - 99: 仅文件
3614
+
3615
+ - version: str = <default> 💡 版本号,比如 3.1
3616
+ """
3617
+ api = complete_proapi("/open/ufile/search", base_url)
3618
+ if isinstance(payload, str):
3619
+ payload = {
3620
+ "aid": 1, "cid": 0, "format": "json", "limit": 32, "offset": 0,
3621
+ "show_dir": 1, "search_value": payload,
3622
+ }
3623
+ else:
3624
+ payload = {
3625
+ "aid": 1, "cid": 0, "format": "json", "limit": 32, "offset": 0,
3626
+ "show_dir": 1, "search_value": ".", **payload,
3627
+ }
3628
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
3629
+
3630
+ @overload
3631
+ def fs_star_set(
3632
+ self,
3633
+ payload: int | str | Iterable[int | str] | dict,
3634
+ /,
3635
+ star: bool = True,
3636
+ base_url: bool | str | Callable[[], str] = False,
3637
+ *,
3638
+ async_: Literal[False] = False,
3639
+ **request_kwargs,
3640
+ ) -> dict:
3641
+ ...
3642
+ @overload
3643
+ def fs_star_set(
3644
+ self,
3645
+ payload: int | str | Iterable[int | str] | dict,
3646
+ /,
3647
+ star: bool = True,
3648
+ base_url: bool | str | Callable[[], str] = False,
3649
+ *,
3650
+ async_: Literal[True],
3651
+ **request_kwargs,
3652
+ ) -> Coroutine[Any, Any, dict]:
3653
+ ...
3654
+ def fs_star_set(
3655
+ self,
3656
+ payload: int | str | Iterable[int | str] | dict,
3657
+ /,
3658
+ star: bool = True,
3659
+ base_url: bool | str | Callable[[], str] = False,
3660
+ *,
3661
+ async_: Literal[False, True] = False,
3662
+ **request_kwargs,
3663
+ ) -> dict | Coroutine[Any, Any, dict]:
3664
+ """为文件或目录设置或取消星标,此接口是对 `fs_update_open` 的封装
3665
+
3666
+ .. note::
3667
+ 即使其中任何一个 id 目前已经被删除,也可以操作成功
3668
+
3669
+ :payload:
3670
+ - file_id: int | str 💡 只能传入 1 个
3671
+ - file_id[0]: int | str 💡 如果有多个,则按顺序给出
3672
+ - file_id[1]: int | str
3673
+ - ...
3674
+ - star: 0 | 1 = 1
3675
+ """
3676
+ api = complete_webapi("/files/star", base_url=base_url)
3677
+ if isinstance(payload, (int, str)):
3678
+ payload = {"file_id": payload, "star": int(star)}
3679
+ elif not isinstance(payload, dict):
3680
+ payload = {f"file_id[{i}]": id for i, id in enumerate(payload)}
3681
+ if not payload:
3682
+ return {"state": False, "message": "no op"}
3683
+ payload["star"] = int(star)
3684
+ else:
3685
+ payload = {"star": int(star), **payload}
3686
+ return self.fs_update(payload, async_=async_, **request_kwargs)
3687
+
3688
+ @overload
3689
+ def fs_video_history(
3690
+ self,
3691
+ payload: str | dict,
3692
+ /,
3693
+ base_url: bool | str | Callable[[], str] = False,
3694
+ *,
3695
+ async_: Literal[False] = False,
3696
+ **request_kwargs,
3697
+ ) -> dict:
3698
+ ...
3699
+ @overload
3700
+ def fs_video_history(
3701
+ self,
3702
+ payload: str | dict,
3703
+ /,
3704
+ base_url: bool | str | Callable[[], str] = False,
3705
+ *,
3706
+ async_: Literal[True],
3707
+ **request_kwargs,
3708
+ ) -> Coroutine[Any, Any, dict]:
3709
+ ...
3710
+ def fs_video_history(
3711
+ self,
3712
+ payload: str | dict,
3713
+ /,
3714
+ base_url: bool | str | Callable[[], str] = False,
3715
+ *,
3716
+ async_: Literal[False, True] = False,
3717
+ **request_kwargs,
3718
+ ) -> dict | Coroutine[Any, Any, dict]:
3719
+ """获取视频播放进度
3720
+
3721
+ GET https://proapi.115.com/open/video/history
3722
+
3723
+ .. admonition:: Reference
3724
+
3725
+ https://www.yuque.com/115yun/open/gssqdrsq6vfqigag
3726
+
3727
+ :payload:
3728
+ - pick_code: str 💡 文件提取码
3729
+ """
3730
+ api = complete_proapi("/open/video/history", base_url)
3731
+ if isinstance(payload, str):
3732
+ payload = {"pick_code": payload}
3733
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
3734
+
3735
+ @overload
3736
+ def fs_video_history_set(
3737
+ self,
3738
+ payload: str | dict,
3739
+ /,
3740
+ base_url: bool | str | Callable[[], str] = False,
3741
+ *,
3742
+ async_: Literal[False] = False,
3743
+ **request_kwargs,
3744
+ ) -> dict:
3745
+ ...
3746
+ @overload
3747
+ def fs_video_history_set(
3748
+ self,
3749
+ payload: str | dict,
3750
+ /,
3751
+ base_url: bool | str | Callable[[], str] = False,
3752
+ *,
3753
+ async_: Literal[True],
3754
+ **request_kwargs,
3755
+ ) -> Coroutine[Any, Any, dict]:
3756
+ ...
3757
+ def fs_video_history_set(
3758
+ self,
3759
+ payload: str | dict,
3760
+ /,
3761
+ base_url: bool | str | Callable[[], str] = False,
3762
+ *,
3763
+ async_: Literal[False, True] = False,
3764
+ **request_kwargs,
3765
+ ) -> dict | Coroutine[Any, Any, dict]:
3766
+ """记忆视频播放进度
3767
+
3768
+ POST https://proapi.115.com/open/video/history
3769
+
3770
+ .. admonition:: Reference
3771
+
3772
+ https://www.yuque.com/115yun/open/bshagbxv1gzqglg4
3773
+
3774
+ :payload:
3775
+ - pick_code: str 💡 文件提取码
3776
+ - time: int = <default> 💡 视频播放进度时长 (单位秒)
3777
+ - watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
3778
+ """
3779
+ api = complete_proapi("/open/video/history", base_url)
3780
+ if isinstance(payload, str):
3781
+ payload = {"pick_code": payload}
3782
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
3783
+
3784
+ @overload
3785
+ def fs_video_play(
3786
+ self,
3787
+ payload: str | dict,
3788
+ /,
3789
+ base_url: bool | str | Callable[[], str] = False,
3790
+ *,
3791
+ async_: Literal[False] = False,
3792
+ **request_kwargs,
3793
+ ) -> dict:
3794
+ ...
3795
+ @overload
3796
+ def fs_video_play(
3797
+ self,
3798
+ payload: str | dict,
3799
+ /,
3800
+ base_url: bool | str | Callable[[], str] = False,
3801
+ *,
3802
+ async_: Literal[True],
3803
+ **request_kwargs,
3804
+ ) -> Coroutine[Any, Any, dict]:
3805
+ ...
3806
+ def fs_video_play(
3807
+ self,
3808
+ payload: str | dict,
3809
+ /,
3810
+ base_url: bool | str | Callable[[], str] = False,
3811
+ *,
3812
+ async_: Literal[False, True] = False,
3813
+ **request_kwargs,
3814
+ ) -> dict | Coroutine[Any, Any, dict]:
3815
+ """获取视频在线播放地址(和视频文件相关数据)
3816
+
3817
+ GET https://proapi.115.com/open/video/play
3818
+
3819
+ .. admonition:: Reference
3820
+
3821
+ https://www.yuque.com/115yun/open/hqglxv3cedi3p9dz
3822
+
3823
+ .. hint::
3824
+ 需切换音轨时,在请求返回的播放地址中增加请求参数 `&audio_track=${index}`,值就是接口响应中 `multitrack_list` 中某个成员的索引,从 0 开始计数
3825
+
3826
+ :payload:
3827
+ - pick_code: str 💡 文件提取码
3828
+ - share_id: int | str = <default> 💡 共享 id,获取共享文件播放地址所需
3829
+ """
3830
+ api = complete_proapi("/open/video/play", base_url)
3831
+ if isinstance(payload, str):
3832
+ payload = {"pick_code": payload}
3833
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
3834
+
3835
+ @overload
3836
+ def fs_video_push(
3837
+ self,
3838
+ payload: str | dict,
3839
+ /,
3840
+ base_url: bool | str | Callable[[], str] = False,
3841
+ *,
3842
+ async_: Literal[False] = False,
3843
+ **request_kwargs,
3844
+ ) -> dict:
3845
+ ...
3846
+ @overload
3847
+ def fs_video_push(
3848
+ self,
3849
+ payload: str | dict,
3850
+ /,
3851
+ base_url: bool | str | Callable[[], str] = False,
3852
+ *,
3853
+ async_: Literal[True],
3854
+ **request_kwargs,
3855
+ ) -> Coroutine[Any, Any, dict]:
3856
+ ...
3857
+ def fs_video_push(
3858
+ self,
3859
+ payload: str | dict,
3860
+ /,
3861
+ base_url: bool | str | Callable[[], str] = False,
3862
+ *,
3863
+ async_: Literal[False, True] = False,
3864
+ **request_kwargs,
3865
+ ) -> dict | Coroutine[Any, Any, dict]:
3866
+ """提交视频转码
3867
+
3868
+ POST https://proapi.115.com/open/video/video_push
3869
+
3870
+ .. admonition:: Reference
3871
+
3872
+ https://www.yuque.com/115yun/open/nxt8r1qcktmg3oan
3873
+
3874
+ :payload:
3875
+ - pick_code: str 💡 文件提取码
3876
+ - op: str = "vip_push" 💡 提交视频加速转码方式:vip_push:根据;vip 等级加速 pay_push:枫叶加速
3877
+ """
3878
+ api = complete_proapi("/open/video/video_push", base_url)
3879
+ if isinstance(payload, str):
3880
+ payload = {"pick_code": payload, "op": "vip_push"}
3881
+ else:
3882
+ payload.setdefault("op", "vip_push")
3883
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
3884
+
3885
+ @overload
3886
+ def fs_video_subtitle(
3887
+ self,
3888
+ payload: str | dict,
3889
+ /,
3890
+ base_url: bool | str | Callable[[], str] = False,
3891
+ *,
3892
+ async_: Literal[False] = False,
3893
+ **request_kwargs,
3894
+ ) -> dict:
3895
+ ...
3896
+ @overload
3897
+ def fs_video_subtitle(
3898
+ self,
3899
+ payload: str | dict,
3900
+ /,
3901
+ base_url: bool | str | Callable[[], str] = False,
3902
+ *,
3903
+ async_: Literal[True],
3904
+ **request_kwargs,
3905
+ ) -> Coroutine[Any, Any, dict]:
3906
+ ...
3907
+ def fs_video_subtitle(
3908
+ self,
3909
+ payload: str | dict,
3910
+ /,
3911
+ base_url: bool | str | Callable[[], str] = False,
3912
+ *,
3913
+ async_: Literal[False, True] = False,
3914
+ **request_kwargs,
3915
+ ) -> dict | Coroutine[Any, Any, dict]:
3916
+ """视频字幕列表
3917
+
3918
+ GET https://proapi.115.com/open/video/subtitle
3919
+
3920
+ .. admonition:: Reference
3921
+
3922
+ https://www.yuque.com/115yun/open/nx076h3glapoyh7u
3923
+
3924
+ :payload:
3925
+ - pick_code: str 💡 文件提取码
3926
+ """
3927
+ api = complete_proapi("/open/video/subtitle", base_url)
3928
+ if isinstance(payload, str):
3929
+ payload = {"pick_code": payload}
3930
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
3931
+
3932
+ @overload
3933
+ def fs_update(
3934
+ self,
3935
+ payload: dict,
3936
+ /,
3937
+ base_url: bool | str | Callable[[], str] = False,
3938
+ *,
3939
+ async_: Literal[False] = False,
3940
+ **request_kwargs,
3941
+ ) -> dict:
3942
+ ...
3943
+ @overload
3944
+ def fs_update(
3945
+ self,
3946
+ payload: dict,
3947
+ /,
3948
+ base_url: bool | str | Callable[[], str] = False,
3949
+ *,
3950
+ async_: Literal[True],
3951
+ **request_kwargs,
3952
+ ) -> Coroutine[Any, Any, dict]:
3953
+ ...
3954
+ def fs_update(
3955
+ self,
3956
+ payload: dict,
3957
+ /,
3958
+ base_url: bool | str | Callable[[], str] = False,
3959
+ *,
3960
+ async_: Literal[False, True] = False,
3961
+ **request_kwargs,
3962
+ ) -> dict | Coroutine[Any, Any, dict]:
3963
+ """设置文件或目录(备注、标签等)
3964
+
3965
+ POST https://proapi.115.com/open/ufile/update
3966
+
3967
+ .. hint::
3968
+ 即使文件已经被删除,也可以操作成功
3969
+
3970
+ .. admonition:: Reference
3971
+
3972
+ https://www.yuque.com/115yun/open/gyrpw5a0zc4sengm
3973
+
3974
+ :payload:
3975
+ - file_id: int | str 💡 只能传入 1 个
3976
+ - file_id[0]: int | str 💡 如果有多个,则按顺序给出
3977
+ - file_id[1]: int | str
3978
+ - ...
3979
+ - file_name: str = <default> 💡 文件名
3980
+ - star: 0 | 1 = <default> 💡 是否星标:0:取消星标 1:设置星标
3981
+ - ...
3982
+ """
3983
+ api = complete_proapi("/open/ufile/update", base_url)
3984
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
3985
+
3986
+ @overload
3987
+ def offline_add_torrent(
3988
+ self,
3989
+ payload: dict,
3990
+ /,
3991
+ base_url: bool | str | Callable[[], str] = False,
3992
+ *,
3993
+ async_: Literal[False] = False,
3994
+ **request_kwargs,
3995
+ ) -> dict:
3996
+ ...
3997
+ @overload
3998
+ def offline_add_torrent(
3999
+ self,
4000
+ payload: dict,
4001
+ /,
4002
+ base_url: bool | str | Callable[[], str] = False,
4003
+ *,
4004
+ async_: Literal[True],
4005
+ **request_kwargs,
4006
+ ) -> Coroutine[Any, Any, dict]:
4007
+ ...
4008
+ def offline_add_torrent(
4009
+ self,
4010
+ payload: dict,
4011
+ /,
4012
+ base_url: bool | str | Callable[[], str] = False,
4013
+ *,
4014
+ async_: Literal[False, True] = False,
4015
+ **request_kwargs,
4016
+ ) -> dict | Coroutine[Any, Any, dict]:
4017
+ """添加云下载 BT 任务
4018
+
4019
+ POST https://proapi.115.com/open/offline/add_task_bt
4020
+
4021
+ .. admonition:: Reference
4022
+
4023
+ https://www.yuque.com/115yun/open/svfe4unlhayvluly
4024
+
4025
+ :payload:
4026
+ - info_hash: str 💡 种子文件的 info_hash
4027
+ - pick_code: str 💡 种子文件的提取码
4028
+ - save_path: str 💡 保存到 `wp_path_id` 对应目录下的相对路径
4029
+ - torrent_sha1: str 💡 种子文件的 sha1
4030
+ - wanted: str 💡 选择文件进行下载(是数字索引,从 0 开始计数,用 "," 分隔)
4031
+ - wp_path_id: int | str = <default> 💡 保存目标文件夹 id
4032
+ """
4033
+ api = complete_proapi("/open/offline/add_task_bt ", base_url)
4034
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
4035
+
4036
+ @overload
4037
+ def offline_add_urls(
4038
+ self,
4039
+ payload: str | Iterable[str] | dict,
4040
+ /,
4041
+ base_url: bool | str | Callable[[], str] = False,
4042
+ *,
4043
+ async_: Literal[False] = False,
4044
+ **request_kwargs,
4045
+ ) -> dict:
4046
+ ...
4047
+ @overload
4048
+ def offline_add_urls(
4049
+ self,
4050
+ payload: str | Iterable[str] | dict,
4051
+ /,
4052
+ base_url: bool | str | Callable[[], str] = False,
4053
+ *,
4054
+ async_: Literal[True],
4055
+ **request_kwargs,
4056
+ ) -> Coroutine[Any, Any, dict]:
4057
+ ...
4058
+ def offline_add_urls(
4059
+ self,
4060
+ payload: str | Iterable[str] | dict,
4061
+ /,
4062
+ base_url: bool | str | Callable[[], str] = False,
4063
+ *,
4064
+ async_: Literal[False, True] = False,
4065
+ **request_kwargs,
4066
+ ) -> dict | Coroutine[Any, Any, dict]:
4067
+ """添加云下载链接任务
4068
+
4069
+ POST https://proapi.115.com/open/offline/add_task_urls
4070
+
4071
+ .. admonition:: Reference
4072
+
4073
+ https://www.yuque.com/115yun/open/zkyfq2499gdn3mty
4074
+
4075
+ :payload:
4076
+ - urls: str 💡 链接,用 "\\n" 分隔,支持HTTP、HTTPS、FTP、磁力链和电驴链接
4077
+ - wp_path_id: int | str = <default> 💡 保存到目录的 id
4078
+ """
4079
+ api = complete_proapi("/open/offline/add_task_urls", base_url)
4080
+ if isinstance(payload, str):
4081
+ payload = {"urls": payload.strip("\n")}
4082
+ elif not isinstance(payload, dict):
4083
+ payload = {"urls": ",".join(payload)}
4084
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
4085
+
4086
+ @overload
4087
+ def offline_clear(
4088
+ self,
4089
+ payload: int | dict = 0,
4090
+ /,
4091
+ base_url: bool | str | Callable[[], str] = False,
4092
+ *,
4093
+ async_: Literal[False] = False,
4094
+ **request_kwargs,
4095
+ ) -> dict:
4096
+ ...
4097
+ @overload
4098
+ def offline_clear(
4099
+ self,
4100
+ payload: int | dict = 0,
4101
+ /,
4102
+ base_url: bool | str | Callable[[], str] = False,
4103
+ *,
4104
+ async_: Literal[True],
4105
+ **request_kwargs,
4106
+ ) -> Coroutine[Any, Any, dict]:
4107
+ ...
4108
+ def offline_clear(
4109
+ self,
4110
+ payload: int | dict = 0,
4111
+ /,
4112
+ base_url: bool | str | Callable[[], str] = False,
4113
+ *,
4114
+ async_: Literal[False, True] = False,
4115
+ **request_kwargs,
4116
+ ) -> dict | Coroutine[Any, Any, dict]:
4117
+ """清空云下载任务
4118
+
4119
+ POST https://proapi.115.com/open/offline/clear_task
4120
+
4121
+ .. admonition:: Reference
4122
+
4123
+ https://www.yuque.com/115yun/open/uu5i4urb5ylqwfy4
4124
+
4125
+ :payload:
4126
+ - flag: int = 0 💡 标识,用于对应某种情况
4127
+
4128
+ - 0: 已完成
4129
+ - 1: 全部
4130
+ - 2: 已失败
4131
+ - 3: 进行中
4132
+ - 4: 已完成+删除源文件
4133
+ - 5: 全部+删除源文件
4134
+ """
4135
+ api = complete_proapi("/open/offline/clear_task", base_url)
4136
+ if isinstance(payload, int):
4137
+ payload = {"flag": payload}
4138
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
4139
+
4140
+ @overload
4141
+ def offline_list(
4142
+ self,
4143
+ payload: int | dict = 1,
4144
+ /,
4145
+ base_url: bool | str | Callable[[], str] = False,
4146
+ *,
4147
+ async_: Literal[False] = False,
4148
+ **request_kwargs,
4149
+ ) -> dict:
4150
+ ...
4151
+ @overload
4152
+ def offline_list(
4153
+ self,
4154
+ payload: int | dict = 1,
4155
+ /,
4156
+ base_url: bool | str | Callable[[], str] = False,
4157
+ *,
4158
+ async_: Literal[True],
4159
+ **request_kwargs,
4160
+ ) -> Coroutine[Any, Any, dict]:
4161
+ ...
4162
+ def offline_list(
4163
+ self,
4164
+ payload: int | dict = 1,
4165
+ /,
4166
+ base_url: bool | str | Callable[[], str] = False,
4167
+ *,
4168
+ async_: Literal[False, True] = False,
4169
+ **request_kwargs,
4170
+ ) -> dict | Coroutine[Any, Any, dict]:
4171
+ """获取用户云下载任务列表
4172
+
4173
+ GET https://proapi.115.com/open/offline/get_task_list
4174
+
4175
+ .. admonition:: Reference
4176
+
4177
+ https://www.yuque.com/115yun/open/av2mluz7uwigz74k
4178
+
4179
+ :payload:
4180
+ - page: int | str = 1
4181
+ """
4182
+ api = complete_proapi("/open/offline/get_task_list", base_url)
4183
+ if isinstance(payload, int):
4184
+ payload = {"page": payload}
4185
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
4186
+
4187
+ @overload
4188
+ def offline_quota_info(
3161
4189
  self,
3162
- payload: str | dict = ".",
3163
4190
  /,
3164
4191
  base_url: bool | str | Callable[[], str] = False,
3165
4192
  *,
@@ -3168,9 +4195,8 @@ class P115OpenClient(ClientRequestMixin):
3168
4195
  ) -> dict:
3169
4196
  ...
3170
4197
  @overload
3171
- def fs_search(
4198
+ def offline_quota_info(
3172
4199
  self,
3173
- payload: str | dict = ".",
3174
4200
  /,
3175
4201
  base_url: bool | str | Callable[[], str] = False,
3176
4202
  *,
@@ -3178,87 +4204,30 @@ class P115OpenClient(ClientRequestMixin):
3178
4204
  **request_kwargs,
3179
4205
  ) -> Coroutine[Any, Any, dict]:
3180
4206
  ...
3181
- def fs_search(
4207
+ def offline_quota_info(
3182
4208
  self,
3183
- payload: str | dict = ".",
3184
4209
  /,
3185
4210
  base_url: bool | str | Callable[[], str] = False,
3186
4211
  *,
3187
4212
  async_: Literal[False, True] = False,
3188
4213
  **request_kwargs,
3189
4214
  ) -> dict | Coroutine[Any, Any, dict]:
3190
- """搜索文件或目录
3191
-
3192
- GET https://proapi.115.com/open/ufile/search
4215
+ """获取云下载配额信息
3193
4216
 
3194
- .. hint::
3195
- 相当于 `P115Client.fs_search_app2`
4217
+ GET https://proapi.115.com/open/offline/get_quota_info
3196
4218
 
3197
4219
  .. admonition:: Reference
3198
- https://www.yuque.com/115yun/open/ft2yelxzopusus38
3199
-
3200
- :payload:
3201
- - aid: int | str = 1 💡 area_id。1:正常文件 7:回收站文件 12:瞬间文件 120:彻底删除文件、简历附件
3202
- - asc: 0 | 1 = <default> 💡 是否升序排列
3203
- - cid: int | str = 0 💡 目录 id。cid=-1 时,表示不返回列表任何内容
3204
- - count_folders: 0 | 1 = <default>
3205
- - date: str = <default> 💡 筛选日期
3206
- - fc: 0 | 1 = <default> 💡 只显示文件或目录。1:只显示目录 2:只显示文件
3207
- - fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
3208
- - file_label: int | str = <default> 💡 标签 id
3209
- - format: str = "json" 💡 输出格式(不用管)
3210
- - gte_day: str 💡 搜索结果匹配的开始时间;格式:YYYY-MM-DD
3211
- - limit: int = 32 💡 一页大小,意思就是 page_size
3212
- - lte_day: str 💡 搜索结果匹配的结束时间;格式:YYYY-MM-DD
3213
- - o: str = <default> 💡 用某字段排序
3214
-
3215
- - "file_name": 文件名
3216
- - "file_size": 文件大小
3217
- - "file_type": 文件种类
3218
- - "user_utime": 修改时间
3219
- - "user_ptime": 创建时间
3220
- - "user_otime": 上一次打开时间
3221
-
3222
- - offset: int = 0 💡 索引偏移,索引从 0 开始计算
3223
- - pick_code: str = <default> 💡 是否查询提取码,如果该值为 1 则查询提取码为 `search_value` 的文件
3224
- - search_value: str = "." 💡 搜索文本,可以是 sha1
3225
- - show_dir: 0 | 1 = 1
3226
- - source: str = <default>
3227
- - star: 0 | 1 = <default>
3228
- - suffix: str = <default>
3229
- - type: int = <default> 💡 文件类型
3230
-
3231
- - 0: 全部(仅当前目录)
3232
- - 1: 文档
3233
- - 2: 图片
3234
- - 3: 音频
3235
- - 4: 视频
3236
- - 5: 压缩包
3237
- - 6: 软件/应用
3238
- - 7: 书籍
3239
- - 99: 仅文件
3240
4220
 
3241
- - version: str = <default> 💡 版本号,比如 3.1
4221
+ https://www.yuque.com/115yun/open/gif2n3smh54kyg0p
3242
4222
  """
3243
- api = complete_proapi("/open/ufile/search", base_url)
3244
- if isinstance(payload, str):
3245
- payload = {
3246
- "aid": 1, "cid": 0, "format": "json", "limit": 32, "offset": 0,
3247
- "show_dir": 1, "search_value": payload,
3248
- }
3249
- else:
3250
- payload = {
3251
- "aid": 1, "cid": 0, "format": "json", "limit": 32, "offset": 0,
3252
- "show_dir": 1, "search_value": ".", **payload,
3253
- }
3254
- return self.request(url=api, params=payload, async_=async_, **request_kwargs)
4223
+ api = complete_proapi("/open/offline/get_quota_info", base_url)
4224
+ return self.request(url=api, async_=async_, **request_kwargs)
3255
4225
 
3256
4226
  @overload
3257
- def fs_star_set(
4227
+ def offline_remove(
3258
4228
  self,
3259
- payload: int | str | Iterable[int | str] | dict,
4229
+ payload: str | dict,
3260
4230
  /,
3261
- star: bool = True,
3262
4231
  base_url: bool | str | Callable[[], str] = False,
3263
4232
  *,
3264
4233
  async_: Literal[False] = False,
@@ -3266,53 +4235,44 @@ class P115OpenClient(ClientRequestMixin):
3266
4235
  ) -> dict:
3267
4236
  ...
3268
4237
  @overload
3269
- def fs_star_set(
4238
+ def offline_remove(
3270
4239
  self,
3271
- payload: int | str | Iterable[int | str] | dict,
4240
+ payload: str | dict,
3272
4241
  /,
3273
- star: bool = True,
3274
4242
  base_url: bool | str | Callable[[], str] = False,
3275
4243
  *,
3276
4244
  async_: Literal[True],
3277
4245
  **request_kwargs,
3278
4246
  ) -> Coroutine[Any, Any, dict]:
3279
4247
  ...
3280
- def fs_star_set(
4248
+ def offline_remove(
3281
4249
  self,
3282
- payload: int | str | Iterable[int | str] | dict,
4250
+ payload: str | dict,
3283
4251
  /,
3284
- star: bool = True,
3285
4252
  base_url: bool | str | Callable[[], str] = False,
3286
4253
  *,
3287
4254
  async_: Literal[False, True] = False,
3288
4255
  **request_kwargs,
3289
4256
  ) -> dict | Coroutine[Any, Any, dict]:
3290
- """为文件或目录设置或取消星标,此接口是对 `fs_update_open` 的封装
4257
+ """删除用户云下载任务
3291
4258
 
3292
- .. note::
3293
- 即使其中任何一个 id 目前已经被删除,也可以操作成功
4259
+ POST https://proapi.115.com/open/offline/del_task
4260
+
4261
+ .. admonition:: Reference
4262
+
4263
+ https://www.yuque.com/115yun/open/pmgwc86lpcy238nw
3294
4264
 
3295
4265
  :payload:
3296
- - file_id: int | str 💡 只能传入 1 个
3297
- - file_id[0]: int | str 💡 如果有多个,则按顺序给出
3298
- - file_id[1]: int | str
3299
- - ...
3300
- - star: 0 | 1 = 1
4266
+ - info_hash: str 💡 待删除任务的 info_hash
4267
+ - del_source_file: 0 | 1 = <default> 💡 是否删除源文件 1:删除 0:不删除
3301
4268
  """
3302
- api = complete_webapi("/files/star", base_url=base_url)
3303
- if isinstance(payload, (int, str)):
3304
- payload = {"file_id": payload, "star": int(star)}
3305
- elif not isinstance(payload, dict):
3306
- payload = {f"file_id[{i}]": id for i, id in enumerate(payload)}
3307
- if not payload:
3308
- return {"state": False, "message": "no op"}
3309
- payload["star"] = int(star)
3310
- else:
3311
- payload = {"star": int(star), **payload}
3312
- return self.fs_update(payload, async_=async_, **request_kwargs)
4269
+ api = complete_proapi("/open/offline/del_task", base_url)
4270
+ if isinstance(payload, str):
4271
+ payload = {"info_hash": payload}
4272
+ return self.request(api, method="POST", data=payload, async_=async_, **request_kwargs)
3313
4273
 
3314
4274
  @overload
3315
- def fs_update(
4275
+ def offline_torrent_info(
3316
4276
  self,
3317
4277
  payload: dict,
3318
4278
  /,
@@ -3323,7 +4283,7 @@ class P115OpenClient(ClientRequestMixin):
3323
4283
  ) -> dict:
3324
4284
  ...
3325
4285
  @overload
3326
- def fs_update(
4286
+ def offline_torrent_info(
3327
4287
  self,
3328
4288
  payload: dict,
3329
4289
  /,
@@ -3333,7 +4293,7 @@ class P115OpenClient(ClientRequestMixin):
3333
4293
  **request_kwargs,
3334
4294
  ) -> Coroutine[Any, Any, dict]:
3335
4295
  ...
3336
- def fs_update(
4296
+ def offline_torrent_info(
3337
4297
  self,
3338
4298
  payload: dict,
3339
4299
  /,
@@ -3342,26 +4302,19 @@ class P115OpenClient(ClientRequestMixin):
3342
4302
  async_: Literal[False, True] = False,
3343
4303
  **request_kwargs,
3344
4304
  ) -> dict | Coroutine[Any, Any, dict]:
3345
- """设置文件或目录(备注、标签等)
3346
-
3347
- POST https://proapi.115.com/open/ufile/update
4305
+ """解析 BT 种子
3348
4306
 
3349
- .. hint::
3350
- 即使文件已经被删除,也可以操作成功
4307
+ POST https://proapi.115.com/open/offline/torrent
3351
4308
 
3352
4309
  .. admonition:: Reference
3353
- https://www.yuque.com/115yun/open/gyrpw5a0zc4sengm
4310
+
4311
+ https://www.yuque.com/115yun/open/evez3u50cemoict1
3354
4312
 
3355
4313
  :payload:
3356
- - file_id: int | str 💡 只能传入 1 个
3357
- - file_id[0]: int | str 💡 如果有多个,则按顺序给出
3358
- - file_id[1]: int | str
3359
- - ...
3360
- - file_name: str = <default> 💡 文件名
3361
- - star: 0 | 1 = <default> 💡 是否星标:0:取消星标 1:设置星标
3362
- - ...
4314
+ - torrent_sha1: str 💡 种子文件的 sha1
4315
+ - pick_code: str 💡 种子文件的提取码
3363
4316
  """
3364
- api = complete_proapi("/open/ufile/update", base_url)
4317
+ api = complete_proapi("/open/offline/torrent", base_url)
3365
4318
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
3366
4319
 
3367
4320
  @overload
@@ -3399,7 +4352,8 @@ class P115OpenClient(ClientRequestMixin):
3399
4352
 
3400
4353
  POST https://proapi.115.com/open/rb/del
3401
4354
 
3402
- .. note:
4355
+ .. admonition:: Reference
4356
+
3403
4357
  https://www.yuque.com/115yun/open/gwtof85nmboulrce
3404
4358
 
3405
4359
  :payload:
@@ -3448,6 +4402,7 @@ class P115OpenClient(ClientRequestMixin):
3448
4402
  GET https://proapi.115.com/open/rb/list
3449
4403
 
3450
4404
  .. admonition:: Reference
4405
+
3451
4406
  https://www.yuque.com/115yun/open/bg7l4328t98fwgex
3452
4407
 
3453
4408
  :payload:
@@ -3497,6 +4452,7 @@ class P115OpenClient(ClientRequestMixin):
3497
4452
  POST https://proapi.115.com/open/rb/revert
3498
4453
 
3499
4454
  .. admonition:: Reference
4455
+
3500
4456
  https://www.yuque.com/115yun/open/gq293z80a3kmxbaq
3501
4457
 
3502
4458
  :payload:
@@ -3542,6 +4498,7 @@ class P115OpenClient(ClientRequestMixin):
3542
4498
  GET https://proapi.115.com/open/upload/get_token
3543
4499
 
3544
4500
  .. admonition:: Reference
4501
+
3545
4502
  https://www.yuque.com/115yun/open/kzacvzl0g7aiyyn4
3546
4503
  """
3547
4504
  api = complete_proapi("/open/upload/get_token", base_url)
@@ -3583,6 +4540,7 @@ class P115OpenClient(ClientRequestMixin):
3583
4540
  POST https://proapi.115.com/open/upload/init
3584
4541
 
3585
4542
  .. admonition:: Reference
4543
+
3586
4544
  https://www.yuque.com/115yun/open/ul4mrauo5i2uza0q
3587
4545
 
3588
4546
  :payload:
@@ -3641,6 +4599,7 @@ class P115OpenClient(ClientRequestMixin):
3641
4599
  POST https://proapi.115.com/open/upload/resume
3642
4600
 
3643
4601
  .. admonition:: Reference
4602
+
3644
4603
  https://www.yuque.com/115yun/open/tzvi9sbcg59msddz
3645
4604
 
3646
4605
  :payload:
@@ -3752,7 +4711,7 @@ class P115OpenClient(ClientRequestMixin):
3752
4711
  check_response(resp)
3753
4712
  resp["data"] = {**payload, **resp["data"], "sha1": filesha1, "cid": pid}
3754
4713
  return resp
3755
- return run_gen_step(gen_step, async_=async_)
4714
+ return run_gen_step(gen_step, simple=True, async_=async_)
3756
4715
 
3757
4716
  @overload
3758
4717
  def upload_file(
@@ -4096,7 +5055,7 @@ class P115OpenClient(ClientRequestMixin):
4096
5055
  async_=async_, # type: ignore
4097
5056
  **request_kwargs,
4098
5057
  )
4099
- return run_gen_step(gen_step, async_=async_)
5058
+ return run_gen_step(gen_step, simple=True, async_=async_)
4100
5059
 
4101
5060
  @overload
4102
5061
  def user_info(
@@ -4131,6 +5090,7 @@ class P115OpenClient(ClientRequestMixin):
4131
5090
  GET https://proapi.115.com/open/user/info
4132
5091
 
4133
5092
  .. admonition:: Reference
5093
+
4134
5094
  https://www.yuque.com/115yun/open/ot1litggzxa1czww
4135
5095
  """
4136
5096
  api = complete_proapi("/open/user/info", base_url)
@@ -4146,7 +5106,19 @@ class P115OpenClient(ClientRequestMixin):
4146
5106
  fs_move_open = fs_move
4147
5107
  fs_search_open = fs_search
4148
5108
  fs_star_set_open = fs_star_set
5109
+ fs_video_history_open = fs_video_history
5110
+ fs_video_history_set_open = fs_video_history_set
5111
+ fs_video_play_open = fs_video_play
5112
+ fs_video_push_open = fs_video_push
5113
+ fs_video_subtitle_open = fs_video_subtitle
4149
5114
  fs_update_open = fs_update
5115
+ offline_add_torrent_open = offline_add_torrent
5116
+ offline_add_urls_open = offline_add_urls
5117
+ offline_clear_open = offline_clear
5118
+ offline_list_open = offline_list
5119
+ offline_quota_info_open = offline_quota_info
5120
+ offline_remove_open = offline_remove
5121
+ offline_torrent_info_open = offline_torrent_info
4150
5122
  recyclebin_clean_open = recyclebin_clean
4151
5123
  recyclebin_list_open = recyclebin_list
4152
5124
  recyclebin_revert_open = recyclebin_revert
@@ -4464,7 +5436,7 @@ class P115Client(P115OpenClient):
4464
5436
  )
4465
5437
  setattr(self, "check_for_relogin", check_for_relogin)
4466
5438
  return self
4467
- return run_gen_step(gen_step, async_=async_)
5439
+ return run_gen_step(gen_step, simple=True, async_=async_)
4468
5440
 
4469
5441
  @locked_cacheproperty
4470
5442
  def request_lock(self, /) -> Lock:
@@ -4601,118 +5573,42 @@ class P115Client(P115OpenClient):
4601
5573
  | 20 | P2 | mac | 115生活(macOS端) |
4602
5574
  +-------+----------+------------+-------------------------+
4603
5575
  | 21 | P3 | linux | 115生活(Linux端) |
4604
- +-------+----------+------------+-------------------------+
4605
- | 22 | R1 | wechatmini | 115生活(微信小程序) |
4606
- +-------+----------+------------+-------------------------+
4607
- | 23 | R2 | alipaymini | 115生活(支付宝小程序) |
4608
- +-------+----------+------------+-------------------------+
4609
- | 24 | S1 | harmony | 115(Harmony端) |
4610
- +-------+----------+------------+-------------------------+
4611
- """
4612
- def gen_step():
4613
- nonlocal app
4614
- status = yield self.login_status(async_=async_, **request_kwargs)
4615
- if status:
4616
- return self
4617
- if not app:
4618
- app = yield self.login_app(async_=async_, **request_kwargs)
4619
- if not app:
4620
- app = "alipaymini"
4621
- resp = yield self.login_with_qrcode(
4622
- app,
4623
- console_qrcode=console_qrcode,
4624
- async_=async_,
4625
- **request_kwargs,
4626
- )
4627
- try:
4628
- check_response(resp)
4629
- except AuthenticationError:
4630
- resp = yield self.login_with_qrcode(
4631
- app,
4632
- console_qrcode=console_qrcode,
4633
- async_=async_,
4634
- **request_kwargs,
4635
- )
4636
- check_response(resp)
4637
- setattr(self, "cookies", resp["data"]["cookie"])
4638
- return self
4639
- return run_gen_step(gen_step, async_=async_)
4640
-
4641
- @overload
4642
- def login_qrcode_scan(
4643
- self,
4644
- payload: str | dict,
4645
- /,
4646
- async_: Literal[False] = False,
4647
- **request_kwargs,
4648
- ) -> dict:
4649
- ...
4650
- @overload
4651
- def login_qrcode_scan(
4652
- self,
4653
- payload: str | dict,
4654
- /,
4655
- async_: Literal[True],
4656
- **request_kwargs,
4657
- ) -> Coroutine[Any, Any, dict]:
4658
- ...
4659
- def login_qrcode_scan(
4660
- self,
4661
- payload: str | dict,
4662
- /,
4663
- async_: Literal[False, True] = False,
4664
- **request_kwargs,
4665
- ) -> dict | Coroutine[Any, Any, dict]:
4666
- """扫描二维码,payload 数据取自 `login_qrcode_token` 接口响应
4667
-
4668
- GET https://qrcodeapi.115.com/api/2.0/prompt.php
4669
-
4670
- :payload:
4671
- - uid: str
4672
- """
4673
- api = "https://qrcodeapi.115.com/api/2.0/prompt.php"
4674
- if isinstance(payload, str):
4675
- payload = {"uid": payload}
4676
- return self.request(url=api, params=payload, async_=async_, **request_kwargs)
4677
-
4678
- @overload
4679
- def login_qrcode_scan_confirm(
4680
- self,
4681
- payload: str | dict,
4682
- /,
4683
- async_: Literal[False] = False,
4684
- **request_kwargs,
4685
- ) -> dict:
4686
- ...
4687
- @overload
4688
- def login_qrcode_scan_confirm(
4689
- self,
4690
- payload: str | dict,
4691
- /,
4692
- async_: Literal[True],
4693
- **request_kwargs,
4694
- ) -> Coroutine[Any, Any, dict]:
4695
- ...
4696
- def login_qrcode_scan_confirm(
4697
- self,
4698
- payload: str | dict,
4699
- /,
4700
- async_: Literal[False, True] = False,
4701
- **request_kwargs,
4702
- ) -> dict | Coroutine[Any, Any, dict]:
4703
- """确认扫描二维码,payload 数据取自 `login_qrcode_scan` 接口响应
4704
-
4705
- GET https://hnqrcodeapi.115.com/api/2.0/slogin.php
4706
-
4707
- :payload:
4708
- - key: str
4709
- - uid: str
4710
- - client: int = 0
5576
+ +-------+----------+------------+-------------------------+
5577
+ | 22 | R1 | wechatmini | 115生活(微信小程序) |
5578
+ +-------+----------+------------+-------------------------+
5579
+ | 23 | R2 | alipaymini | 115生活(支付宝小程序) |
5580
+ +-------+----------+------------+-------------------------+
5581
+ | 24 | S1 | harmony | 115(Harmony端) |
5582
+ +-------+----------+------------+-------------------------+
4711
5583
  """
4712
- api = "https://hnqrcodeapi.115.com/api/2.0/slogin.php"
4713
- if isinstance(payload, str):
4714
- payload = {"key": payload, "uid": payload, "client": 0}
4715
- return self.request(url=api, params=payload, async_=async_, **request_kwargs)
5584
+ def gen_step():
5585
+ nonlocal app
5586
+ status = yield self.login_status(async_=async_, **request_kwargs)
5587
+ if status:
5588
+ return self
5589
+ if not app:
5590
+ app = yield self.login_app(async_=async_, **request_kwargs)
5591
+ if not app:
5592
+ app = "alipaymini"
5593
+ resp = yield self.login_with_qrcode(
5594
+ app,
5595
+ console_qrcode=console_qrcode,
5596
+ async_=async_,
5597
+ **request_kwargs,
5598
+ )
5599
+ try:
5600
+ check_response(resp)
5601
+ except AuthenticationError:
5602
+ resp = yield self.login_with_qrcode(
5603
+ app,
5604
+ console_qrcode=console_qrcode,
5605
+ async_=async_,
5606
+ **request_kwargs,
5607
+ )
5608
+ check_response(resp)
5609
+ setattr(self, "cookies", resp["data"]["cookie"])
5610
+ return self
5611
+ return run_gen_step(gen_step, simple=True, async_=async_)
4716
5612
 
4717
5613
  @overload
4718
5614
  def login_with_app(
@@ -4844,7 +5740,7 @@ class P115Client(P115OpenClient):
4844
5740
  async_=async_,
4845
5741
  **request_kwargs,
4846
5742
  )
4847
- return run_gen_step(gen_step, async_=async_)
5743
+ return run_gen_step(gen_step, simple=True, async_=async_)
4848
5744
 
4849
5745
  @overload
4850
5746
  def login_without_app(
@@ -4902,7 +5798,7 @@ class P115Client(P115OpenClient):
4902
5798
  )
4903
5799
  check_response(resp)
4904
5800
  return uid
4905
- return run_gen_step(gen_step, async_=async_)
5801
+ return run_gen_step(gen_step, simple=True, async_=async_)
4906
5802
 
4907
5803
  @overload
4908
5804
  def login_with_open(
@@ -4954,7 +5850,7 @@ class P115Client(P115OpenClient):
4954
5850
  resp = yield self.login_qrcode_scan_confirm(login_uid, async_=async_, **request_kwargs)
4955
5851
  check_response(resp)
4956
5852
  return self.login_qrcode_access_token_open(login_uid, async_=async_, **request_kwargs)
4957
- return run_gen_step(gen_step, async_=async_)
5853
+ return run_gen_step(gen_step, simple=True, async_=async_)
4958
5854
 
4959
5855
  @overload
4960
5856
  def login_another_app(
@@ -5098,7 +5994,7 @@ class P115Client(P115OpenClient):
5098
5994
  if self is not inst and ssoent == inst.login_ssoent:
5099
5995
  warn(f"login with the same ssoent {ssoent!r}, {self!r} will expire within 60 seconds", category=P115Warning)
5100
5996
  return inst
5101
- return run_gen_step(gen_step, async_=async_)
5997
+ return run_gen_step(gen_step, simple=True, async_=async_)
5102
5998
 
5103
5999
  @overload
5104
6000
  def login_another_open(
@@ -5192,7 +6088,7 @@ class P115Client(P115OpenClient):
5192
6088
  inst.access_token = data["access_token"]
5193
6089
  inst.app_id = app_id
5194
6090
  return inst
5195
- return run_gen_step(gen_step, async_=async_)
6091
+ return run_gen_step(gen_step, simple=True, async_=async_)
5196
6092
 
5197
6093
  @overload
5198
6094
  @classmethod
@@ -5311,7 +6207,7 @@ class P115Client(P115OpenClient):
5311
6207
  resp = yield cls.login_qrcode_scan_result(uid, app, async_=async_, **request_kwargs)
5312
6208
  cookies = check_response(resp)["data"]["cookie"]
5313
6209
  return cls(cookies, check_for_relogin=check_for_relogin)
5314
- return run_gen_step(gen_step, async_=async_)
6210
+ return run_gen_step(gen_step, simple=True, async_=async_)
5315
6211
 
5316
6212
  @overload
5317
6213
  def logout(
@@ -5347,7 +6243,7 @@ class P115Client(P115OpenClient):
5347
6243
  return None
5348
6244
  return self.logout_by_ssoent(ssoent, async_=async_, **request_kwargs)
5349
6245
 
5350
- def request(
6246
+ def _request(
5351
6247
  self,
5352
6248
  /,
5353
6249
  url: str,
@@ -5463,7 +6359,7 @@ class P115Client(P115OpenClient):
5463
6359
  if async_:
5464
6360
  fetch_cert_headers = ensure_async(fetch_cert_headers)
5465
6361
  if fetch_cert_headers_argcount:
5466
- fetch_cert_headers = partial(fetch_cert_headers, async_)
6362
+ fetch_cert_headers = cast(Callable, fetch_cert_headers)(async_)
5467
6363
  if revert_cert_headers is not None and async_:
5468
6364
  revert_cert_headers = ensure_async(revert_cert_headers)
5469
6365
  if is_open_api:
@@ -5517,8 +6413,7 @@ class P115Client(P115OpenClient):
5517
6413
  cert: str = headers["authorization"]
5518
6414
  else:
5519
6415
  cert = headers["cookie"]
5520
- resp = yield partial(
5521
- cast(Callable, request),
6416
+ resp = yield cast(Callable, request)(
5522
6417
  url=url,
5523
6418
  method=method,
5524
6419
  **request_kwargs,
@@ -5531,10 +6426,10 @@ class P115Client(P115OpenClient):
5531
6426
  not is_auth_error and
5532
6427
  get_status_code(e) != 405
5533
6428
  ):
5534
- yield partial(revert_cert_headers, cert_headers)
6429
+ yield revert_cert_headers(cert_headers)
5535
6430
  if not need_to_check:
5536
6431
  raise
5537
- res = yield partial(cast(Callable, check_for_relogin), e)
6432
+ res = yield cast(Callable, check_for_relogin)(e)
5538
6433
  if not res if isinstance(res, bool) else res != 405:
5539
6434
  raise
5540
6435
  if fetch_cert_headers is not None:
@@ -5583,11 +6478,95 @@ class P115Client(P115OpenClient):
5583
6478
  lock.release()
5584
6479
  else:
5585
6480
  if cert_headers is not None and revert_cert_headers is not None:
5586
- yield partial(revert_cert_headers, cert_headers)
6481
+ yield revert_cert_headers(cert_headers)
5587
6482
  if check and isinstance(resp, dict):
5588
6483
  check_response(resp)
5589
6484
  return resp
5590
- return run_gen_step(gen_step, async_=async_)
6485
+ return run_gen_step(gen_step, simple=True, async_=async_)
6486
+
6487
+ def request(
6488
+ self,
6489
+ /,
6490
+ url: str,
6491
+ method: str = "GET",
6492
+ params = None,
6493
+ data = None,
6494
+ *,
6495
+ async_: Literal[False, True] = False,
6496
+ **request_kwargs,
6497
+ ):
6498
+ """帮助函数:可执行同步和异步的网络请求
6499
+
6500
+ :param url: HTTP 的请求链接
6501
+ :param method: HTTP 的请求方法
6502
+ :param params: 查询参数
6503
+ :param check: 是否用 `check_response` 函数检查返回值
6504
+ :param ecdh_encrypt: 使用 ecdh 算法进行加密(返回值也要解密)
6505
+ :param fetch_cert_headers: 调用以获取认证信息头
6506
+ :param revert_cert_headers: 调用以退还认证信息头
6507
+ :param async_: 说明 `request` 是同步调用还是异步调用
6508
+ :param request: HTTP 请求调用,如果为 None,则默认用 httpx 执行请求
6509
+ 如果传入调用,则必须至少能接受以下几个关键词参数:
6510
+
6511
+ - url: HTTP 的请求链接
6512
+ - method: HTTP 的请求方法
6513
+ - headers: HTTP 的请求头
6514
+ - data: HTTP 的请求体
6515
+ - parse: 解析 HTTP 响应的方法,默认会构建一个 Callable,会把响应的字节数据视为 JSON 进行反序列化解析
6516
+
6517
+ - 如果为 None,则直接把响应对象返回
6518
+ - 如果为 ...(Ellipsis),则把响应对象关闭后将其返回
6519
+ - 如果为 True,则根据响应头来确定把响应得到的字节数据解析成何种格式(反序列化),请求也会被自动关闭
6520
+ - 如果为 False,则直接返回响应得到的字节数据,请求也会被自动关闭
6521
+ - 如果为 Callable,则使用此调用来解析数据,接受 1-2 个位置参数,并把解析结果返回给 `request` 的调用者,请求也会被自动关闭
6522
+ - 如果只接受 1 个位置参数,则把响应对象传给它
6523
+ - 如果能接受 2 个位置参数,则把响应对象和响应得到的字节数据(响应体)传给它
6524
+
6525
+ :param request_kwargs: 其余的请求参数,会被传给 `request`
6526
+
6527
+ :return: 直接返回 `request` 执行请求后的返回值
6528
+
6529
+ .. note::
6530
+ `request` 可以由不同的请求库来提供,下面是封装了一些模块
6531
+
6532
+ 1. `httpx_request <https://pypi.org/project/httpx_request/>`_,由 `httpx <https://pypi.org/project/httpx/>`_ 封装,支持同步和异步调用,本模块默认用的就是这个封装
6533
+
6534
+ .. code:: python
6535
+
6536
+ from httpx_request import request
6537
+
6538
+ 2. `python-urlopen <https://pypi.org/project/python-urlopen/>`_,由 `urllib.request.urlopen <https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen>`_ 封装,支持同步调用,性能相对最差
6539
+
6540
+ .. code:: python
6541
+
6542
+ from urlopen import request
6543
+
6544
+ 3. `urllib3_request <https://pypi.org/project/urllib3_request/>`_,由 `urllib3 <https://pypi.org/project/urllib3/>`_ 封装,支持同步调用,性能相对较好,推荐使用
6545
+
6546
+ .. code:: python
6547
+
6548
+ from urllib3_request import request
6549
+
6550
+ 4. `requests_request <https://pypi.org/project/requests_request/>`_,由 `requests <https://pypi.org/project/requests/>`_ 封装,支持同步调用
6551
+
6552
+ .. code:: python
6553
+
6554
+ from requests_request import request
6555
+
6556
+ 5. `aiohttp_client_request <https://pypi.org/project/aiohttp_client_request/>`_,由 `aiohttp <https://pypi.org/project/aiohttp/>`_ 封装,支持异步调用,异步并发能力最强,推荐使用
6557
+
6558
+ .. code:: python
6559
+
6560
+ from aiohttp_client_request import request
6561
+
6562
+ 6. `blacksheep_client_request <https://pypi.org/project/blacksheep_client_request/>`_,由 `blacksheep <https://pypi.org/project/blacksheep/>`_ 封装,支持异步调用
6563
+
6564
+ .. code:: python
6565
+
6566
+ from blacksheep_client_request import request
6567
+ """
6568
+ kwargs = {**self.request_kwargs, **request_kwargs}
6569
+ return self._request(url, method, params, data, async_=async_, **kwargs)
5591
6570
 
5592
6571
  ########## Activity API ##########
5593
6572
 
@@ -6355,15 +7334,14 @@ class P115Client(P115OpenClient):
6355
7334
  if "sign" not in payload:
6356
7335
  resp = yield self.captcha_sign(async_=async_)
6357
7336
  payload["sign"] = resp["sign"]
6358
- return partial(
6359
- self.request,
7337
+ return self.request(
6360
7338
  url=api,
6361
7339
  method="POST",
6362
7340
  data=payload,
6363
7341
  async_=async_,
6364
7342
  **request_kwargs,
6365
7343
  )
6366
- return run_gen_step(gen_step, async_=async_)
7344
+ return run_gen_step(gen_step, simple=True, async_=async_)
6367
7345
 
6368
7346
  ########## Download API ##########
6369
7347
 
@@ -9057,9 +10035,9 @@ class P115Client(P115OpenClient):
9057
10035
  4. show_dir=0 且 cur=0(或不指定 cur)
9058
10036
 
9059
10037
  .. hint::
9060
- 如果仅指定 natsort=1&show_dir=1,以及一个可选的 cid,则当文件数不大于 1150 时可仅统计某个目录内的文件或目录总数,而不返回具体的文件信息,超过那个数值时,则会返回完整的文件列表
10038
+ 如果不指定或者指定的 cid 不存在,则会视为 cid=0 进行处理
9061
10039
 
9062
- 但如果不指定或者指定的 cid 不存在,则会视为 cid=0 进行处理
10040
+ 当指定 natsort=1 时,如果里面的数量较少时,可仅统计某个目录内的文件或目录总数,而不返回具体的文件信息
9063
10041
 
9064
10042
  .. hint::
9065
10043
  当一个 cookies 被另一个更新的登录所失效,并不意味着这个 cookies 就直接不可用了。
@@ -14938,12 +15916,145 @@ class P115Client(P115OpenClient):
14938
15916
  if device is None:
14939
15917
  return None
14940
15918
  return device["icon"]
14941
- return run_gen_step(gen_step, async_=async_)
15919
+ return run_gen_step(gen_step, simple=True, async_=async_)
15920
+
15921
+ @overload
15922
+ def login_open_auth_detail(
15923
+ self,
15924
+ payload: int | str | dict,
15925
+ /,
15926
+ app: str = "web",
15927
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15928
+ *,
15929
+ async_: Literal[False] = False,
15930
+ **request_kwargs,
15931
+ ) -> dict:
15932
+ ...
15933
+ @overload
15934
+ def login_open_auth_detail(
15935
+ self,
15936
+ payload: int | str | dict,
15937
+ /,
15938
+ app: str = "web",
15939
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15940
+ *,
15941
+ async_: Literal[True],
15942
+ **request_kwargs,
15943
+ ) -> Coroutine[Any, Any, dict]:
15944
+ ...
15945
+ def login_open_auth_detail(
15946
+ self,
15947
+ payload: int | str | dict,
15948
+ /,
15949
+ app: str = "web",
15950
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15951
+ *,
15952
+ async_: Literal[False, True] = False,
15953
+ **request_kwargs,
15954
+ ) -> dict | Coroutine[Any, Any, dict]:
15955
+ """获取某个开放应用的授权信息
15956
+
15957
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/user/getAppAuthDetail
15958
+
15959
+ :payload:
15960
+ - auth_id: int | str 💡 授权 id
15961
+ """
15962
+ api = complete_api(f"/app/1.0/{app}/1.0/user/getAppAuthDetail", base_url=base_url)
15963
+ if isinstance(payload, (int, str)):
15964
+ payload = {"auth_id": payload}
15965
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
15966
+
15967
+ @overload
15968
+ def login_open_auth_list(
15969
+ self,
15970
+ /,
15971
+ app: str = "web",
15972
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15973
+ *,
15974
+ async_: Literal[False] = False,
15975
+ **request_kwargs,
15976
+ ) -> dict:
15977
+ ...
15978
+ @overload
15979
+ def login_open_auth_list(
15980
+ self,
15981
+ /,
15982
+ app: str = "web",
15983
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15984
+ *,
15985
+ async_: Literal[True],
15986
+ **request_kwargs,
15987
+ ) -> Coroutine[Any, Any, dict]:
15988
+ ...
15989
+ def login_open_auth_list(
15990
+ self,
15991
+ /,
15992
+ app: str = "web",
15993
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15994
+ *,
15995
+ async_: Literal[False, True] = False,
15996
+ **request_kwargs,
15997
+ ) -> dict | Coroutine[Any, Any, dict]:
15998
+ """获取所有授权的开放应用的列表
15999
+
16000
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/user/getAppAuthList
16001
+ """
16002
+ api = complete_api(f"/app/1.0/{app}/1.0/user/getAppAuthList", base_url=base_url)
16003
+ return self.request(url=api, async_=async_, **request_kwargs)
16004
+
16005
+ @overload
16006
+ def login_open_deauth(
16007
+ self,
16008
+ payload: int | str | dict,
16009
+ /,
16010
+ app: str = "web",
16011
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16012
+ *,
16013
+ async_: Literal[False] = False,
16014
+ **request_kwargs,
16015
+ ) -> dict:
16016
+ ...
16017
+ @overload
16018
+ def login_open_deauth(
16019
+ self,
16020
+ payload: int | str | dict,
16021
+ /,
16022
+ app: str = "web",
16023
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16024
+ *,
16025
+ async_: Literal[True],
16026
+ **request_kwargs,
16027
+ ) -> Coroutine[Any, Any, dict]:
16028
+ ...
16029
+ def login_open_deauth(
16030
+ self,
16031
+ payload: int | str | dict,
16032
+ /,
16033
+ app: str = "web",
16034
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16035
+ *,
16036
+ async_: Literal[False, True] = False,
16037
+ **request_kwargs,
16038
+ ) -> dict | Coroutine[Any, Any, dict]:
16039
+ """取消某个开放应用的授权
16040
+
16041
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/user/deauthApp
16042
+
16043
+ :payload:
16044
+ - auth_id: int | str 💡 授权 id
16045
+ """
16046
+ api = complete_api(f"/app/1.0/{app}/1.0/user/deauthApp", base_url=base_url)
16047
+ if isinstance(payload, (int, str)):
16048
+ payload = {"auth_id": payload}
16049
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
14942
16050
 
14943
16051
  @overload
14944
16052
  def login_check_sso(
14945
16053
  self,
14946
16054
  /,
16055
+ app: str = "web",
16056
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16057
+ *,
14947
16058
  async_: Literal[False] = False,
14948
16059
  **request_kwargs,
14949
16060
  ) -> dict:
@@ -14952,6 +16063,9 @@ class P115Client(P115OpenClient):
14952
16063
  def login_check_sso(
14953
16064
  self,
14954
16065
  /,
16066
+ app: str = "web",
16067
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16068
+ *,
14955
16069
  async_: Literal[True],
14956
16070
  **request_kwargs,
14957
16071
  ) -> Coroutine[Any, Any, dict]:
@@ -14959,14 +16073,17 @@ class P115Client(P115OpenClient):
14959
16073
  def login_check_sso(
14960
16074
  self,
14961
16075
  /,
16076
+ app: str = "web",
16077
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16078
+ *,
14962
16079
  async_: Literal[False, True] = False,
14963
16080
  **request_kwargs,
14964
16081
  ) -> dict | Coroutine[Any, Any, dict]:
14965
16082
  """检查当前 cookies 的登录状态信息,并且自最近一次登录的 60 秒后,使当前设备下除最近一次登录外的所有 cookies 失效
14966
16083
 
14967
- GET https://passportapi.115.com/app/1.0/web/1.0/check/sso
16084
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/check/sso
14968
16085
  """
14969
- api = "https://passportapi.115.com/app/1.0/web/1.0/check/sso"
16086
+ api = complete_api(f"/app/1.0/{app}/1.0/check/sso", base_url=base_url)
14970
16087
  return self.request(url=api, async_=async_, **request_kwargs)
14971
16088
 
14972
16089
  @overload
@@ -15005,6 +16122,9 @@ class P115Client(P115OpenClient):
15005
16122
  def login_devices(
15006
16123
  self,
15007
16124
  /,
16125
+ app: str = "web",
16126
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16127
+ *,
15008
16128
  async_: Literal[False] = False,
15009
16129
  **request_kwargs,
15010
16130
  ) -> dict:
@@ -15013,6 +16133,9 @@ class P115Client(P115OpenClient):
15013
16133
  def login_devices(
15014
16134
  self,
15015
16135
  /,
16136
+ app: str = "web",
16137
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16138
+ *,
15016
16139
  async_: Literal[True],
15017
16140
  **request_kwargs,
15018
16141
  ) -> Coroutine[Any, Any, dict]:
@@ -15020,14 +16143,17 @@ class P115Client(P115OpenClient):
15020
16143
  def login_devices(
15021
16144
  self,
15022
16145
  /,
16146
+ app: str = "web",
16147
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
16148
+ *,
15023
16149
  async_: Literal[False, True] = False,
15024
16150
  **request_kwargs,
15025
16151
  ) -> dict | Coroutine[Any, Any, dict]:
15026
16152
  """获取所有的已登录设备的信息,不过当前的 cookies 必须是登录状态(未退出或未失效)
15027
16153
 
15028
- GET https://passportapi.115.com/app/1.0/web/1.0/login_log/login_devices
16154
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/login_log/login_devices
15029
16155
  """
15030
- api = "https://passportapi.115.com/app/1.0/web/1.0/login_log/login_devices"
16156
+ api = complete_api(f"/app/1.0/{app}/1.0/login_log/login_devices", base_url=base_url)
15031
16157
  return self.request(url=api, async_=async_, **request_kwargs)
15032
16158
 
15033
16159
  @overload
@@ -15073,6 +16199,8 @@ class P115Client(P115OpenClient):
15073
16199
  self,
15074
16200
  payload: dict = {},
15075
16201
  /,
16202
+ app: str = "web",
16203
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15076
16204
  *,
15077
16205
  async_: Literal[False] = False,
15078
16206
  **request_kwargs,
@@ -15083,6 +16211,8 @@ class P115Client(P115OpenClient):
15083
16211
  self,
15084
16212
  payload: dict = {},
15085
16213
  /,
16214
+ app: str = "web",
16215
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15086
16216
  *,
15087
16217
  async_: Literal[True],
15088
16218
  **request_kwargs,
@@ -15092,19 +16222,21 @@ class P115Client(P115OpenClient):
15092
16222
  self,
15093
16223
  payload: dict = {},
15094
16224
  /,
16225
+ app: str = "web",
16226
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15095
16227
  *,
15096
16228
  async_: Literal[False, True] = False,
15097
16229
  **request_kwargs,
15098
16230
  ) -> dict | Coroutine[Any, Any, dict]:
15099
16231
  """获取登录信息日志列表
15100
16232
 
15101
- GET https://passportapi.115.com/app/1.0/web/1.0/login_log/log
16233
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/login_log/log
15102
16234
 
15103
16235
  :payload:
15104
16236
  - start: int = 0
15105
16237
  - limit: int = 100
15106
16238
  """
15107
- api = "https://passportapi.115.com/app/1.0/web/1.0/login_log/log"
16239
+ api = complete_api(f"/app/1.0/{app}/1.0/login_log/log", base_url=base_url)
15108
16240
  payload = {"start": 0, "limit": 100, **payload}
15109
16241
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
15110
16242
 
@@ -15113,6 +16245,7 @@ class P115Client(P115OpenClient):
15113
16245
  self,
15114
16246
  /,
15115
16247
  app: str = "web",
16248
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15116
16249
  *,
15117
16250
  async_: Literal[False] = False,
15118
16251
  **request_kwargs,
@@ -15123,6 +16256,7 @@ class P115Client(P115OpenClient):
15123
16256
  self,
15124
16257
  /,
15125
16258
  app: str = "web",
16259
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15126
16260
  *,
15127
16261
  async_: Literal[True],
15128
16262
  **request_kwargs,
@@ -15132,15 +16266,16 @@ class P115Client(P115OpenClient):
15132
16266
  self,
15133
16267
  /,
15134
16268
  app: str = "web",
16269
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15135
16270
  *,
15136
16271
  async_: Literal[False, True] = False,
15137
16272
  **request_kwargs,
15138
16273
  ) -> dict | Coroutine[Any, Any, dict]:
15139
16274
  """当前登录的设备总数和最近登录的设备
15140
16275
 
15141
- GET https://passportapi.115.com/app/1.0/web/1.0/login_log/login_online
16276
+ GET https://qrcodeapi.115.com/app/1.0/web/1.0/login_log/login_online
15142
16277
  """
15143
- api = f"http://passportapi.115.com/app/1.0/{app}/1.0/login_log/login_online"
16278
+ api = complete_api(f"/app/1.0/{app}/1.0/login_log/login_online", base_url=base_url)
15144
16279
  return self.request(url=api, async_=async_, **request_kwargs)
15145
16280
 
15146
16281
  @overload
@@ -15202,6 +16337,7 @@ class P115Client(P115OpenClient):
15202
16337
  /,
15203
16338
  app: None | str = None,
15204
16339
  request: None | Callable = None,
16340
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15205
16341
  *,
15206
16342
  async_: Literal[False] = False,
15207
16343
  **request_kwargs,
@@ -15213,6 +16349,7 @@ class P115Client(P115OpenClient):
15213
16349
  /,
15214
16350
  app: None | str = None,
15215
16351
  request: None | Callable = None,
16352
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15216
16353
  *,
15217
16354
  async_: Literal[True],
15218
16355
  **request_kwargs,
@@ -15223,13 +16360,14 @@ class P115Client(P115OpenClient):
15223
16360
  /,
15224
16361
  app: None | str = None,
15225
16362
  request: None | Callable = None,
16363
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15226
16364
  *,
15227
16365
  async_: Literal[False, True] = False,
15228
16366
  **request_kwargs,
15229
16367
  ) -> None | Coroutine[Any, Any, None]:
15230
16368
  """退出登录状态(可以把某个客户端下线,所有已登录设备可从 `login_devices` 获取)
15231
16369
 
15232
- GET https://passportapi.115.com/app/1.0/{app}/1.0/logout/logout
16370
+ GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/logout/logout
15233
16371
 
15234
16372
  :param app: 退出登录的 app
15235
16373
 
@@ -15295,20 +16433,22 @@ class P115Client(P115OpenClient):
15295
16433
  app = yield self.login_app(async_=async_)
15296
16434
  if app == "desktop":
15297
16435
  app = "web"
15298
- api = f"http://passportapi.115.com/app/1.0/{app}/1.0/logout/logout"
16436
+ api = complete_api(f"/app/1.0/{app}/1.0/logout/logout", base_url=base_url)
15299
16437
  request_kwargs["headers"] = {**(request_kwargs.get("headers") or {}), "Cookie": self.cookies_str}
15300
16438
  request_kwargs.setdefault("parse", ...)
15301
16439
  if request is None:
15302
16440
  return get_default_request()(url=api, async_=async_, **request_kwargs)
15303
16441
  else:
15304
16442
  return request(url=api, **request_kwargs)
15305
- return run_gen_step(gen_step, async_=async_)
16443
+ return run_gen_step(gen_step, simple=True, async_=async_)
15306
16444
 
15307
16445
  @overload
15308
16446
  def logout_by_ssoent(
15309
16447
  self,
15310
16448
  payload: None | str | dict = None,
15311
16449
  /,
16450
+ app: str = "web",
16451
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15312
16452
  *,
15313
16453
  async_: Literal[False] = False,
15314
16454
  **request_kwargs,
@@ -15319,6 +16459,8 @@ class P115Client(P115OpenClient):
15319
16459
  self,
15320
16460
  payload: None | str | dict = None,
15321
16461
  /,
16462
+ app: str = "web",
16463
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15322
16464
  *,
15323
16465
  async_: Literal[True],
15324
16466
  **request_kwargs,
@@ -15328,13 +16470,15 @@ class P115Client(P115OpenClient):
15328
16470
  self,
15329
16471
  payload: None | str | dict = None,
15330
16472
  /,
16473
+ app: str = "web",
16474
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
15331
16475
  *,
15332
16476
  async_: Literal[False, True] = False,
15333
16477
  **request_kwargs,
15334
16478
  ) -> dict | Coroutine[Any, Any, dict]:
15335
16479
  """退出登录状态(可以把某个客户端下线,所有已登录设备可从 `login_devices` 获取)
15336
16480
 
15337
- POST https://passportapi.115.com/app/1.0/web/1.0/logout/mange
16481
+ POST https://qrcodeapi.115.com/app/1.0/web/1.0/logout/mange
15338
16482
 
15339
16483
  :payload:
15340
16484
  - ssoent: str
@@ -15395,7 +16539,7 @@ class P115Client(P115OpenClient):
15395
16539
  | 24 | S1 | harmony | 115(Harmony端) |
15396
16540
  +-------+----------+------------+-------------------------+
15397
16541
  """
15398
- api = "https://passportapi.115.com/app/1.0/web/1.0/logout/mange"
16542
+ api = complete_api(f"/app/1.0/{app}/1.0/logout/mange", base_url=base_url)
15399
16543
  if payload is None:
15400
16544
  payload = {"ssoent": self.login_ssoent or ""}
15401
16545
  elif isinstance(payload, str):
@@ -16389,7 +17533,7 @@ class P115Client(P115OpenClient):
16389
17533
  method = self._offline_lixianssp_post
16390
17534
  return method(payload, ac, async_=async_, ecdh_encrypt=ecdh_encrypt, **request_kwargs)
16391
17535
 
16392
- @overload
17536
+ @overload # type: ignore
16393
17537
  def offline_add_torrent(
16394
17538
  self,
16395
17539
  payload: str | dict,
@@ -16431,8 +17575,8 @@ class P115Client(P115OpenClient):
16431
17575
 
16432
17576
  :payload:
16433
17577
  - info_hash: str 💡 种子文件的 info_hash
16434
- - wanted: str = <default> 💡 选择文件进行下载(是数字用 "," 分隔)
16435
- - savepath: str = <default> 💡 保存到目录下的相对路径
17578
+ - wanted: str = <default> 💡 选择文件进行下载(是数字索引,从 0 开始计数,用 "," 分隔)
17579
+ - savepath: str = <default> 💡 保存到 `wp_path_id` 对应目录下的相对路径
16436
17580
  - wp_path_id: int | str = <default> 💡 保存到目录的 id
16437
17581
  """
16438
17582
  if isinstance(payload, str):
@@ -16483,7 +17627,7 @@ class P115Client(P115OpenClient):
16483
17627
  payload = {"url": payload}
16484
17628
  return self._offline_post(payload, "add_task_url", use_web_api=use_web_api, async_=async_, **request_kwargs)
16485
17629
 
16486
- @overload
17630
+ @overload # type: ignore
16487
17631
  def offline_add_urls(
16488
17632
  self,
16489
17633
  payload: str | Iterable[str] | dict,
@@ -16526,7 +17670,7 @@ class P115Client(P115OpenClient):
16526
17670
  - wp_path_id: int | str = <default> 💡 保存到目录的 id
16527
17671
  """
16528
17672
  if isinstance(payload, str):
16529
- payload = payload.strip().split("\n")
17673
+ payload = payload.strip("\n").split("\n")
16530
17674
  if not isinstance(payload, dict):
16531
17675
  payload = {f"url[{i}]": url for i, url in enumerate(payload)}
16532
17676
  if not payload:
@@ -16536,7 +17680,7 @@ class P115Client(P115OpenClient):
16536
17680
  @overload
16537
17681
  def offline_clear(
16538
17682
  self,
16539
- payload: int | dict,
17683
+ payload: int | dict = 0,
16540
17684
  /,
16541
17685
  base_url: None | bool | str | Callable[[], str] = None,
16542
17686
  *,
@@ -16547,7 +17691,7 @@ class P115Client(P115OpenClient):
16547
17691
  @overload
16548
17692
  def offline_clear(
16549
17693
  self,
16550
- payload: int | dict,
17694
+ payload: int | dict = 0,
16551
17695
  /,
16552
17696
  base_url: None | bool | str | Callable[[], str] = None,
16553
17697
  *,
@@ -16557,7 +17701,7 @@ class P115Client(P115OpenClient):
16557
17701
  ...
16558
17702
  def offline_clear(
16559
17703
  self,
16560
- payload: int | dict = {"flag": 0},
17704
+ payload: int | dict = 0,
16561
17705
  /,
16562
17706
  base_url: None | bool | str | Callable[[], str] = None,
16563
17707
  *,
@@ -16580,11 +17724,6 @@ class P115Client(P115OpenClient):
16580
17724
  """
16581
17725
  api = complete_lixian_api("?ct=lixian&ac=task_clear", base_url=base_url)
16582
17726
  if isinstance(payload, int):
16583
- flag = payload
16584
- if flag < 0:
16585
- flag = 0
16586
- elif flag > 5:
16587
- flag = 5
16588
17727
  payload = {"flag": payload}
16589
17728
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
16590
17729
 
@@ -16658,7 +17797,7 @@ class P115Client(P115OpenClient):
16658
17797
  api = complete_api("/?ct=offline&ac=space", base_url=base_url)
16659
17798
  return self.request(url=api, async_=async_, **request_kwargs)
16660
17799
 
16661
- @overload
17800
+ @overload # type: ignore
16662
17801
  def offline_list(
16663
17802
  self,
16664
17803
  payload: int | dict = 1,
@@ -16694,7 +17833,7 @@ class P115Client(P115OpenClient):
16694
17833
  POST https://lixian.115.com/lixian/?ct=lixian&ac=task_lists
16695
17834
 
16696
17835
  :payload:
16697
- - page: int | str
17836
+ - page: int | str = 1
16698
17837
  """
16699
17838
  api = complete_lixian_api("?ct=lixian&ac=task_lists", base_url=base_url)
16700
17839
  if isinstance(payload, int):
@@ -16809,7 +17948,7 @@ class P115Client(P115OpenClient):
16809
17948
  @overload
16810
17949
  def offline_remove(
16811
17950
  self,
16812
- payload: str | dict,
17951
+ payload: str | Iterable[str] | dict,
16813
17952
  /,
16814
17953
  base_url: None | bool | str | Callable[[], str] = None,
16815
17954
  *,
@@ -16820,7 +17959,7 @@ class P115Client(P115OpenClient):
16820
17959
  @overload
16821
17960
  def offline_remove(
16822
17961
  self,
16823
- payload: str | dict,
17962
+ payload: str | Iterable[str] | dict,
16824
17963
  /,
16825
17964
  base_url: None | bool | str | Callable[[], str] = None,
16826
17965
  *,
@@ -16830,7 +17969,7 @@ class P115Client(P115OpenClient):
16830
17969
  ...
16831
17970
  def offline_remove(
16832
17971
  self,
16833
- payload: str | dict,
17972
+ payload: str | Iterable[str] | dict,
16834
17973
  /,
16835
17974
  base_url: None | bool | str | Callable[[], str] = None,
16836
17975
  *,
@@ -19195,7 +20334,7 @@ class P115Client(P115OpenClient):
19195
20334
  if resp["state"]:
19196
20335
  self.user_key = resp["data"]["userkey"]
19197
20336
  return resp
19198
- return run_gen_step(gen_step, async_=async_)
20337
+ return run_gen_step(gen_step, simple=True, async_=async_)
19199
20338
 
19200
20339
  @overload
19201
20340
  def upload_resume(
@@ -19562,7 +20701,7 @@ class P115Client(P115OpenClient):
19562
20701
  "pickcode": resp["pickcode"],
19563
20702
  }
19564
20703
  return resp
19565
- return run_gen_step(gen_step, async_=async_)
20704
+ return run_gen_step(gen_step, simple=True, async_=async_)
19566
20705
 
19567
20706
  @overload
19568
20707
  def upload_file_sample(
@@ -19698,7 +20837,7 @@ class P115Client(P115OpenClient):
19698
20837
  async_=async_,
19699
20838
  **request_kwargs,
19700
20839
  )
19701
- return run_gen_step(gen_step, async_=async_)
20840
+ return run_gen_step(gen_step, simple=True, async_=async_)
19702
20841
 
19703
20842
  @overload # type: ignore
19704
20843
  def upload_file(
@@ -20057,7 +21196,7 @@ class P115Client(P115OpenClient):
20057
21196
  async_=async_, # type: ignore
20058
21197
  **request_kwargs,
20059
21198
  )
20060
- return run_gen_step(gen_step, async_=async_)
21199
+ return run_gen_step(gen_step, simple=True, async_=async_)
20061
21200
 
20062
21201
  ########## User API ##########
20063
21202