p115client 0.0.5.10.8__py3-none-any.whl → 0.0.5.10.9__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
@@ -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 ("open", "aps", "web"):
185
185
  app = "android"
186
186
  if app and not app.startswith("/"):
187
187
  app = "/" + app
@@ -616,9 +616,6 @@ def normalize_attr_web(
616
616
  if "te" in info:
617
617
  attr["mtime"] = int(info["te"])
618
618
  else:
619
- attr["labels"] = info["fl"]
620
- if "score" in info:
621
- attr["score"] = int(info.get("score") or 0)
622
619
  attr["ico"] = info.get("ico", "folder" if is_directory else "")
623
620
  if "te" in info:
624
621
  attr["mtime"] = attr["user_utime"] = int(info["te"])
@@ -651,12 +648,14 @@ def normalize_attr_web(
651
648
  ("c", "violated"),
652
649
  ("c", "is_collect"),
653
650
  ("sh", "is_share"),
651
+ ("score", "score"),
654
652
  #("d", "has_desc"),
655
653
  #("p", "has_pass"),
656
654
  ):
657
655
  if key in info:
658
656
  attr[name] = int(info[key] or 0)
659
657
  for key, name in (
658
+ ("fl", "labels"),
660
659
  ("dp", "dir_path"),
661
660
  ("style", "style"),
662
661
  ("ns", "name_show"),
@@ -744,10 +743,7 @@ def normalize_attr_app(
744
743
  if "upt" in info:
745
744
  attr["mtime"] = int(info["upt"])
746
745
  else:
747
- attr["labels"] = info["fl"]
748
746
  attr["ico"] = info.get("ico", "folder" if attr["is_dir"] else "")
749
- if "ftype" in info:
750
- attr["file_type"] = int(info["ftype"] or 0)
751
747
  if "thumb" in info:
752
748
  thumb = info["thumb"]
753
749
  if thumb.startswith("?"):
@@ -763,6 +759,7 @@ def normalize_attr_app(
763
759
  ("aid", "area_id"),
764
760
  ("fatr", "audio_play_long"),
765
761
  ("fta", "status"),
762
+ ("ftype", "file_type"),
766
763
  ("ism", "star"),
767
764
  ("ism", "is_mark"),
768
765
  ("is_top", "is_top"),
@@ -784,6 +781,7 @@ def normalize_attr_app(
784
781
  ("fco", "cover"),
785
782
  ("fco", "folder_cover"),
786
783
  ("fdesc", "desc"),
784
+ ("fl", "labels"),
787
785
  ("flabel", "fflabel"),
788
786
  ("multitrack", "multitrack"),
789
787
  ("play_long", "play_long"),
@@ -838,18 +836,28 @@ def normalize_attr_app2(
838
836
  else:
839
837
  dict_cls = AttrDict
840
838
  attr: dict[str, Any] = dict_cls()
841
- is_directory = attr["is_dir"] = "file_id" not in info
842
- if not simple:
843
- attr["is_directory"] = is_directory
844
- if is_directory:
839
+ if "file_id" in info and "parent_id" in info:
840
+ if "file_category" in info:
841
+ is_directory = not int(info["file_category"])
842
+ else:
843
+ is_directory = bool(info.get("sha1") or info.get("file_sha1"))
845
844
  attr["id"] = int(info["file_id"])
846
- attr["parent_id"] = int(info["category_id"])
845
+ attr["parent_id"] = int(info["parent_id"])
847
846
  attr["name"] = info["file_name"]
848
847
  else:
849
- attr["id"] = int(info["category_id"])
850
- attr["parent_id"] = int(info["parent_id"])
851
- attr["name"] = info["category_name"]
852
- attr["sha1"] = info.get("sha1") or ""
848
+ is_directory = "file_id" not in info
849
+ if is_directory:
850
+ attr["id"] = int(info["file_id"])
851
+ attr["parent_id"] = int(info["category_id"])
852
+ attr["name"] = info["file_name"]
853
+ else:
854
+ attr["id"] = int(info["category_id"])
855
+ attr["parent_id"] = int(info["parent_id"])
856
+ attr["name"] = info["category_name"]
857
+ attr["is_dir"] = is_directory
858
+ if not simple:
859
+ attr["is_directory"] = is_directory
860
+ attr["sha1"] = info.get("sha1") or info.get("file_sha1") or ""
853
861
  attr["size"] = int(info.get("file_size") or 0)
854
862
  if "pick_code" in info:
855
863
  attr["pickcode"] = info["pick_code"]
@@ -890,7 +898,8 @@ def normalize_attr_app2(
890
898
  if "utime" in info:
891
899
  attr["utime"] = int(info["utime"])
892
900
  attr["ico"] = info.get("ico", "folder" if attr["is_dir"] else "")
893
- attr["labels"] = info["fl"]
901
+ if "fl" in info:
902
+ attr["labels"] = info["fl"]
894
903
  for key, name in (
895
904
  ("area_id", "area_id"),
896
905
  ("has_desc", "has_desc"),
@@ -1158,13 +1167,17 @@ class ClientRequestMixin:
1158
1167
  "user-agent": "Mozilla/5.0 AppleWebKit/600 Safari/600 Chrome/124.0.0.0",
1159
1168
  })
1160
1169
 
1170
+ @locked_cacheproperty
1171
+ def request_kwargs(self, /) -> dict:
1172
+ return {}
1173
+
1161
1174
  def close(self, /) -> None:
1162
1175
  """删除 session 和 async_session 属性,如果它们未被引用,则应该会被自动清理
1163
1176
  """
1164
1177
  self.__dict__.pop("session", None)
1165
1178
  self.__dict__.pop("async_session", None)
1166
1179
 
1167
- def request(
1180
+ def _request(
1168
1181
  self,
1169
1182
  /,
1170
1183
  url: str,
@@ -1282,120 +1295,252 @@ class ClientRequestMixin:
1282
1295
  request_kwargs.setdefault("parse", default_parse)
1283
1296
  return request(url=url, method=method, **request_kwargs)
1284
1297
 
1298
+ def request(
1299
+ self,
1300
+ /,
1301
+ url: str,
1302
+ method: str = "GET",
1303
+ params = None,
1304
+ data = None,
1305
+ *,
1306
+ async_: Literal[False, True] = False,
1307
+ **request_kwargs,
1308
+ ):
1309
+ """帮助函数:可执行同步和异步的网络请求
1310
+
1311
+ :param url: HTTP 的请求链接
1312
+ :param method: HTTP 的请求方法
1313
+ :param params: 查询参数
1314
+ :param ecdh_encrypt: 使用 ecdh 算法进行加密(返回值也要解密)
1315
+ :param async_: 说明 `request` 是同步调用还是异步调用
1316
+ :param request: HTTP 请求调用,如果为 None,则默认用 httpx 执行请求
1317
+ 如果传入调用,则必须至少能接受以下几个关键词参数:
1318
+
1319
+ - url: HTTP 的请求链接
1320
+ - method: HTTP 的请求方法
1321
+ - headers: HTTP 的请求头
1322
+ - data: HTTP 的请求体
1323
+ - parse: 解析 HTTP 响应的方法,默认会构建一个 Callable,会把响应的字节数据视为 JSON 进行反序列化解析
1324
+
1325
+ - 如果为 None,则直接把响应对象返回
1326
+ - 如果为 ...(Ellipsis),则把响应对象关闭后将其返回
1327
+ - 如果为 True,则根据响应头来确定把响应得到的字节数据解析成何种格式(反序列化),请求也会被自动关闭
1328
+ - 如果为 False,则直接返回响应得到的字节数据,请求也会被自动关闭
1329
+ - 如果为 Callable,则使用此调用来解析数据,接受 1-2 个位置参数,并把解析结果返回给 `request` 的调用者,请求也会被自动关闭
1330
+ - 如果只接受 1 个位置参数,则把响应对象传给它
1331
+ - 如果能接受 2 个位置参数,则把响应对象和响应得到的字节数据(响应体)传给它
1332
+
1333
+ :param request_kwargs: 其余的请求参数,会被传给 `request`
1334
+
1335
+ :return: 直接返回 `request` 执行请求后的返回值
1336
+
1337
+ .. note::
1338
+ `request` 可以由不同的请求库来提供,下面是封装了一些模块
1339
+
1340
+ 1. `httpx_request <https://pypi.org/project/httpx_request/>`_,由 `httpx <https://pypi.org/project/httpx/>`_ 封装,支持同步和异步调用,本模块默认用的就是这个封装
1341
+
1342
+ .. code:: python
1343
+
1344
+ from httpx_request import request
1345
+
1346
+ 2. `python-urlopen <https://pypi.org/project/python-urlopen/>`_,由 `urllib.request.urlopen <https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen>`_ 封装,支持同步调用,性能相对最差
1347
+
1348
+ .. code:: python
1349
+
1350
+ from urlopen import request
1351
+
1352
+ 3. `urllib3_request <https://pypi.org/project/urllib3_request/>`_,由 `urllib3 <https://pypi.org/project/urllib3/>`_ 封装,支持同步调用,性能相对较好,推荐使用
1353
+
1354
+ .. code:: python
1355
+
1356
+ from urllib3_request import request
1357
+
1358
+ 4. `requests_request <https://pypi.org/project/requests_request/>`_,由 `requests <https://pypi.org/project/requests/>`_ 封装,支持同步调用
1359
+
1360
+ .. code:: python
1361
+
1362
+ from requests_request import request
1363
+
1364
+ 5. `aiohttp_client_request <https://pypi.org/project/aiohttp_client_request/>`_,由 `aiohttp <https://pypi.org/project/aiohttp/>`_ 封装,支持异步调用,异步并发能力最强,推荐使用
1365
+
1366
+ .. code:: python
1367
+
1368
+ from aiohttp_client_request import request
1369
+
1370
+ 6. `blacksheep_client_request <https://pypi.org/project/blacksheep_client_request/>`_,由 `blacksheep <https://pypi.org/project/blacksheep/>`_ 封装,支持异步调用
1371
+
1372
+ .. code:: python
1373
+
1374
+ from blacksheep_client_request import request
1375
+ """
1376
+ kwargs = {**self.request_kwargs, **request_kwargs}
1377
+ return self._request(url, method, params, data, async_=async_, **kwargs)
1378
+
1285
1379
  ########## Qrcode API ##########
1286
1380
 
1381
+ @overload
1382
+ def login_authorize_open(
1383
+ self,
1384
+ payload: dict,
1385
+ /,
1386
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1387
+ *,
1388
+ async_: Literal[False] = False,
1389
+ **request_kwargs,
1390
+ ) -> dict:
1391
+ ...
1392
+ @overload
1393
+ def login_authorize_open(
1394
+ self,
1395
+ payload: dict,
1396
+ /,
1397
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1398
+ *,
1399
+ async_: Literal[True],
1400
+ **request_kwargs,
1401
+ ) -> Coroutine[Any, Any, dict]:
1402
+ ...
1403
+ def login_authorize_open(
1404
+ self,
1405
+ payload: dict,
1406
+ /,
1407
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1408
+ *,
1409
+ async_: Literal[False, True] = False,
1410
+ **request_kwargs,
1411
+ ) -> dict | Coroutine[Any, Any, dict]:
1412
+ """授权码方式请求开放接口应用授权
1413
+
1414
+ GET https://qrcodeapi.115.com/open/authorize
1415
+
1416
+ .. admonition:: Reference
1417
+
1418
+ https://www.yuque.com/115yun/open/okr2cq0wywelscpe#EiOrD
1419
+
1420
+ :payload:
1421
+ - client_id: int | str 💡 AppID
1422
+ - redirect_uri: str 💡 授权成功后重定向到指定的地址并附上授权码 code,需要先到 https://open.115.com/ 应用管理应用域名设置
1423
+ - response_type: str = "code" 💡 授权模式,固定为 code,表示授权码模式
1424
+ - state: int | str = <default> 💡 随机值,会通过 redirect_uri 原样返回,可用于验证以防 MITM 和 CSRF
1425
+ """
1426
+ api = complete_api("/open/authorize", base_url=base_url)
1427
+ payload = {"response_type": "code", **payload}
1428
+ return self.request(url=api, params=payload, async_=async_, **request_kwargs)
1429
+
1287
1430
  @overload
1288
1431
  @staticmethod
1289
- def login_qrcode(
1290
- payload: str | dict,
1432
+ def login_authorize_access_token_open(
1433
+ payload: dict,
1291
1434
  /,
1292
1435
  request: None | Callable = None,
1293
- app: str = "web",
1294
1436
  base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1295
1437
  *,
1296
1438
  async_: Literal[False] = False,
1297
1439
  **request_kwargs,
1298
- ) -> bytes:
1440
+ ) -> dict:
1299
1441
  ...
1300
1442
  @overload
1301
1443
  @staticmethod
1302
- def login_qrcode(
1303
- payload: str | dict,
1444
+ def login_authorize_access_token_open(
1445
+ payload: dict,
1304
1446
  /,
1305
1447
  request: None | Callable = None,
1306
- app: str = "web",
1307
1448
  base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1308
1449
  *,
1309
1450
  async_: Literal[True],
1310
1451
  **request_kwargs,
1311
- ) -> Coroutine[Any, Any, bytes]:
1452
+ ) -> Coroutine[Any, Any, dict]:
1312
1453
  ...
1313
1454
  @staticmethod
1314
- def login_qrcode(
1315
- payload: str | dict,
1455
+ def login_authorize_access_token_open(
1456
+ payload: dict,
1316
1457
  /,
1317
1458
  request: None | Callable = None,
1318
- app: str = "web",
1319
1459
  base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1320
1460
  *,
1321
1461
  async_: Literal[False, True] = False,
1322
1462
  **request_kwargs,
1323
- ) -> bytes | Coroutine[Any, Any, bytes]:
1324
- """下载登录二维码图片
1463
+ ) -> dict | Coroutine[Any, Any, dict]:
1464
+ """用授权码获取开放接口应用的 access_token
1325
1465
 
1326
- GET https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode
1466
+ POST https://qrcodeapi.115.com/open/authCodeToToken
1327
1467
 
1328
- :params uid: 二维码的 uid
1468
+ .. admonition:: Reference
1329
1469
 
1330
- :return: 图片的二进制数据(PNG 图片)
1470
+ https://www.yuque.com/115yun/open/okr2cq0wywelscpe#JnDgl
1471
+
1472
+ :payload:
1473
+ - client_id: int | str 💡 AppID
1474
+ - client_secret: str 💡 AppSecret
1475
+ - code: str 💡 授权码,/open/authCodeToToken 重定向地址里面
1476
+ - redirect_uri: str 💡 与 /open/authCodeToToken 传的 redirect_uri 一致,可用于验证以防 MITM 和 CSRF
1477
+ - grant_type: str = "authorization_code" 💡 授权类型,固定为 authorization_code,表示授权码类型
1331
1478
  """
1332
- api = complete_api(f"/api/1.0/{app}/1.0/qrcode", base_url=base_url)
1333
- if isinstance(payload, str):
1334
- payload = {"uid": payload}
1335
- request_kwargs.setdefault("parse", False)
1479
+ api = complete_api("/open/authCodeToToken", base_url=base_url)
1480
+ payload = {"grant_type": "authorization_code", **payload}
1481
+ request_kwargs.setdefault("parse", default_parse)
1336
1482
  if request is None:
1337
- return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
1483
+ return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
1338
1484
  else:
1339
- return request(url=api, params=payload, **request_kwargs)
1485
+ return request(url=api, method="POST", data=payload, **request_kwargs)
1340
1486
 
1341
1487
  @overload
1342
1488
  @staticmethod
1343
- def login_qrcode_access_token_open(
1489
+ def login_qrcode(
1344
1490
  payload: str | dict,
1345
1491
  /,
1346
1492
  request: None | Callable = None,
1493
+ app: str = "web",
1347
1494
  base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1348
1495
  *,
1349
1496
  async_: Literal[False] = False,
1350
1497
  **request_kwargs,
1351
- ) -> dict:
1498
+ ) -> bytes:
1352
1499
  ...
1353
1500
  @overload
1354
1501
  @staticmethod
1355
- def login_qrcode_access_token_open(
1502
+ def login_qrcode(
1356
1503
  payload: str | dict,
1357
1504
  /,
1358
1505
  request: None | Callable = None,
1506
+ app: str = "web",
1359
1507
  base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1360
1508
  *,
1361
1509
  async_: Literal[True],
1362
1510
  **request_kwargs,
1363
- ) -> Coroutine[Any, Any, dict]:
1511
+ ) -> Coroutine[Any, Any, bytes]:
1364
1512
  ...
1365
1513
  @staticmethod
1366
- def login_qrcode_access_token_open(
1514
+ def login_qrcode(
1367
1515
  payload: str | dict,
1368
1516
  /,
1369
1517
  request: None | Callable = None,
1518
+ app: str = "web",
1370
1519
  base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1371
1520
  *,
1372
1521
  async_: Literal[False, True] = False,
1373
1522
  **request_kwargs,
1374
- ) -> dict | Coroutine[Any, Any, dict]:
1375
- """绑定扫码并获取开放平台应用的 access_token 和 refresh_token
1376
-
1377
- POST https://qrcodeapi.115.com/open/deviceCodeToToken
1523
+ ) -> bytes | Coroutine[Any, Any, bytes]:
1524
+ """下载登录二维码图片
1378
1525
 
1379
- .. admonition:: Reference
1526
+ GET https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode
1380
1527
 
1381
- https://www.yuque.com/115yun/open/shtpzfhewv5nag11#QCCVQ
1528
+ :params uid: 二维码的 uid
1382
1529
 
1383
- :payload:
1384
- - uid: str
1385
- - code_verifier: str = <default> 💡 默认字符串是 64 个 "0"
1530
+ :return: 图片的二进制数据(PNG 图片)
1386
1531
  """
1387
- api = complete_api("/open/deviceCodeToToken", base_url=base_url)
1532
+ api = complete_api(f"/api/1.0/{app}/1.0/qrcode", base_url=base_url)
1388
1533
  if isinstance(payload, str):
1389
- payload = {"uid": payload, "code_verifier": _default_code_verifier}
1390
- request_kwargs.setdefault("parse", default_parse)
1534
+ payload = {"uid": payload}
1535
+ request_kwargs.setdefault("parse", False)
1391
1536
  if request is None:
1392
- return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
1537
+ return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
1393
1538
  else:
1394
- return request(url=api, method="POST", data=payload, **request_kwargs)
1539
+ return request(url=api, params=payload, **request_kwargs)
1395
1540
 
1396
1541
  @overload
1397
1542
  @staticmethod
1398
- def login_qrcode_refresh_token_open(
1543
+ def login_qrcode_access_token_open(
1399
1544
  payload: str | dict,
1400
1545
  /,
1401
1546
  request: None | Callable = None,
@@ -1407,7 +1552,7 @@ class ClientRequestMixin:
1407
1552
  ...
1408
1553
  @overload
1409
1554
  @staticmethod
1410
- def login_qrcode_refresh_token_open(
1555
+ def login_qrcode_access_token_open(
1411
1556
  payload: str | dict,
1412
1557
  /,
1413
1558
  request: None | Callable = None,
@@ -1418,7 +1563,7 @@ class ClientRequestMixin:
1418
1563
  ) -> Coroutine[Any, Any, dict]:
1419
1564
  ...
1420
1565
  @staticmethod
1421
- def login_qrcode_refresh_token_open(
1566
+ def login_qrcode_access_token_open(
1422
1567
  payload: str | dict,
1423
1568
  /,
1424
1569
  request: None | Callable = None,
@@ -1427,20 +1572,21 @@ class ClientRequestMixin:
1427
1572
  async_: Literal[False, True] = False,
1428
1573
  **request_kwargs,
1429
1574
  ) -> dict | Coroutine[Any, Any, dict]:
1430
- """用一个 refresh_token 去获取新的 access_token 和 refresh_token,然后原来的 refresh_token 作废
1575
+ """绑定扫码并获取开放平台应用的 access_token 和 refresh_token
1431
1576
 
1432
- POST https://qrcodeapi.115.com/open/refreshToken
1577
+ POST https://qrcodeapi.115.com/open/deviceCodeToToken
1433
1578
 
1434
1579
  .. admonition:: Reference
1435
1580
 
1436
- https://www.yuque.com/115yun/open/shtpzfhewv5nag11#ve54x
1581
+ https://www.yuque.com/115yun/open/shtpzfhewv5nag11#QCCVQ
1437
1582
 
1438
1583
  :payload:
1439
- - refresh_token: str
1584
+ - uid: str
1585
+ - code_verifier: str = <default> 💡 默认字符串是 64 个 "0"
1440
1586
  """
1441
- api = complete_api("/open/refreshToken", base_url=base_url)
1587
+ api = complete_api("/open/deviceCodeToToken", base_url=base_url)
1442
1588
  if isinstance(payload, str):
1443
- payload = {"refresh_token": payload}
1589
+ payload = {"uid": payload, "code_verifier": _default_code_verifier}
1444
1590
  request_kwargs.setdefault("parse", default_parse)
1445
1591
  if request is None:
1446
1592
  return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -1766,7 +1912,7 @@ class ClientRequestMixin:
1766
1912
  async_: Literal[False, True] = False,
1767
1913
  **request_kwargs,
1768
1914
  ) -> dict | Coroutine[Any, Any, dict]:
1769
- """获取开放平台的登录二维码,扫码可用
1915
+ """获取开放平台的登录二维码,扫码可用,采用 PKCE (Proof Key for Code Exchange)
1770
1916
 
1771
1917
  POST https://qrcodeapi.115.com/open/authDeviceCode
1772
1918
 
@@ -1806,6 +1952,62 @@ class ClientRequestMixin:
1806
1952
  else:
1807
1953
  return request(url=api, method="POST", data=payload, **request_kwargs)
1808
1954
 
1955
+ @overload
1956
+ @staticmethod
1957
+ def login_refresh_token_open(
1958
+ payload: str | dict,
1959
+ /,
1960
+ request: None | Callable = None,
1961
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1962
+ *,
1963
+ async_: Literal[False] = False,
1964
+ **request_kwargs,
1965
+ ) -> dict:
1966
+ ...
1967
+ @overload
1968
+ @staticmethod
1969
+ def login_refresh_token_open(
1970
+ payload: str | dict,
1971
+ /,
1972
+ request: None | Callable = None,
1973
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1974
+ *,
1975
+ async_: Literal[True],
1976
+ **request_kwargs,
1977
+ ) -> Coroutine[Any, Any, dict]:
1978
+ ...
1979
+ @staticmethod
1980
+ def login_refresh_token_open(
1981
+ payload: str | dict,
1982
+ /,
1983
+ request: None | Callable = None,
1984
+ base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
1985
+ *,
1986
+ async_: Literal[False, True] = False,
1987
+ **request_kwargs,
1988
+ ) -> dict | Coroutine[Any, Any, dict]:
1989
+ """用一个 refresh_token 去获取新的 access_token 和 refresh_token,然后原来的 refresh_token 作废
1990
+
1991
+ POST https://qrcodeapi.115.com/open/refreshToken
1992
+
1993
+ .. admonition:: Reference
1994
+
1995
+ https://www.yuque.com/115yun/open/shtpzfhewv5nag11#ve54x
1996
+
1997
+ https://www.yuque.com/115yun/open/opnx8yezo4at2be6
1998
+
1999
+ :payload:
2000
+ - refresh_token: str
2001
+ """
2002
+ api = complete_api("/open/refreshToken", base_url=base_url)
2003
+ if isinstance(payload, str):
2004
+ payload = {"refresh_token": payload}
2005
+ request_kwargs.setdefault("parse", default_parse)
2006
+ if request is None:
2007
+ return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
2008
+ else:
2009
+ return request(url=api, method="POST", data=payload, **request_kwargs)
2010
+
1809
2011
  @overload
1810
2012
  @classmethod
1811
2013
  def login_with_qrcode(
@@ -2668,7 +2870,7 @@ class P115OpenClient(ClientRequestMixin):
2668
2870
  app_id_or_refresh_token.startswith("0") or
2669
2871
  app_id_or_refresh_token.strip(digits)
2670
2872
  ):
2671
- resp = yield self.login_qrcode_refresh_token_open(
2873
+ resp = yield self.login_refresh_token_open(
2672
2874
  app_id_or_refresh_token,
2673
2875
  async_=async_,
2674
2876
  **request_kwargs,
@@ -2743,7 +2945,7 @@ class P115OpenClient(ClientRequestMixin):
2743
2945
  """更新 access_token 和 refresh_token (⚠️ 目前是 7200 秒内就要求刷新一次)
2744
2946
  """
2745
2947
  def gen_step():
2746
- resp = yield self.login_qrcode_refresh_token_open(
2948
+ resp = yield self.login_refresh_token_open(
2747
2949
  self.refresh_token,
2748
2950
  async_=async_,
2749
2951
  **request_kwargs,
@@ -4489,10 +4691,6 @@ class P115Client(P115OpenClient):
4489
4691
  """
4490
4692
  self.cookies = None
4491
4693
 
4492
- @locked_cacheproperty
4493
- def request_kwargs(self, /) -> dict:
4494
- return {}
4495
-
4496
4694
  @locked_cacheproperty
4497
4695
  def user_id(self, /) -> int:
4498
4696
  cookie_uid = self.cookies.get("UID")
@@ -13,6 +13,7 @@ __all__ = [
13
13
  "iter_selected_nodes_using_edit", "iter_selected_nodes_using_star_event",
14
14
  "iter_selected_dirs_using_star", "iter_files_with_dirname", "iter_files_with_path",
15
15
  "iter_files_with_path_by_export_dir", "iter_parents_3_level", "iter_dir_nodes",
16
+ "search_for_any_file",
16
17
  ]
17
18
  __doc__ = "这个模块提供了一些和目录信息罗列有关的函数"
18
19
 
@@ -5096,3 +5097,82 @@ def iter_dir_nodes(
5096
5097
  )
5097
5098
  return run_gen_step_iter(gen_step(cid or 0), async_=async_)
5098
5099
 
5100
+
5101
+ @overload
5102
+ def search_for_any_file(
5103
+ client: str | P115Client,
5104
+ cid: int = 0,
5105
+ search_value: str = ".",
5106
+ suffix: str = "",
5107
+ type: int = 99,
5108
+ app: str = "web",
5109
+ *,
5110
+ async_: Literal[False] = False,
5111
+ **request_kwargs,
5112
+ ) -> bool:
5113
+ ...
5114
+ @overload
5115
+ def search_for_any_file(
5116
+ client: str | P115Client,
5117
+ cid: int = 0,
5118
+ search_value: str = ".",
5119
+ suffix: str = "",
5120
+ type: int = 99,
5121
+ app: str = "web",
5122
+ *,
5123
+ async_: Literal[True],
5124
+ **request_kwargs,
5125
+ ) -> Coroutine[Any, Any, bool]:
5126
+ ...
5127
+ def search_for_any_file(
5128
+ client: str | P115Client,
5129
+ cid: int = 0,
5130
+ search_value: str = ".",
5131
+ suffix: str = "",
5132
+ type: int = 99,
5133
+ app: str = "web",
5134
+ *,
5135
+ async_: Literal[False, True] = False,
5136
+ **request_kwargs,
5137
+ ) -> bool | Coroutine[Any, Any, bool]:
5138
+ """搜索以判断是否存在某种文件
5139
+
5140
+ :param client: 115 客户端或 cookies
5141
+ :param cid: 目录 id
5142
+ :param search_value: 搜索关键词,搜索到的文件名必须包含这个字符串
5143
+ :param suffix: 后缀名(优先级高于 type)
5144
+ :param type: 文件类型
5145
+
5146
+ - 1: 文档
5147
+ - 2: 图片
5148
+ - 3: 音频
5149
+ - 4: 视频
5150
+ - 5: 压缩包
5151
+ - 6: 应用
5152
+ - 7: 书籍
5153
+ - 99: 仅文件
5154
+
5155
+ :param app: 使用某个 app (设备)的接口
5156
+ :param async_: 是否异步
5157
+ :param request_kwargs: 其它请求参数
5158
+
5159
+ :return: 是否存在某种文件
5160
+ """
5161
+ if isinstance(client, str):
5162
+ client = P115Client(client, check_for_relogin=True)
5163
+ if not isinstance(client, P115Client) or app == "open":
5164
+ fs_search: Callable = client.fs_search_open
5165
+ elif app in ("", "web", "desktop", "harmony"):
5166
+ fs_search = partial(client.fs_search, app=app)
5167
+ else:
5168
+ fs_search = client.fs_search_app
5169
+ def gen_step():
5170
+ resp = yield fs_search(
5171
+ {"cid": cid, "limit": 1, "search_value": search_value, "suffix": suffix, "type": type},
5172
+ async_=async_,
5173
+ **request_kwargs,
5174
+ )
5175
+ check_response(resp)
5176
+ return bool(resp["data"])
5177
+ return run_gen_step(gen_step, async_=async_)
5178
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: p115client
3
- Version: 0.0.5.10.8
3
+ Version: 0.0.5.10.9
4
4
  Summary: Python 115 webdisk client.
5
5
  Home-page: https://github.com/ChenyangGao/p115client
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
2
  p115client/__init__.py,sha256=1mx7njuAlqcuEWONTjSiiGnXyyNyqOcJyNX1FMHqQ-4,214
3
3
  p115client/_upload.py,sha256=DOckFLU_P7Fl0BNu_0-2od6pPsCnzroYY6zZE5_EMnM,30735
4
- p115client/client.py,sha256=PPKoMONdbGQ7FqH2a00QBNwAEcec6N4D1J6trWfvTdw,740587
4
+ p115client/client.py,sha256=OV6XeA36L8PxpHB-27hhpz_Zv_XMAuMR2GuNDNuy6uo,749032
5
5
  p115client/const.py,sha256=Ks8eYjHYApxlkelVCbSJlpmlCYtbk8fX80jETV72BTY,7754
6
6
  p115client/exception.py,sha256=O4SlfjbtI9GPjzbzJrFaFUZENJFz2qA3VDEKZfdhHbc,3590
7
7
  p115client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -12,7 +12,7 @@ p115client/tool/edit.py,sha256=eRlR1UZMpfGnoetpqSAAx5bPBOEiSlx7HPkpOSs5agk,17739
12
12
  p115client/tool/export_dir.py,sha256=iMnKtnESi8HKvW9WhIvOdEoMXSBpAnhFlGeyKXHpQbE,24545
13
13
  p115client/tool/fs_files.py,sha256=jY57L4nhB9t-2kT_1j3mdiwOdPZKsimN3eN5mUJAqMI,15745
14
14
  p115client/tool/history.py,sha256=2S26BH7uBNfVHlbYFNy-aNOLMcM-HrlR7TLHAZacUJI,7458
15
- p115client/tool/iterdir.py,sha256=IJdhOvccPHpkEQkBwuX5NA5jPEbqZ67VaBuIEP1XMrM,197491
15
+ p115client/tool/iterdir.py,sha256=ei1VddAc96DD0QSSZm7c3ivcCjiP_y6tuSJr5zcpqvg,199695
16
16
  p115client/tool/life.py,sha256=X6Bp0GYKwnZeWlttE2DIVw5iOCXM5QzIkM6Zuj24c5Y,17323
17
17
  p115client/tool/pool.py,sha256=K-ALC-JEwik3DYCr5Eckok15oQnHRe6yznKIuJ9jyyM,13953
18
18
  p115client/tool/request.py,sha256=rjXuQwRganE5Z-4rfgnyPFjE4jzdQSLdIs9s0cIDshU,7043
@@ -20,7 +20,7 @@ p115client/tool/upload.py,sha256=qK1OQYxP-Faq2eMDhc5sBXJiSr8m8EZ_gb0O_iA2TrI,159
20
20
  p115client/tool/util.py,sha256=0o9TrXdoPcljgxDDRdxRon41bq1OjuUYCWsR0XLfmPo,3357
21
21
  p115client/tool/xys.py,sha256=WMTisCyN4eYyv4A14yYOpjud2mBhQvBcUHOp6pJUQeA,10140
22
22
  p115client/type.py,sha256=7kOp98uLaYqcTTCgCrb3DRcl8ukMpn7ibsnVvtw2nG8,6250
23
- p115client-0.0.5.10.8.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
24
- p115client-0.0.5.10.8.dist-info/METADATA,sha256=o0xNuA8shlBXypTl1jBLay2nsh01W_I-zv5HhBhPdpo,8241
25
- p115client-0.0.5.10.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
26
- p115client-0.0.5.10.8.dist-info/RECORD,,
23
+ p115client-0.0.5.10.9.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
24
+ p115client-0.0.5.10.9.dist-info/METADATA,sha256=Ih2qnVhmpJ4wtSYYLN-tU0LK4l4idH5uT9lnUzHUXRk,8241
25
+ p115client-0.0.5.10.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
26
+ p115client-0.0.5.10.9.dist-info/RECORD,,