p115client 0.0.5.10.7__tar.gz → 0.0.5.10.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/PKG-INFO +3 -3
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/client.py +376 -72
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/iterdir.py +80 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/pyproject.toml +3 -3
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/LICENSE +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/__init__.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/_upload.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/const.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/exception.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/py.typed +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/__init__.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/attr.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/download.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/edit.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/export_dir.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/fs_files.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/history.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/life.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/pool.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/request.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/upload.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/util.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/tool/xys.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/p115client/type.py +0 -0
- {p115client-0.0.5.10.7 → p115client-0.0.5.10.9}/readme.md +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: p115client
|
3
|
-
Version: 0.0.5.10.
|
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
|
@@ -23,8 +23,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Dist: aiofile
|
24
24
|
Requires-Dist: ed2k (>=0.0.2.1)
|
25
25
|
Requires-Dist: http_response (>=0.0.2.2)
|
26
|
-
Requires-Dist: httpx
|
27
|
-
Requires-Dist: httpx_request (>=0.1.
|
26
|
+
Requires-Dist: httpx (>=0.28)
|
27
|
+
Requires-Dist: httpx_request (>=0.1.4)
|
28
28
|
Requires-Dist: iter_collect (>=0.0.5.1)
|
29
29
|
Requires-Dist: multidict
|
30
30
|
Requires-Dist: orjson
|
@@ -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
|
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
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
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["
|
845
|
+
attr["parent_id"] = int(info["parent_id"])
|
847
846
|
attr["name"] = info["file_name"]
|
848
847
|
else:
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
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
|
-
|
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
|
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
|
1290
|
-
payload:
|
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
|
-
) ->
|
1440
|
+
) -> dict:
|
1299
1441
|
...
|
1300
1442
|
@overload
|
1301
1443
|
@staticmethod
|
1302
|
-
def
|
1303
|
-
payload:
|
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,
|
1452
|
+
) -> Coroutine[Any, Any, dict]:
|
1312
1453
|
...
|
1313
1454
|
@staticmethod
|
1314
|
-
def
|
1315
|
-
payload:
|
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
|
-
) ->
|
1324
|
-
"""
|
1463
|
+
) -> dict | Coroutine[Any, Any, dict]:
|
1464
|
+
"""用授权码获取开放接口应用的 access_token
|
1325
1465
|
|
1326
|
-
|
1466
|
+
POST https://qrcodeapi.115.com/open/authCodeToToken
|
1327
1467
|
|
1328
|
-
|
1468
|
+
.. admonition:: Reference
|
1329
1469
|
|
1330
|
-
|
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(
|
1333
|
-
|
1334
|
-
|
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,
|
1483
|
+
return get_default_request()(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
|
1338
1484
|
else:
|
1339
|
-
return request(url=api,
|
1485
|
+
return request(url=api, method="POST", data=payload, **request_kwargs)
|
1340
1486
|
|
1341
1487
|
@overload
|
1342
1488
|
@staticmethod
|
1343
|
-
def
|
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
|
-
) ->
|
1498
|
+
) -> bytes:
|
1352
1499
|
...
|
1353
1500
|
@overload
|
1354
1501
|
@staticmethod
|
1355
|
-
def
|
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,
|
1511
|
+
) -> Coroutine[Any, Any, bytes]:
|
1364
1512
|
...
|
1365
1513
|
@staticmethod
|
1366
|
-
def
|
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
|
-
) ->
|
1375
|
-
"""
|
1376
|
-
|
1377
|
-
POST https://qrcodeapi.115.com/open/deviceCodeToToken
|
1523
|
+
) -> bytes | Coroutine[Any, Any, bytes]:
|
1524
|
+
"""下载登录二维码图片
|
1378
1525
|
|
1379
|
-
|
1526
|
+
GET https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode
|
1380
1527
|
|
1381
|
-
|
1528
|
+
:params uid: 二维码的 uid
|
1382
1529
|
|
1383
|
-
:
|
1384
|
-
- uid: str
|
1385
|
-
- code_verifier: str = <default> 💡 默认字符串是 64 个 "0"
|
1530
|
+
:return: 图片的二进制数据(PNG 图片)
|
1386
1531
|
"""
|
1387
|
-
api = complete_api("/
|
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
|
1390
|
-
request_kwargs.setdefault("parse",
|
1534
|
+
payload = {"uid": payload}
|
1535
|
+
request_kwargs.setdefault("parse", False)
|
1391
1536
|
if request is None:
|
1392
|
-
return get_default_request()(url=api,
|
1537
|
+
return get_default_request()(url=api, params=payload, async_=async_, **request_kwargs)
|
1393
1538
|
else:
|
1394
|
-
return request(url=api,
|
1539
|
+
return request(url=api, params=payload, **request_kwargs)
|
1395
1540
|
|
1396
1541
|
@overload
|
1397
1542
|
@staticmethod
|
1398
|
-
def
|
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
|
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
|
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
|
-
"""
|
1575
|
+
"""绑定扫码并获取开放平台应用的 access_token 和 refresh_token
|
1431
1576
|
|
1432
|
-
POST https://qrcodeapi.115.com/open/
|
1577
|
+
POST https://qrcodeapi.115.com/open/deviceCodeToToken
|
1433
1578
|
|
1434
1579
|
.. admonition:: Reference
|
1435
1580
|
|
1436
|
-
https://www.yuque.com/115yun/open/shtpzfhewv5nag11#
|
1581
|
+
https://www.yuque.com/115yun/open/shtpzfhewv5nag11#QCCVQ
|
1437
1582
|
|
1438
1583
|
:payload:
|
1439
|
-
-
|
1584
|
+
- uid: str
|
1585
|
+
- code_verifier: str = <default> 💡 默认字符串是 64 个 "0"
|
1440
1586
|
"""
|
1441
|
-
api = complete_api("/open/
|
1587
|
+
api = complete_api("/open/deviceCodeToToken", base_url=base_url)
|
1442
1588
|
if isinstance(payload, str):
|
1443
|
-
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.
|
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.
|
2948
|
+
resp = yield self.login_refresh_token_open(
|
2747
2949
|
self.refresh_token,
|
2748
2950
|
async_=async_,
|
2749
2951
|
**request_kwargs,
|
@@ -5420,7 +5622,7 @@ class P115Client(P115OpenClient):
|
|
5420
5622
|
return None
|
5421
5623
|
return self.logout_by_ssoent(ssoent, async_=async_, **request_kwargs)
|
5422
5624
|
|
5423
|
-
def
|
5625
|
+
def _request(
|
5424
5626
|
self,
|
5425
5627
|
/,
|
5426
5628
|
url: str,
|
@@ -5662,6 +5864,90 @@ class P115Client(P115OpenClient):
|
|
5662
5864
|
return resp
|
5663
5865
|
return run_gen_step(gen_step, async_=async_)
|
5664
5866
|
|
5867
|
+
def request(
|
5868
|
+
self,
|
5869
|
+
/,
|
5870
|
+
url: str,
|
5871
|
+
method: str = "GET",
|
5872
|
+
params = None,
|
5873
|
+
data = None,
|
5874
|
+
*,
|
5875
|
+
async_: Literal[False, True] = False,
|
5876
|
+
**request_kwargs,
|
5877
|
+
):
|
5878
|
+
"""帮助函数:可执行同步和异步的网络请求
|
5879
|
+
|
5880
|
+
:param url: HTTP 的请求链接
|
5881
|
+
:param method: HTTP 的请求方法
|
5882
|
+
:param params: 查询参数
|
5883
|
+
:param check: 是否用 `check_response` 函数检查返回值
|
5884
|
+
:param ecdh_encrypt: 使用 ecdh 算法进行加密(返回值也要解密)
|
5885
|
+
:param fetch_cert_headers: 调用以获取认证信息头
|
5886
|
+
:param revert_cert_headers: 调用以退还认证信息头
|
5887
|
+
:param async_: 说明 `request` 是同步调用还是异步调用
|
5888
|
+
:param request: HTTP 请求调用,如果为 None,则默认用 httpx 执行请求
|
5889
|
+
如果传入调用,则必须至少能接受以下几个关键词参数:
|
5890
|
+
|
5891
|
+
- url: HTTP 的请求链接
|
5892
|
+
- method: HTTP 的请求方法
|
5893
|
+
- headers: HTTP 的请求头
|
5894
|
+
- data: HTTP 的请求体
|
5895
|
+
- parse: 解析 HTTP 响应的方法,默认会构建一个 Callable,会把响应的字节数据视为 JSON 进行反序列化解析
|
5896
|
+
|
5897
|
+
- 如果为 None,则直接把响应对象返回
|
5898
|
+
- 如果为 ...(Ellipsis),则把响应对象关闭后将其返回
|
5899
|
+
- 如果为 True,则根据响应头来确定把响应得到的字节数据解析成何种格式(反序列化),请求也会被自动关闭
|
5900
|
+
- 如果为 False,则直接返回响应得到的字节数据,请求也会被自动关闭
|
5901
|
+
- 如果为 Callable,则使用此调用来解析数据,接受 1-2 个位置参数,并把解析结果返回给 `request` 的调用者,请求也会被自动关闭
|
5902
|
+
- 如果只接受 1 个位置参数,则把响应对象传给它
|
5903
|
+
- 如果能接受 2 个位置参数,则把响应对象和响应得到的字节数据(响应体)传给它
|
5904
|
+
|
5905
|
+
:param request_kwargs: 其余的请求参数,会被传给 `request`
|
5906
|
+
|
5907
|
+
:return: 直接返回 `request` 执行请求后的返回值
|
5908
|
+
|
5909
|
+
.. note::
|
5910
|
+
`request` 可以由不同的请求库来提供,下面是封装了一些模块
|
5911
|
+
|
5912
|
+
1. `httpx_request <https://pypi.org/project/httpx_request/>`_,由 `httpx <https://pypi.org/project/httpx/>`_ 封装,支持同步和异步调用,本模块默认用的就是这个封装
|
5913
|
+
|
5914
|
+
.. code:: python
|
5915
|
+
|
5916
|
+
from httpx_request import request
|
5917
|
+
|
5918
|
+
2. `python-urlopen <https://pypi.org/project/python-urlopen/>`_,由 `urllib.request.urlopen <https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen>`_ 封装,支持同步调用,性能相对最差
|
5919
|
+
|
5920
|
+
.. code:: python
|
5921
|
+
|
5922
|
+
from urlopen import request
|
5923
|
+
|
5924
|
+
3. `urllib3_request <https://pypi.org/project/urllib3_request/>`_,由 `urllib3 <https://pypi.org/project/urllib3/>`_ 封装,支持同步调用,性能相对较好,推荐使用
|
5925
|
+
|
5926
|
+
.. code:: python
|
5927
|
+
|
5928
|
+
from urllib3_request import request
|
5929
|
+
|
5930
|
+
4. `requests_request <https://pypi.org/project/requests_request/>`_,由 `requests <https://pypi.org/project/requests/>`_ 封装,支持同步调用
|
5931
|
+
|
5932
|
+
.. code:: python
|
5933
|
+
|
5934
|
+
from requests_request import request
|
5935
|
+
|
5936
|
+
5. `aiohttp_client_request <https://pypi.org/project/aiohttp_client_request/>`_,由 `aiohttp <https://pypi.org/project/aiohttp/>`_ 封装,支持异步调用,异步并发能力最强,推荐使用
|
5937
|
+
|
5938
|
+
.. code:: python
|
5939
|
+
|
5940
|
+
from aiohttp_client_request import request
|
5941
|
+
|
5942
|
+
6. `blacksheep_client_request <https://pypi.org/project/blacksheep_client_request/>`_,由 `blacksheep <https://pypi.org/project/blacksheep/>`_ 封装,支持异步调用
|
5943
|
+
|
5944
|
+
.. code:: python
|
5945
|
+
|
5946
|
+
from blacksheep_client_request import request
|
5947
|
+
"""
|
5948
|
+
kwargs = {**self.request_kwargs, **request_kwargs}
|
5949
|
+
return self._request(url, method, params, data, async_=async_, **kwargs)
|
5950
|
+
|
5665
5951
|
########## Activity API ##########
|
5666
5952
|
|
5667
5953
|
@overload
|
@@ -15047,6 +15333,13 @@ class P115Client(P115OpenClient):
|
|
15047
15333
|
async_: Literal[False, True] = False,
|
15048
15334
|
**request_kwargs,
|
15049
15335
|
) -> dict | Coroutine[Any, Any, dict]:
|
15336
|
+
"""获取某个开放应用的授权信息
|
15337
|
+
|
15338
|
+
GET https://qrcodeapi.115.com/app/1.0/web/1.0/user/getAppAuthDetail
|
15339
|
+
|
15340
|
+
:payload:
|
15341
|
+
- auth_id: int | str 💡 授权 id
|
15342
|
+
"""
|
15050
15343
|
api = complete_api(f"/app/1.0/{app}/1.0/user/getAppAuthDetail", base_url=base_url)
|
15051
15344
|
if isinstance(payload, (int, str)):
|
15052
15345
|
payload = {"auth_id": payload}
|
@@ -15083,6 +15376,10 @@ class P115Client(P115OpenClient):
|
|
15083
15376
|
async_: Literal[False, True] = False,
|
15084
15377
|
**request_kwargs,
|
15085
15378
|
) -> dict | Coroutine[Any, Any, dict]:
|
15379
|
+
"""获取所有授权的开放应用的列表
|
15380
|
+
|
15381
|
+
GET https://qrcodeapi.115.com/app/1.0/web/1.0/user/getAppAuthList
|
15382
|
+
"""
|
15086
15383
|
api = complete_api(f"/app/1.0/{app}/1.0/user/getAppAuthList", base_url=base_url)
|
15087
15384
|
return self.request(url=api, async_=async_, **request_kwargs)
|
15088
15385
|
|
@@ -15120,6 +15417,13 @@ class P115Client(P115OpenClient):
|
|
15120
15417
|
async_: Literal[False, True] = False,
|
15121
15418
|
**request_kwargs,
|
15122
15419
|
) -> dict | Coroutine[Any, Any, dict]:
|
15420
|
+
"""取消某个开放应用的授权
|
15421
|
+
|
15422
|
+
GET https://qrcodeapi.115.com/app/1.0/web/1.0/user/deauthApp
|
15423
|
+
|
15424
|
+
:payload:
|
15425
|
+
- auth_id: int | str 💡 授权 id
|
15426
|
+
"""
|
15123
15427
|
api = complete_api(f"/app/1.0/{app}/1.0/user/deauthApp", base_url=base_url)
|
15124
15428
|
if isinstance(payload, (int, str)):
|
15125
15429
|
payload = {"auth_id": payload}
|
@@ -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
|
[tool.poetry]
|
2
2
|
name = "p115client"
|
3
|
-
version = "0.0.5.10.
|
3
|
+
version = "0.0.5.10.9"
|
4
4
|
description = "Python 115 webdisk client."
|
5
5
|
authors = ["ChenyangGao <wosiwujm@gmail.com>"]
|
6
6
|
license = "MIT"
|
@@ -31,8 +31,8 @@ python = "^3.12"
|
|
31
31
|
aiofile = "*"
|
32
32
|
ed2k = ">=0.0.2.1"
|
33
33
|
http_response = ">=0.0.2.2"
|
34
|
-
httpx = "
|
35
|
-
httpx_request = ">=0.1.
|
34
|
+
httpx = ">=0.28"
|
35
|
+
httpx_request = ">=0.1.4"
|
36
36
|
iter_collect = ">=0.0.5.1"
|
37
37
|
multidict = "*"
|
38
38
|
orjson = "*"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|