p123client 0.0.6.8__py3-none-any.whl → 0.0.6.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
p123client/client.py CHANGED
@@ -5,21 +5,25 @@ from __future__ import annotations
5
5
 
6
6
  __all__ = ["check_response", "P123OpenClient", "P123Client"]
7
7
 
8
+ from base64 import b64decode
8
9
  from collections.abc import (
9
10
  AsyncIterable, Awaitable, Buffer, Callable, Coroutine,
10
11
  ItemsView, Iterable, Iterator, Mapping, MutableMapping,
11
12
  )
12
- from errno import EIO, EISDIR, ENOENT
13
+ from contextlib import contextmanager
14
+ from errno import EAUTH, EIO, EISDIR, ENOENT
13
15
  from functools import partial
14
16
  from hashlib import md5
15
17
  from http.cookiejar import CookieJar
16
18
  from inspect import isawaitable
17
19
  from itertools import chain
18
- from os import fsdecode, fstat, PathLike
20
+ from os import fsdecode, fstat, isatty, PathLike
19
21
  from os.path import basename
20
- from re import compile as re_compile
22
+ from pathlib import Path, PurePath
23
+ from re import compile as re_compile, MULTILINE
24
+ from sys import _getframe
21
25
  from tempfile import TemporaryFile
22
- from typing import cast, overload, Any, Literal
26
+ from typing import cast, overload, Any, Final, Literal, Self
23
27
  from uuid import uuid4
24
28
  from warnings import warn
25
29
 
@@ -31,21 +35,24 @@ from filewrap import (
31
35
  copyfileobj, copyfileobj_async, SupportsRead,
32
36
  )
33
37
  from hashtools import file_digest, file_digest_async
34
- from http_request import encode_multipart_data, encode_multipart_data_async, SupportsGeturl
38
+ from http_request import (
39
+ encode_multipart_data, encode_multipart_data_async, SupportsGeturl,
40
+ )
35
41
  from iterutils import run_gen_step
36
42
  from property import locked_cacheproperty
37
43
  from yarl import URL
38
44
 
39
- from .exception import P123OSError, P123BrokenUpload
45
+ from .const import CLIENT_API_METHODS_MAP, CLIENT_METHOD_API_MAP
46
+ from .exception import P123OSError, P123BrokenUpload, P123LoginError, P123AuthenticationError
40
47
 
41
48
 
42
49
  # 默认使用的域名
43
50
  # "https://www.123pan.com"
44
51
  # "https://www.123pan.com/a"
45
52
  # "https://www.123pan.com/b"
46
- DEFAULT_BASE_URL = "https://www.123pan.com/b"
47
- DEFAULT_LOGIN_BASE_URL = "https://login.123pan.com"
48
- DEFAULT_OPEN_BASE_URL = "https://open-api.123pan.com"
53
+ DEFAULT_BASE_URL: Final = "https://www.123pan.com/b"
54
+ DEFAULT_LOGIN_BASE_URL: Final = "https://login.123pan.com"
55
+ DEFAULT_OPEN_BASE_URL: Final = "https://open-api.123pan.com"
49
56
  # 默认的请求函数
50
57
  _httpx_request = None
51
58
 
@@ -147,6 +154,20 @@ def items[K, V](
147
154
  return m
148
155
 
149
156
 
157
+ @contextmanager
158
+ def temp_globals(f_globals: None | dict = None, /, **ns):
159
+ if f_globals is None:
160
+ f_globals = _getframe(2).f_globals
161
+ old_globals = f_globals.copy()
162
+ if ns:
163
+ f_globals.update(ns)
164
+ try:
165
+ yield f_globals
166
+ finally:
167
+ f_globals.clear()
168
+ f_globals.update(old_globals)
169
+
170
+
150
171
  @overload
151
172
  def check_response(resp: dict, /) -> dict:
152
173
  ...
@@ -157,9 +178,16 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
157
178
  """检测 123 的某个接口的响应,如果成功则直接返回,否则根据具体情况抛出一个异常,基本上是 OSError 的实例
158
179
  """
159
180
  def check(resp, /) -> dict:
160
- if not isinstance(resp, dict) or resp.get("code", 0) not in (0, 200):
181
+ if not isinstance(resp, dict):
161
182
  raise P123OSError(EIO, resp)
162
- return resp
183
+ code = resp.get("code", 0)
184
+ if code in (0, 200):
185
+ return resp
186
+ match code:
187
+ case 401:
188
+ raise P123AuthenticationError(EAUTH, resp)
189
+ case _:
190
+ raise P123OSError(EIO, resp)
163
191
  if isawaitable(resp):
164
192
  async def check_await() -> dict:
165
193
  return check(await resp)
@@ -176,17 +204,32 @@ class P123OpenClient:
176
204
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced
177
205
  """
178
206
 
207
+ token_path: None | PurePath = None
208
+
179
209
  def __init__(
180
210
  self, /,
181
- client_id: str = "",
211
+ client_id: str | PathLike = "",
182
212
  client_secret: str = "",
183
- token: str = "",
213
+ token: None | str | PathLike = None,
184
214
  ):
185
- self.client_id = client_id
186
- self.client_secret = client_secret
187
- self.token = token
188
- if client_id and client_secret:
189
- self.login_open()
215
+ if isinstance(client_id, PathLike):
216
+ token = client_id
217
+ else:
218
+ self.client_id = client_id
219
+ self.client_secret = client_secret
220
+ if token is None:
221
+ if client_id and client_secret:
222
+ self.login_open()
223
+ elif isinstance(token, str):
224
+ self.token = token.removeprefix("Bearer ")
225
+ else:
226
+ if isinstance(token, PurePath) and hasattr(token, "open"):
227
+ self.token_path = token
228
+ else:
229
+ self.token_path = Path(fsdecode(token))
230
+ self._read_token()
231
+ if not self.token and client_id and client_secret:
232
+ self.login_open()
190
233
 
191
234
  def __del__(self, /):
192
235
  self.close()
@@ -258,20 +301,62 @@ class P123OpenClient:
258
301
 
259
302
  @property
260
303
  def token(self, /) -> str:
261
- return self._token
304
+ return self.__dict__.get("token", "")
262
305
 
263
306
  @token.setter
264
- def token(self, value: str, /):
265
- self._token = value
266
- if value:
267
- self.headers["authorization"] = f"Bearer {self._token}"
268
- else:
269
- self.headers.pop("authorization", None)
307
+ def token(self, token: str, /):
308
+ if token != self.token:
309
+ self._write_token(token)
310
+ ns = self.__dict__
311
+ ns["token"] = token
312
+ if token:
313
+ self.headers["authorization"] = f"Bearer {token}"
314
+ else:
315
+ self.headers.pop("authorization", None)
316
+ ns.pop("token_user_info", None)
317
+ ns.pop("user_id", None)
270
318
 
271
319
  @token.deleter
272
320
  def token(self, /):
273
321
  self.token = ""
274
322
 
323
+ @locked_cacheproperty
324
+ def token_user_info(self, /) -> dict:
325
+ from orjson import loads
326
+ return loads(b64decode(self.token.split(".", 2)[1] + "=="))
327
+
328
+ @locked_cacheproperty
329
+ def user_id(self, /) -> dict:
330
+ return self.token_user_info["id"]
331
+
332
+ def _read_token(
333
+ self,
334
+ /,
335
+ encoding: str = "latin-1",
336
+ ) -> None | str:
337
+ if token_path := self.token_path:
338
+ try:
339
+ with token_path.open("rb") as f: # type: ignore
340
+ token = str(f.read().strip(), encoding)
341
+ self.token = token.removeprefix("Bearer ")
342
+ return token
343
+ except OSError:
344
+ pass
345
+ return self.token
346
+
347
+ def _write_token(
348
+ self,
349
+ token: None | str = None,
350
+ /,
351
+ encoding: str = "latin-1",
352
+ ):
353
+ if token_path := self.token_path:
354
+ if token is None:
355
+ token = self.token
356
+ token_bytes = bytes(token, encoding)
357
+ with token_path.open("wb") as f: # type: ignore
358
+ f.write(token_bytes)
359
+
275
360
  def close(self, /) -> None:
276
361
  """删除 session 和 async_session 属性,如果它们未被引用,则应该会被自动清理
277
362
  """
@@ -433,13 +518,16 @@ class P123OpenClient:
433
518
  - clientID: str 💡 应用标识,创建应用时分配的 appId
434
519
  - clientSecret: str 💡 应用密钥,创建应用时分配的 secretId
435
520
  """
436
- request_kwargs["url"] = complete_url("/api/v1/access_token", base_url)
437
- request_kwargs.setdefault("method", "POST")
438
521
  request_kwargs.setdefault("parse", default_parse)
439
522
  if request is None:
440
523
  request = get_default_request()
441
524
  request_kwargs["async_"] = async_
442
- return request(json=payload, **request_kwargs)
525
+ return request(
526
+ url=complete_url("/api/v1/access_token", base_url),
527
+ method="POST",
528
+ json=payload,
529
+ **request_kwargs,
530
+ )
443
531
 
444
532
  @overload
445
533
  @staticmethod
@@ -490,13 +578,16 @@ class P123OpenClient:
490
578
  - scope: str = "user:base,file:all:read,file:all:write" 💡 权限
491
579
  - state: str = "" 💡 自定义参数,任意取值
492
580
  """
493
- request_kwargs["url"] = complete_url("/auth", base_url)
494
581
  request_kwargs.setdefault("parse", default_parse)
495
582
  payload = dict_to_lower_merge(payload, scope="user:base,file:all:read,file:all:write")
496
583
  if request is None:
497
584
  request = get_default_request()
498
585
  request_kwargs["async_"] = async_
499
- return request(params=payload, **request_kwargs)
586
+ return request(
587
+ url=complete_url("/auth", base_url),
588
+ params=payload,
589
+ **request_kwargs,
590
+ )
500
591
 
501
592
  @overload
502
593
  @staticmethod
@@ -556,8 +647,6 @@ class P123OpenClient:
556
647
  - redirect_uri: str = <default> 💡 应用注册的回调地址,`grant_type` 为 "authorization_code" 时必携带
557
648
  - refresh_token: str = <default> 💡 刷新 token,单次请求有效
558
649
  """
559
- request_kwargs["url"] = complete_url("/api/v1/oauth2/access_token", base_url)
560
- request_kwargs.setdefault("method", "POST")
561
650
  request_kwargs.setdefault("parse", default_parse)
562
651
  payload = dict_to_lower(payload)
563
652
  if not payload.get("grant_type"):
@@ -568,7 +657,163 @@ class P123OpenClient:
568
657
  if request is None:
569
658
  request = get_default_request()
570
659
  request_kwargs["async_"] = async_
571
- return request(params=payload, **request_kwargs)
660
+ return request(
661
+ url=complete_url("/api/v1/oauth2/access_token", base_url),
662
+ method="POST",
663
+ params=payload,
664
+ **request_kwargs,
665
+ )
666
+
667
+ ########## Developer API ##########
668
+
669
+ @overload
670
+ def developer_config_forbide_ip_list(
671
+ self,
672
+ /,
673
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
674
+ *,
675
+ async_: Literal[False] = False,
676
+ **request_kwargs,
677
+ ) -> dict:
678
+ ...
679
+ @overload
680
+ def developer_config_forbide_ip_list(
681
+ self,
682
+ /,
683
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
684
+ *,
685
+ async_: Literal[True],
686
+ **request_kwargs,
687
+ ) -> Coroutine[Any, Any, dict]:
688
+ ...
689
+ def developer_config_forbide_ip_list(
690
+ self,
691
+ /,
692
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
693
+ *,
694
+ async_: Literal[False, True] = False,
695
+ **request_kwargs,
696
+ ) -> dict | Coroutine[Any, Any, dict]:
697
+ """ip黑名单列表
698
+
699
+ GET https://open-api.123pan.com/api/v1/developer/config/forbide-ip/list
700
+
701
+ .. admonition:: Reference
702
+ /API列表/直链/IP黑名单配置/ip黑名单列表
703
+
704
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/mxldrm9d5gpw5h2d
705
+
706
+ .. caution::
707
+ 获取用户配置的黑名单
708
+ """
709
+ api = complete_url("/api/v1/developer/config/forbide-ip/list", base_url)
710
+ return self.request(api, async_=async_, **request_kwargs)
711
+
712
+ @overload
713
+ def developer_config_forbide_ip_switch(
714
+ self,
715
+ payload: dict | Literal[1, 2] = 1,
716
+ /,
717
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
718
+ *,
719
+ async_: Literal[False] = False,
720
+ **request_kwargs,
721
+ ) -> dict:
722
+ ...
723
+ @overload
724
+ def developer_config_forbide_ip_switch(
725
+ self,
726
+ payload: dict | Literal[1, 2] = 1,
727
+ /,
728
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
729
+ *,
730
+ async_: Literal[True],
731
+ **request_kwargs,
732
+ ) -> Coroutine[Any, Any, dict]:
733
+ ...
734
+ def developer_config_forbide_ip_switch(
735
+ self,
736
+ payload: dict | Literal[1, 2] = 1,
737
+ /,
738
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
739
+ *,
740
+ async_: Literal[False, True] = False,
741
+ **request_kwargs,
742
+ ) -> dict | Coroutine[Any, Any, dict]:
743
+ """开启关闭ip黑名单
744
+
745
+ POST https://open-api.123pan.com/api/v1/developer/config/forbide-ip/switch
746
+
747
+ .. admonition:: Reference
748
+ /API列表/直链/IP黑名单配置/开启关闭ip黑名单
749
+
750
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xwx77dbzrkxquuxm
751
+
752
+ .. caution::
753
+ 此接口需要开通开发者权益
754
+
755
+ :payload:
756
+ - Status: 1 | 2 = 1 💡 状态:1:启用 2:禁用
757
+ """
758
+ api = complete_url("/api/v1/developer/config/forbide-ip/switch", base_url)
759
+ if not isinstance(payload, dict):
760
+ payload = {"Status": payload}
761
+ return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
762
+
763
+ @overload
764
+ def developer_config_forbide_ip_update(
765
+ self,
766
+ payload: dict | Iterable[str],
767
+ /,
768
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
769
+ *,
770
+ async_: Literal[False] = False,
771
+ **request_kwargs,
772
+ ) -> dict:
773
+ ...
774
+ @overload
775
+ def developer_config_forbide_ip_update(
776
+ self,
777
+ payload: dict | Iterable[str],
778
+ /,
779
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
780
+ *,
781
+ async_: Literal[True],
782
+ **request_kwargs,
783
+ ) -> Coroutine[Any, Any, dict]:
784
+ ...
785
+ def developer_config_forbide_ip_update(
786
+ self,
787
+ payload: dict | Iterable[str],
788
+ /,
789
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
790
+ *,
791
+ async_: Literal[False, True] = False,
792
+ **request_kwargs,
793
+ ) -> dict | Coroutine[Any, Any, dict]:
794
+ """更新ip黑名单列表
795
+
796
+ POST https://open-api.123pan.com/api/v1/developer/config/forbide-ip/update
797
+
798
+ .. admonition:: Reference
799
+ /API列表/直链/IP黑名单配置/更新ip黑名单列表
800
+
801
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tt3s54slh87q8wuh
802
+
803
+ .. caution::
804
+ 此接口需要开通开发者权益
805
+
806
+ :payload:
807
+ - IpList: list[str] 💡 IP 地址列表,最多 500 个 IPv4 地址
808
+ """
809
+ api = complete_url("/api/v1/developer/config/forbide-ip/update", base_url)
810
+ if not isinstance(payload, dict):
811
+ if not isinstance(payload, (list, tuple)):
812
+ payload = list(payload)
813
+ payload = {"IpList": payload}
814
+ return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
815
+
816
+ ########## Direct Link API ##########
572
817
 
573
818
  @overload
574
819
  def dlink_disable(
@@ -706,6 +951,9 @@ class P123OpenClient:
706
951
 
707
952
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/agmqpmu0dm0iogc9
708
953
 
954
+ .. caution::
955
+ 此接口需要开通开发者权益,并且仅限查询近 3 天的日志数据
956
+
709
957
  :payload:
710
958
  - pageNum: int 💡 第几页
711
959
  - pageSize: int = 100 💡 分页大小
@@ -781,6 +1029,65 @@ class P123OpenClient:
781
1029
  payload = {"fileID": payload}
782
1030
  return self.request(api, params=payload, async_=async_, **request_kwargs)
783
1031
 
1032
+ @overload
1033
+ def dlink_offline_log(
1034
+ self,
1035
+ payload: dict | int = 1,
1036
+ /,
1037
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1038
+ *,
1039
+ async_: Literal[False] = False,
1040
+ **request_kwargs,
1041
+ ) -> dict:
1042
+ ...
1043
+ @overload
1044
+ def dlink_offline_log(
1045
+ self,
1046
+ payload: dict | int = 1,
1047
+ /,
1048
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1049
+ *,
1050
+ async_: Literal[True],
1051
+ **request_kwargs,
1052
+ ) -> Coroutine[Any, Any, dict]:
1053
+ ...
1054
+ def dlink_offline_log(
1055
+ self,
1056
+ payload: dict | int = 1,
1057
+ /,
1058
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1059
+ *,
1060
+ async_: Literal[False, True] = False,
1061
+ **request_kwargs,
1062
+ ) -> dict | Coroutine[Any, Any, dict]:
1063
+ """获取直链离线日志
1064
+
1065
+ GET https://open-api.123pan.com/api/v1/direct-link/offline/logs
1066
+
1067
+ .. admonition:: Reference
1068
+ /API列表/直链/获取直链离线日志
1069
+
1070
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/yz4bdynw9yx5erqb
1071
+
1072
+ .. caution::
1073
+ 此接口需要开通开发者权益,并且仅限查询近30天的日志数据
1074
+
1075
+ :payload:
1076
+ - pageNum: int 💡 第几页
1077
+ - pageSize: int = 100 💡 分页大小
1078
+ - startHour: str = "0001010100" 💡 开始时间,格式:YYYYMMDDhh
1079
+ - endHour: str. = "9999123123" 💡 结束时间,格式:YYYYMMDDhh
1080
+ """
1081
+ api = complete_url("/api/v1/direct-link/offline/logs", base_url)
1082
+ if not isinstance(payload, dict):
1083
+ payload = {"pageNum": payload}
1084
+ payload = dict_to_lower_merge(payload, {
1085
+ "pageSize": 100,
1086
+ "startTime": "0001010100",
1087
+ "endTime": "9999123123",
1088
+ })
1089
+ return self.request(api, params=payload, async_=async_, **request_kwargs)
1090
+
784
1091
  @overload
785
1092
  def dlink_transcode(
786
1093
  self,
@@ -934,6 +1241,8 @@ class P123OpenClient:
934
1241
  payload = {"fileID": payload}
935
1242
  return self.request(api, params=payload, async_=async_, **request_kwargs)
936
1243
 
1244
+ ########## Download API ##########
1245
+
937
1246
  @overload
938
1247
  def download_info(
939
1248
  self,
@@ -983,6 +1292,8 @@ class P123OpenClient:
983
1292
  payload = {"fileId": payload}
984
1293
  return self.request(api, params=payload, async_=async_, **request_kwargs)
985
1294
 
1295
+ ########## File System API ##########
1296
+
986
1297
  @overload
987
1298
  def fs_delete(
988
1299
  self,
@@ -1078,6 +1389,9 @@ class P123OpenClient:
1078
1389
 
1079
1390
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/owapsz373dzwiqbp
1080
1391
 
1392
+ .. note::
1393
+ 支持查询单文件夹包含文件大小
1394
+
1081
1395
  :payload:
1082
1396
  - fileID: int 💡 文件 id
1083
1397
  """
@@ -1188,11 +1502,14 @@ class P123OpenClient:
1188
1502
 
1189
1503
  .. note::
1190
1504
  如果返回信息中,"lastFileId" 字段的值为 "-1",代表最后一页(无需再翻页查询)。
1191
- 其它则代表下一页开始的文件 id,携带到请求参数中,可查询下一页
1505
+ 其它则代表下一页开始的文件 id,携带到请求参数中,可查询下一页。
1506
+
1507
+ .. caution::
1508
+ 此接口查询结果包含回收站的文件,需自行根据字段 `trashed` 判断处理
1192
1509
 
1193
1510
  :payload:
1194
1511
  - businessType: int = <default> 💡 业务类型:2:转码空间
1195
- - category: int = <default> 💡 分类代码:0:未知 1:音频 2:视频 3:图片
1512
+ - category: int = <default> 💡 分类代码:0:未知 1:音频 2:视频 3:图片 4:音频 5:其它
1196
1513
  - lastFileId: int = <default> 💡 上一页的最后一条记录的 FileID,翻页查询时需要填写
1197
1514
  - limit: int = 100 💡 分页大小,最多 100
1198
1515
  - parentFileId: int | str = 0 💡 父目录 id,根目录是 0
@@ -1215,6 +1532,8 @@ class P123OpenClient:
1215
1532
  })
1216
1533
  return self.request(api, params=payload, async_=async_, **request_kwargs)
1217
1534
 
1535
+ fs_list_v2 = fs_list
1536
+
1218
1537
  @overload
1219
1538
  def fs_list_v1(
1220
1539
  self,
@@ -1260,8 +1579,8 @@ class P123OpenClient:
1260
1579
 
1261
1580
  :payload:
1262
1581
  - limit: int = 100 💡 分页大小,最多 100
1263
- - orderBy: str = "file_id" 💡 排序依据
1264
-
1582
+ - orderBy: str = "file_name" 💡 排序依据
1583
+
1265
1584
  - "file_id": 文件 id
1266
1585
  - "file_name": 文件名
1267
1586
  - "size": 文件大小
@@ -1285,7 +1604,7 @@ class P123OpenClient:
1285
1604
  payload = {"parentFileId": payload}
1286
1605
  payload = dict_to_lower_merge(payload, {
1287
1606
  "limit": 100,
1288
- "orderBy": "file_id",
1607
+ "orderBy": "file_name",
1289
1608
  "orderDirection": "asc",
1290
1609
  "page": 1,
1291
1610
  "parentFileId": 0,
@@ -1615,6 +1934,8 @@ class P123OpenClient:
1615
1934
  payload = {"fileIDs": payload}
1616
1935
  return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
1617
1936
 
1937
+ ########## Offline Download API ##########
1938
+
1618
1939
  @overload
1619
1940
  def offline_download(
1620
1941
  self,
@@ -1724,6 +2045,8 @@ class P123OpenClient:
1724
2045
  payload = {"taskID": payload}
1725
2046
  return self.request(api, params=payload, async_=async_, **request_kwargs)
1726
2047
 
2048
+ ########## Oss API ##########
2049
+
1727
2050
  @overload
1728
2051
  def oss_copy(
1729
2052
  self,
@@ -2862,6 +3185,8 @@ class P123OpenClient:
2862
3185
  }) from e
2863
3186
  return run_gen_step(gen_step, async_)
2864
3187
 
3188
+ ########## Share API ##########
3189
+
2865
3190
  @overload
2866
3191
  def share_create(
2867
3192
  self,
@@ -3070,6 +3395,8 @@ class P123OpenClient:
3070
3395
  payload = {"limit": payload}
3071
3396
  return self.request(api, params=payload, async_=async_, **request_kwargs)
3072
3397
 
3398
+ ########## Transcode API ##########
3399
+
3073
3400
  @overload
3074
3401
  def transcode_delete(
3075
3402
  self,
@@ -3616,6 +3943,8 @@ class P123OpenClient:
3616
3943
  api = complete_url("/api/v1/transcode/video", base_url)
3617
3944
  return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
3618
3945
 
3946
+ ########## Upload API ##########
3947
+
3619
3948
  @overload
3620
3949
  def upload_create(
3621
3950
  self,
@@ -4218,6 +4547,8 @@ class P123OpenClient:
4218
4547
  }) from e
4219
4548
  return run_gen_step(gen_step, async_)
4220
4549
 
4550
+ ########## User API ##########
4551
+
4221
4552
  @overload
4222
4553
  def user_info(
4223
4554
  self,
@@ -4272,6 +4603,8 @@ class P123OpenClient:
4272
4603
  api = complete_url("/api/v1/user/info", base_url)
4273
4604
  return self.request(api, async_=async_, **request_kwargs)
4274
4605
 
4606
+ ########## API Aliases ##########
4607
+
4275
4608
  login_open = login
4276
4609
  login_access_token_open = login_access_token
4277
4610
  login_auth_open = login_auth
@@ -4288,6 +4621,7 @@ class P123OpenClient:
4288
4621
  fs_detail_open = fs_detail
4289
4622
  fs_info_open = fs_info
4290
4623
  fs_list_open = fs_list
4624
+ fs_list_v2_open = fs_list_v2
4291
4625
  fs_list_v1_open = fs_list_v1
4292
4626
  fs_mkdir_open = fs_mkdir
4293
4627
  fs_move_open = fs_move
@@ -4338,19 +4672,36 @@ class P123OpenClient:
4338
4672
 
4339
4673
 
4340
4674
  class P123Client(P123OpenClient):
4675
+ """123 的客户端对象
4341
4676
 
4677
+ :param passport: 手机号或邮箱
4678
+ :param password: 密码
4679
+ :param token: 123 的访问令牌
4680
+ """
4342
4681
  def __init__(
4343
4682
  self,
4344
4683
  /,
4345
- passport: int | str = "",
4684
+ passport: int | str | PathLike = "",
4346
4685
  password: str = "",
4347
- token: str = "",
4686
+ token: None | str | PathLike = None,
4348
4687
  ):
4349
- self.passport = passport
4350
- self.password = password
4351
- self.token = token
4352
- if passport and password:
4688
+ if isinstance(passport, PathLike):
4689
+ token = passport
4690
+ else:
4691
+ self.passport = passport
4692
+ self.password = password
4693
+ if token is None:
4353
4694
  self.login()
4695
+ elif isinstance(token, str):
4696
+ self.token = token.removeprefix("Bearer ")
4697
+ else:
4698
+ if isinstance(token, PurePath) and hasattr(token, "open"):
4699
+ self.token_path = token
4700
+ else:
4701
+ self.token_path = Path(fsdecode(token))
4702
+ self._read_token()
4703
+ if not self.token:
4704
+ self.login()
4354
4705
 
4355
4706
  @overload # type: ignore
4356
4707
  def login(
@@ -4409,193 +4760,236 @@ class P123Client(P123OpenClient):
4409
4760
  else:
4410
4761
  password = self.password
4411
4762
  def gen_step():
4412
- resp = yield self.user_login(
4413
- {"passport": passport, "password": password, "remember": remember},
4414
- base_url=base_url,
4415
- async_=async_,
4416
- **request_kwargs,
4417
- )
4418
- check_response(resp)
4419
- self.token = resp["data"]["token"]
4420
- return resp
4763
+ if passport and password:
4764
+ resp = yield self.login_passport(
4765
+ {"passport": passport, "password": password, "remember": remember},
4766
+ base_url=base_url,
4767
+ async_=async_,
4768
+ **request_kwargs,
4769
+ )
4770
+ check_response(resp)
4771
+ self.token = resp["data"]["token"]
4772
+ return resp
4773
+ else:
4774
+ resp = yield self.login_with_qrcode(
4775
+ base_url=base_url,
4776
+ async_=async_,
4777
+ **request_kwargs,
4778
+ )
4779
+ self.token = resp["data"]["token"]
4780
+ return resp
4421
4781
  return run_gen_step(gen_step, async_)
4422
4782
 
4423
4783
  @overload
4424
- @staticmethod
4425
- def app_dydomain(
4426
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4427
- request: None | Callable = None,
4784
+ def login_another(
4785
+ self,
4786
+ /,
4787
+ replace: bool | Self = False,
4788
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4428
4789
  *,
4429
4790
  async_: Literal[False] = False,
4430
4791
  **request_kwargs,
4431
- ) -> dict:
4792
+ ) -> Self:
4432
4793
  ...
4433
4794
  @overload
4434
- @staticmethod
4435
- def app_dydomain(
4436
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4437
- request: None | Callable = None,
4795
+ def login_another(
4796
+ self,
4797
+ /,
4798
+ replace: bool | Self = False,
4799
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4438
4800
  *,
4439
4801
  async_: Literal[True],
4440
4802
  **request_kwargs,
4441
- ) -> Coroutine[Any, Any, dict]:
4803
+ ) -> Coroutine[Any, Any, Self]:
4442
4804
  ...
4443
- @staticmethod
4444
- def app_dydomain(
4445
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4446
- request: None | Callable = None,
4805
+ def login_another(
4806
+ self,
4807
+ /,
4808
+ replace: bool | Self = False,
4809
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4447
4810
  *,
4448
4811
  async_: Literal[False, True] = False,
4449
4812
  **request_kwargs,
4450
- ) -> dict | Coroutine[Any, Any, dict]:
4451
- """获取 123 网盘的各种域名
4813
+ ) -> Self | Coroutine[Any, Any, Self]:
4814
+ """再执行一次登录
4452
4815
 
4453
- GET https://www.123pan.com/api/dydomain
4816
+ :param replace: 替换某个 client 对象的 token
4817
+
4818
+ - 如果为 P123Client, 则更新到此对象
4819
+ - 如果为 True,则更新到 `self`
4820
+ - 如果为 False,否则返回新的 `P123Client` 对象
4821
+
4822
+ :param base_url: 接口的基地址
4823
+ :param async_: 是否异步
4824
+ :param request_kwargs: 其它请求参数
4825
+
4826
+ :return: 客户端实例
4454
4827
  """
4455
- request_kwargs["url"] = complete_url("/api/dydomain", base_url)
4456
- request_kwargs.setdefault("parse", default_parse)
4457
- if request is None:
4458
- request = get_default_request()
4459
- request_kwargs["async_"] = async_
4460
- return request(**request_kwargs)
4828
+ def gen_step():
4829
+ resp = yield self.login_qrcode_auto(
4830
+ base_url=base_url,
4831
+ async_=async_,
4832
+ **request_kwargs,
4833
+ )
4834
+ check_response(resp)
4835
+ if resp["code"] != 200:
4836
+ raise P123LoginError(EAUTH, resp)
4837
+ token = resp["data"]["token"]
4838
+ if replace is False:
4839
+ return type(self)(passport=self.passport, password=self.password, token=token)
4840
+ elif replace is True:
4841
+ inst = self
4842
+ else:
4843
+ inst = replace
4844
+ inst.token = token
4845
+ return inst
4846
+ return run_gen_step(gen_step, async_)
4461
4847
 
4462
4848
  @overload
4463
- @staticmethod
4464
- def app_server_time(
4465
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4466
- request: None | Callable = None,
4849
+ def login_qrcode_auto(
4850
+ self,
4851
+ /,
4852
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4467
4853
  *,
4468
4854
  async_: Literal[False] = False,
4469
4855
  **request_kwargs,
4470
4856
  ) -> dict:
4471
4857
  ...
4472
4858
  @overload
4473
- @staticmethod
4474
- def app_server_time(
4475
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4476
- request: None | Callable = None,
4859
+ def login_qrcode_auto(
4860
+ self,
4861
+ /,
4862
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4477
4863
  *,
4478
4864
  async_: Literal[True],
4479
4865
  **request_kwargs,
4480
4866
  ) -> Coroutine[Any, Any, dict]:
4481
4867
  ...
4482
- @staticmethod
4483
- def app_server_time(
4484
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4485
- request: None | Callable = None,
4868
+ def login_qrcode_auto(
4869
+ self,
4870
+ /,
4871
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4486
4872
  *,
4487
4873
  async_: Literal[False, True] = False,
4488
4874
  **request_kwargs,
4489
4875
  ) -> dict | Coroutine[Any, Any, dict]:
4490
- """获取 123 网盘的服务器时间戳
4876
+ """执行一次自动扫码,但并不因此更新 `self.token`
4491
4877
 
4492
- GET https://www.123pan.com/api/get/server/time
4878
+ :param base_url: 接口的基地址
4879
+ :param async_: 是否异步
4880
+ :param request_kwargs: 其它请求参数
4881
+
4882
+ :return: 接口响应
4493
4883
  """
4494
- request_kwargs["url"] = complete_url("/api/get/server/time", base_url)
4495
- request_kwargs.setdefault("parse", default_parse)
4496
- if request is None:
4497
- request = get_default_request()
4498
- request_kwargs["async_"] = async_
4499
- return request(**request_kwargs)
4884
+ def gen_step():
4885
+ resp = yield self.login_qrcode_generate(
4886
+ base_url=base_url,
4887
+ async_=async_,
4888
+ **request_kwargs,
4889
+ )
4890
+ check_response(resp)
4891
+ uniID = resp["data"]["uniID"]
4892
+ resp = yield self.login_qrcode_confirm(
4893
+ uniID,
4894
+ base_url=base_url,
4895
+ async_=async_,
4896
+ **request_kwargs,
4897
+ )
4898
+ check_response(resp)
4899
+ resp = yield self.login_qrcode_result(
4900
+ uniID,
4901
+ base_url=base_url,
4902
+ async_=async_,
4903
+ **request_kwargs,
4904
+ )
4905
+ return resp
4906
+ return run_gen_step(gen_step, async_)
4500
4907
 
4501
4908
  @overload
4502
- def download_info(
4503
- self,
4504
- payload: dict | int | str,
4909
+ @classmethod
4910
+ def login_with_qrcode(
4911
+ cls,
4505
4912
  /,
4506
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4913
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4507
4914
  *,
4508
4915
  async_: Literal[False] = False,
4509
4916
  **request_kwargs,
4510
4917
  ) -> dict:
4511
4918
  ...
4512
4919
  @overload
4513
- def download_info(
4514
- self,
4515
- payload: dict | int | str,
4920
+ @classmethod
4921
+ def login_with_qrcode(
4922
+ cls,
4516
4923
  /,
4517
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4924
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4518
4925
  *,
4519
4926
  async_: Literal[True],
4520
4927
  **request_kwargs,
4521
4928
  ) -> Coroutine[Any, Any, dict]:
4522
4929
  ...
4523
- def download_info(
4524
- self,
4525
- payload: dict | int | str,
4930
+ @classmethod
4931
+ def login_with_qrcode(
4932
+ cls,
4526
4933
  /,
4527
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4934
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4528
4935
  *,
4529
4936
  async_: Literal[False, True] = False,
4530
4937
  **request_kwargs,
4531
4938
  ) -> dict | Coroutine[Any, Any, dict]:
4532
- """获取下载信息
4533
-
4534
- POST https://www.123pan.com/api/file/download_info
4535
-
4536
- .. hint::
4537
- 即使文件已经被删除,只要还有 S3KeyFlag 和 Etag (即 MD5) 就依然可以下载
4538
-
4539
- 你完全可以构造这样的查询参数
4540
-
4541
- .. code:: python
4542
-
4543
- payload = {
4544
- "Etag": "...", # 必填,文件的 MD5
4545
- "FileID": 0, # 可以随便填
4546
- "FileName": "a", # 随便填一个名字
4547
- "S3KeyFlag": str # 必填,格式为 f"{UID}-0",UID 就是上传此文件的用户的 UID,如果此文件是由你上传的,则可从 `P123Client.user_info` 的响应中获取
4548
- "Size": 0, # 可以随便填,填了可能搜索更准确
4549
- }
4939
+ """二维码扫码登录
4550
4940
 
4551
- .. note::
4552
- 获取的直链有效期是 24 小时
4941
+ :param base_url: 接口的基地址
4942
+ :param async_: 是否异步
4943
+ :param request_kwargs: 其它请求参数
4553
4944
 
4554
- :payload:
4555
- - Etag: str 💡 文件的 MD5 散列值
4556
- - S3KeyFlag: str
4557
- - FileName: str = <default> 💡 默认用 Etag(即 MD5)作为文件名
4558
- - FileID: int | str = 0
4559
- - Size: int = <default>
4560
- - Type: int = 0
4561
- - driveId: int | str = 0
4562
- - ...
4945
+ :return: 接口响应
4563
4946
  """
4564
4947
  def gen_step():
4565
- nonlocal payload
4566
- update_headers_in_kwargs(request_kwargs, platform="android")
4567
- if not isinstance(payload, dict):
4568
- resp = yield self.fs_info(
4569
- payload,
4948
+ resp = yield cls.login_qrcode_generate(
4949
+ base_url=base_url,
4950
+ async_=async_,
4951
+ **request_kwargs,
4952
+ )
4953
+ check_response(resp)
4954
+ uniID = resp["data"]["uniID"]
4955
+ qrcode_url = f"{resp['data']['url']}?env=production&uniID={uniID}&source=123pan&type=login"
4956
+ from qrcode import QRCode # type: ignore
4957
+ qr = QRCode(border=1)
4958
+ qr.add_data(qrcode_url)
4959
+ qr.print_ascii(tty=isatty(1))
4960
+ while True:
4961
+ resp = yield cls.login_qrcode_result(
4962
+ uniID,
4570
4963
  base_url=base_url,
4571
4964
  async_=async_,
4572
4965
  **request_kwargs,
4573
4966
  )
4574
- resp["payload"] = payload
4575
4967
  check_response(resp)
4576
- if not (info_list := resp["data"]["infoList"]):
4577
- raise FileNotFoundError(ENOENT, resp)
4578
- payload = cast(dict, info_list[0])
4579
- if payload["Type"]:
4580
- raise IsADirectoryError(EISDIR, resp)
4581
- payload = dict_to_lower_merge(
4582
- payload, {"driveId": 0, "Type": 0, "FileID": 0})
4583
- if "filename" not in payload:
4584
- payload["filename"] = payload["etag"]
4585
- return self.request(
4586
- "file/download_info",
4587
- "POST",
4588
- json=payload,
4589
- base_url=base_url,
4590
- async_=async_,
4591
- **request_kwargs,
4592
- )
4968
+ if resp["code"] == 200:
4969
+ return resp
4970
+ match resp["data"]["loginStatus"]:
4971
+ case 0:
4972
+ print("\r\033[1K[loginStatus=0] qrcode: waiting", end="")
4973
+ case 1:
4974
+ print("\r\033[1K[loginStatus=1] qrcode: scanned", end="")
4975
+ case 2:
4976
+ print("\r\033[1K[loginStatus=2] qrcode: cancelled", end="")
4977
+ raise P123LoginError(EAUTH, f"qrcode: cancelled with {resp!r}")
4978
+ case 3:
4979
+ print("\r\033[1K[loginStatus=3] qrcode: login", end="")
4980
+ case 4:
4981
+ print("\r\033[1K[loginStatus=4] qrcode: expired", end="")
4982
+ raise P123LoginError(EAUTH, f"qrcode: expired with {resp!r}")
4983
+ case _:
4984
+ raise P123LoginError(EAUTH, f"qrcode: aborted with {resp!r}")
4593
4985
  return run_gen_step(gen_step, async_)
4594
4986
 
4987
+ ########## App API ##########
4988
+
4595
4989
  @overload
4596
- def download_info_batch(
4990
+ def app_config(
4597
4991
  self,
4598
- payload: dict | int | str | Iterable[int | str],
4992
+ payload: dict | str = "OfflineDownload",
4599
4993
  /,
4600
4994
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4601
4995
  *,
@@ -4604,9 +4998,9 @@ class P123Client(P123OpenClient):
4604
4998
  ) -> dict:
4605
4999
  ...
4606
5000
  @overload
4607
- def download_info_batch(
5001
+ def app_config(
4608
5002
  self,
4609
- payload: dict | int | str | Iterable[int | str],
5003
+ payload: dict | str = "OfflineDownload",
4610
5004
  /,
4611
5005
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4612
5006
  *,
@@ -4614,37 +5008,26 @@ class P123Client(P123OpenClient):
4614
5008
  **request_kwargs,
4615
5009
  ) -> Coroutine[Any, Any, dict]:
4616
5010
  ...
4617
- def download_info_batch(
5011
+ def app_config(
4618
5012
  self,
4619
- payload: dict | int | str | Iterable[int | str],
5013
+ payload: dict | str = "OfflineDownload",
4620
5014
  /,
4621
5015
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4622
5016
  *,
4623
5017
  async_: Literal[False, True] = False,
4624
5018
  **request_kwargs,
4625
5019
  ) -> dict | Coroutine[Any, Any, dict]:
4626
- """获取批量下载信息
4627
-
4628
- POST https://www.123pan.com/api/file/batch_download_info
5020
+ """获取配置信息
4629
5021
 
4630
- .. warning::
4631
- 会把一些文件或目录以 zip 包的形式下载,但非会员有流量限制,所以还是推荐用 `P123Client.download_info` 逐个获取下载链接并下载
5022
+ POST https://www.123pan.com/api/config/get
4632
5023
 
4633
5024
  :payload:
4634
- - fileIdList: list[FileID]
4635
-
4636
- .. code:: python
4637
-
4638
- FileID = {
4639
- "FileId": int | str
4640
- }
5025
+ - business_key: str 💡 配置键名(字段)
4641
5026
  """
4642
- if isinstance(payload, (int, str)):
4643
- payload = {"fileIdList": [{"FileId": payload}]}
4644
- elif not isinstance(payload, dict):
4645
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5027
+ if not isinstance(payload, dict):
5028
+ payload = {"business_key": payload}
4646
5029
  return self.request(
4647
- "file/batch_download_info",
5030
+ "config/get",
4648
5031
  "POST",
4649
5032
  json=payload,
4650
5033
  base_url=base_url,
@@ -4653,63 +5036,531 @@ class P123Client(P123OpenClient):
4653
5036
  )
4654
5037
 
4655
5038
  @overload
4656
- def download_url(
4657
- self,
4658
- payload: dict | int | str,
4659
- /,
5039
+ @staticmethod
5040
+ def app_dydomain(
5041
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5042
+ request: None | Callable = None,
4660
5043
  *,
4661
5044
  async_: Literal[False] = False,
4662
5045
  **request_kwargs,
4663
- ) -> str:
5046
+ ) -> dict:
4664
5047
  ...
4665
5048
  @overload
4666
- def download_url(
4667
- self,
4668
- payload: dict | int | str,
4669
- /,
5049
+ @staticmethod
5050
+ def app_dydomain(
5051
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5052
+ request: None | Callable = None,
4670
5053
  *,
4671
5054
  async_: Literal[True],
4672
5055
  **request_kwargs,
4673
- ) -> Coroutine[Any, Any, str]:
5056
+ ) -> Coroutine[Any, Any, dict]:
4674
5057
  ...
4675
- def download_url(
4676
- self,
4677
- payload: dict | int | str,
4678
- /,
5058
+ @staticmethod
5059
+ def app_dydomain(
5060
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5061
+ request: None | Callable = None,
4679
5062
  *,
4680
5063
  async_: Literal[False, True] = False,
4681
5064
  **request_kwargs,
4682
- ) -> str | Coroutine[Any, Any, str]:
4683
- """获取下载链接
4684
-
4685
- .. note::
4686
- `payload` 支持多种格式的输入,按下面的规则按顺序进行判断:
4687
-
4688
- 1. 如果是 `int` 或 `str`,则视为文件 id,必须在你的网盘中存在此文件
4689
- 2. 如果是 `dict`(不区分大小写),有 "S3KeyFlag", "Etag" 和 "Size" 的值,则直接获取链接,文件不必在你网盘中
4690
- 3. 如果是 `dict`(不区分大小写),有 "Etag" 和 "Size" 的值,则会先秒传(临时文件路径为 /.tempfile)再获取链接,文件不必在你网盘中
4691
- 4. 如果是 `dict`(不区分大小写),有 "FileID",则会先获取信息,再获取链接,必须在你的网盘中存在此文件
4692
- 5. 否则会报错 ValueError
4693
-
4694
- :params payload: 文件 id 或者文件信息,文件信息必须包含的信息如下:
5065
+ ) -> dict | Coroutine[Any, Any, dict]:
5066
+ """获取 123 网盘的各种域名
4695
5067
 
4696
- - FileID: int | str 💡 下载链接
4697
- - S3KeyFlag: str 💡 s3 存储名
4698
- - Etag: str 💡 文件的 MD5 散列值
4699
- - Size: int 💡 文件大小
4700
- - FileName: str 💡 默认用 Etag(即 MD5)作为文件名,可以省略
5068
+ GET https://www.123pan.com/api/dydomain
5069
+ """
5070
+ request_kwargs.setdefault("parse", default_parse)
5071
+ if request is None:
5072
+ request = get_default_request()
5073
+ request_kwargs["async_"] = async_
5074
+ return request(
5075
+ url=complete_url("/api/dydomain", base_url),
5076
+ **request_kwargs,
5077
+ )
4701
5078
 
4702
- :params async_: 是否异步
4703
- :params request_kwargs: 其它请求参数
5079
+ @overload
5080
+ @staticmethod
5081
+ def app_id_get(
5082
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5083
+ request: None | Callable = None,
5084
+ *,
5085
+ async_: Literal[False] = False,
5086
+ **request_kwargs,
5087
+ ) -> dict:
5088
+ ...
5089
+ @overload
5090
+ @staticmethod
5091
+ def app_id_get(
5092
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5093
+ request: None | Callable = None,
5094
+ *,
5095
+ async_: Literal[True],
5096
+ **request_kwargs,
5097
+ ) -> Coroutine[Any, Any, dict]:
5098
+ ...
5099
+ @staticmethod
5100
+ def app_id_get(
5101
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5102
+ request: None | Callable = None,
5103
+ *,
5104
+ async_: Literal[False, True] = False,
5105
+ **request_kwargs,
5106
+ ) -> dict | Coroutine[Any, Any, dict]:
5107
+ """获取 app-id
4704
5108
 
4705
- :return: 下载链接
5109
+ GET https://www.123pan.com/api/v3/3rd/app-id
4706
5110
  """
4707
- def gen_step():
4708
- nonlocal payload
4709
- if isinstance(payload, dict):
4710
- payload = dict_to_lower(payload)
4711
- if not ("size" in payload and "etag" in payload):
4712
- if fileid := payload.get("fileid"):
5111
+ request_kwargs.setdefault("parse", default_parse)
5112
+ if request is None:
5113
+ request = get_default_request()
5114
+ request_kwargs["async_"] = async_
5115
+ return request(
5116
+ url=complete_url("/api/v3/3rd/app-id", base_url),
5117
+ **request_kwargs,
5118
+ )
5119
+
5120
+ @overload
5121
+ @staticmethod
5122
+ def app_server_time(
5123
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5124
+ request: None | Callable = None,
5125
+ *,
5126
+ async_: Literal[False] = False,
5127
+ **request_kwargs,
5128
+ ) -> dict:
5129
+ ...
5130
+ @overload
5131
+ @staticmethod
5132
+ def app_server_time(
5133
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5134
+ request: None | Callable = None,
5135
+ *,
5136
+ async_: Literal[True],
5137
+ **request_kwargs,
5138
+ ) -> Coroutine[Any, Any, dict]:
5139
+ ...
5140
+ @staticmethod
5141
+ def app_server_time(
5142
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5143
+ request: None | Callable = None,
5144
+ *,
5145
+ async_: Literal[False, True] = False,
5146
+ **request_kwargs,
5147
+ ) -> dict | Coroutine[Any, Any, dict]:
5148
+ """获取 123 网盘的服务器时间戳
5149
+
5150
+ GET https://www.123pan.com/api/get/server/time
5151
+ """
5152
+ request_kwargs.setdefault("parse", default_parse)
5153
+ if request is None:
5154
+ request = get_default_request()
5155
+ request_kwargs["async_"] = async_
5156
+ return request(
5157
+ url=complete_url("/api/get/server/time", base_url),
5158
+ **request_kwargs,
5159
+ )
5160
+
5161
+ @overload
5162
+ @staticmethod
5163
+ def app_transfer_metrics(
5164
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5165
+ request: None | Callable = None,
5166
+ *,
5167
+ async_: Literal[False] = False,
5168
+ **request_kwargs,
5169
+ ) -> dict:
5170
+ ...
5171
+ @overload
5172
+ @staticmethod
5173
+ def app_transfer_metrics(
5174
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5175
+ request: None | Callable = None,
5176
+ *,
5177
+ async_: Literal[True],
5178
+ **request_kwargs,
5179
+ ) -> Coroutine[Any, Any, dict]:
5180
+ ...
5181
+ @staticmethod
5182
+ def app_transfer_metrics(
5183
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5184
+ request: None | Callable = None,
5185
+ *,
5186
+ async_: Literal[False, True] = False,
5187
+ **request_kwargs,
5188
+ ) -> dict | Coroutine[Any, Any, dict]:
5189
+ """获取和传输有关的配置信息
5190
+
5191
+ GET https://www.123pan.com/api/transfer/metrics/whether/report
5192
+ """
5193
+ request_kwargs.setdefault("parse", default_parse)
5194
+ if request is None:
5195
+ request = get_default_request()
5196
+ request_kwargs["async_"] = async_
5197
+ return request(
5198
+ url=complete_url("/api/transfer/metrics/whether/report", base_url),
5199
+ **request_kwargs,
5200
+ )
5201
+
5202
+ ########## Download API ##########
5203
+
5204
+ @overload
5205
+ def dlink_disable(
5206
+ self,
5207
+ payload: dict | int | str,
5208
+ /,
5209
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5210
+ *,
5211
+ async_: Literal[False] = False,
5212
+ **request_kwargs,
5213
+ ) -> dict:
5214
+ ...
5215
+ @overload
5216
+ def dlink_disable(
5217
+ self,
5218
+ payload: dict | int | str,
5219
+ /,
5220
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5221
+ *,
5222
+ async_: Literal[True],
5223
+ **request_kwargs,
5224
+ ) -> Coroutine[Any, Any, dict]:
5225
+ ...
5226
+ def dlink_disable(
5227
+ self,
5228
+ payload: dict | int | str,
5229
+ /,
5230
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5231
+ *,
5232
+ async_: Literal[False, True] = False,
5233
+ **request_kwargs,
5234
+ ) -> dict | Coroutine[Any, Any, dict]:
5235
+ """禁用直链空间
5236
+
5237
+ POST https://www.123pan.com/api/cdn-link/disable
5238
+
5239
+ :payload:
5240
+ - fileID: int | str 💡 目录 id
5241
+ """
5242
+ if not isinstance(payload, dict):
5243
+ payload = {"fileID": payload}
5244
+ return self.request(
5245
+ "cdn-link/disable",
5246
+ "POST",
5247
+ json=payload,
5248
+ base_url=base_url,
5249
+ async_=async_,
5250
+ **request_kwargs,
5251
+ )
5252
+
5253
+ @overload
5254
+ def dlink_enable(
5255
+ self,
5256
+ payload: dict | int | str,
5257
+ /,
5258
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5259
+ *,
5260
+ async_: Literal[False] = False,
5261
+ **request_kwargs,
5262
+ ) -> dict:
5263
+ ...
5264
+ @overload
5265
+ def dlink_enable(
5266
+ self,
5267
+ payload: dict | int | str,
5268
+ /,
5269
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5270
+ *,
5271
+ async_: Literal[True],
5272
+ **request_kwargs,
5273
+ ) -> Coroutine[Any, Any, dict]:
5274
+ ...
5275
+ def dlink_enable(
5276
+ self,
5277
+ payload: dict | int | str,
5278
+ /,
5279
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5280
+ *,
5281
+ async_: Literal[False, True] = False,
5282
+ **request_kwargs,
5283
+ ) -> dict | Coroutine[Any, Any, dict]:
5284
+ """启用直链空间
5285
+
5286
+ POST https://www.123pan.com/api/cdn-link/enable
5287
+
5288
+ :payload:
5289
+ - fileID: int | str 💡 目录 id
5290
+ """
5291
+ if not isinstance(payload, dict):
5292
+ payload = {"fileID": payload}
5293
+ return self.request(
5294
+ "cdn-link/enable",
5295
+ "POST",
5296
+ json=payload,
5297
+ base_url=base_url,
5298
+ async_=async_,
5299
+ **request_kwargs,
5300
+ )
5301
+
5302
+ @overload
5303
+ def dlink_url(
5304
+ self,
5305
+ payload: dict | int | str,
5306
+ /,
5307
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5308
+ *,
5309
+ async_: Literal[False] = False,
5310
+ **request_kwargs,
5311
+ ) -> dict:
5312
+ ...
5313
+ @overload
5314
+ def dlink_url(
5315
+ self,
5316
+ payload: dict | int | str,
5317
+ /,
5318
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5319
+ *,
5320
+ async_: Literal[True],
5321
+ **request_kwargs,
5322
+ ) -> Coroutine[Any, Any, dict]:
5323
+ ...
5324
+ def dlink_url(
5325
+ self,
5326
+ payload: dict | int | str,
5327
+ /,
5328
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5329
+ *,
5330
+ async_: Literal[False, True] = False,
5331
+ **request_kwargs,
5332
+ ) -> dict | Coroutine[Any, Any, dict]:
5333
+ """获取直链链接
5334
+
5335
+ GET https://www.123pan.com/api/cdn-link/url
5336
+
5337
+ :payload:
5338
+ - fileID: int | str 💡 文件 id
5339
+ """
5340
+ if not isinstance(payload, dict):
5341
+ payload = {"fileID": payload}
5342
+ return self.request(
5343
+ "cdn-link/url",
5344
+ params=payload,
5345
+ base_url=base_url,
5346
+ async_=async_,
5347
+ **request_kwargs,
5348
+ )
5349
+
5350
+ ########## Download API ##########
5351
+
5352
+ @overload
5353
+ def download_info(
5354
+ self,
5355
+ payload: dict | int | str,
5356
+ /,
5357
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5358
+ *,
5359
+ async_: Literal[False] = False,
5360
+ **request_kwargs,
5361
+ ) -> dict:
5362
+ ...
5363
+ @overload
5364
+ def download_info(
5365
+ self,
5366
+ payload: dict | int | str,
5367
+ /,
5368
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5369
+ *,
5370
+ async_: Literal[True],
5371
+ **request_kwargs,
5372
+ ) -> Coroutine[Any, Any, dict]:
5373
+ ...
5374
+ def download_info(
5375
+ self,
5376
+ payload: dict | int | str,
5377
+ /,
5378
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5379
+ *,
5380
+ async_: Literal[False, True] = False,
5381
+ **request_kwargs,
5382
+ ) -> dict | Coroutine[Any, Any, dict]:
5383
+ """获取下载信息
5384
+
5385
+ POST https://www.123pan.com/api/file/download_info
5386
+
5387
+ .. hint::
5388
+ 即使文件已经被删除,只要还有 S3KeyFlag 和 Etag (即 MD5) 就依然可以下载
5389
+
5390
+ 你完全可以构造这样的查询参数
5391
+
5392
+ .. code:: python
5393
+
5394
+ payload = {
5395
+ "Etag": "...", # 必填,文件的 MD5
5396
+ "FileID": 0, # 可以随便填
5397
+ "FileName": "a", # 随便填一个名字
5398
+ "S3KeyFlag": str # 必填,格式为 f"{UID}-0",UID 就是上传此文件的用户的 UID,如果此文件是由你上传的,则可从 `P123Client.user_info` 的响应中获取
5399
+ "Size": 0, # 可以随便填,填了可能搜索更准确
5400
+ }
5401
+
5402
+ .. note::
5403
+ 获取的直链有效期是 24 小时
5404
+
5405
+ :payload:
5406
+ - Etag: str 💡 文件的 MD5 散列值
5407
+ - S3KeyFlag: str
5408
+ - FileName: str = <default> 💡 默认用 Etag(即 MD5)作为文件名
5409
+ - FileID: int | str = 0
5410
+ - Size: int = <default>
5411
+ - Type: int = 0
5412
+ - driveId: int | str = 0
5413
+ - ...
5414
+ """
5415
+ def gen_step():
5416
+ nonlocal payload
5417
+ update_headers_in_kwargs(request_kwargs, platform="android")
5418
+ if not isinstance(payload, dict):
5419
+ resp = yield self.fs_info(
5420
+ payload,
5421
+ base_url=base_url,
5422
+ async_=async_,
5423
+ **request_kwargs,
5424
+ )
5425
+ resp["payload"] = payload
5426
+ check_response(resp)
5427
+ if not (info_list := resp["data"]["infoList"]):
5428
+ raise FileNotFoundError(ENOENT, resp)
5429
+ payload = cast(dict, info_list[0])
5430
+ if payload["Type"]:
5431
+ raise IsADirectoryError(EISDIR, resp)
5432
+ payload = dict_to_lower_merge(
5433
+ payload, {"driveId": 0, "Type": 0, "FileID": 0})
5434
+ if "filename" not in payload:
5435
+ payload["filename"] = payload["etag"]
5436
+ return self.request(
5437
+ "file/download_info",
5438
+ "POST",
5439
+ json=payload,
5440
+ base_url=base_url,
5441
+ async_=async_,
5442
+ **request_kwargs,
5443
+ )
5444
+ return run_gen_step(gen_step, async_)
5445
+
5446
+ @overload
5447
+ def download_info_batch(
5448
+ self,
5449
+ payload: dict | int | str | Iterable[int | str],
5450
+ /,
5451
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5452
+ *,
5453
+ async_: Literal[False] = False,
5454
+ **request_kwargs,
5455
+ ) -> dict:
5456
+ ...
5457
+ @overload
5458
+ def download_info_batch(
5459
+ self,
5460
+ payload: dict | int | str | Iterable[int | str],
5461
+ /,
5462
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5463
+ *,
5464
+ async_: Literal[True],
5465
+ **request_kwargs,
5466
+ ) -> Coroutine[Any, Any, dict]:
5467
+ ...
5468
+ def download_info_batch(
5469
+ self,
5470
+ payload: dict | int | str | Iterable[int | str],
5471
+ /,
5472
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5473
+ *,
5474
+ async_: Literal[False, True] = False,
5475
+ **request_kwargs,
5476
+ ) -> dict | Coroutine[Any, Any, dict]:
5477
+ """获取批量下载信息
5478
+
5479
+ POST https://www.123pan.com/api/file/batch_download_info
5480
+
5481
+ .. warning::
5482
+ 会把一些文件或目录以 zip 包的形式下载,但非会员有流量限制,所以还是推荐用 `P123Client.download_info` 逐个获取下载链接并下载
5483
+
5484
+ :payload:
5485
+ - fileIdList: list[FileID]
5486
+
5487
+ .. code:: python
5488
+
5489
+ FileID = {
5490
+ "FileId": int | str
5491
+ }
5492
+ """
5493
+ if isinstance(payload, (int, str)):
5494
+ payload = {"fileIdList": [{"FileId": payload}]}
5495
+ elif not isinstance(payload, dict):
5496
+ payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5497
+ return self.request(
5498
+ "file/batch_download_info",
5499
+ "POST",
5500
+ json=payload,
5501
+ base_url=base_url,
5502
+ async_=async_,
5503
+ **request_kwargs,
5504
+ )
5505
+
5506
+ @overload
5507
+ def download_url(
5508
+ self,
5509
+ payload: dict | int | str,
5510
+ /,
5511
+ *,
5512
+ async_: Literal[False] = False,
5513
+ **request_kwargs,
5514
+ ) -> str:
5515
+ ...
5516
+ @overload
5517
+ def download_url(
5518
+ self,
5519
+ payload: dict | int | str,
5520
+ /,
5521
+ *,
5522
+ async_: Literal[True],
5523
+ **request_kwargs,
5524
+ ) -> Coroutine[Any, Any, str]:
5525
+ ...
5526
+ def download_url(
5527
+ self,
5528
+ payload: dict | int | str,
5529
+ /,
5530
+ *,
5531
+ async_: Literal[False, True] = False,
5532
+ **request_kwargs,
5533
+ ) -> str | Coroutine[Any, Any, str]:
5534
+ """获取下载链接
5535
+
5536
+ .. note::
5537
+ `payload` 支持多种格式的输入,按下面的规则按顺序进行判断:
5538
+
5539
+ 1. 如果是 `int` 或 `str`,则视为文件 id,必须在你的网盘中存在此文件
5540
+ 2. 如果是 `dict`(不区分大小写),有 "S3KeyFlag", "Etag" 和 "Size" 的值,则直接获取链接,文件不必在你网盘中
5541
+ 3. 如果是 `dict`(不区分大小写),有 "Etag" 和 "Size" 的值,则会先秒传(临时文件路径为 /.tempfile)再获取链接,文件不必在你网盘中
5542
+ 4. 如果是 `dict`(不区分大小写),有 "FileID",则会先获取信息,再获取链接,必须在你的网盘中存在此文件
5543
+ 5. 否则会报错 ValueError
5544
+
5545
+ :params payload: 文件 id 或者文件信息,文件信息必须包含的信息如下:
5546
+
5547
+ - FileID: int | str 💡 下载链接
5548
+ - S3KeyFlag: str 💡 s3 存储名
5549
+ - Etag: str 💡 文件的 MD5 散列值
5550
+ - Size: int 💡 文件大小
5551
+ - FileName: str 💡 默认用 Etag(即 MD5)作为文件名,可以省略
5552
+
5553
+ :params async_: 是否异步
5554
+ :params request_kwargs: 其它请求参数
5555
+
5556
+ :return: 下载链接
5557
+ """
5558
+ def gen_step():
5559
+ nonlocal payload
5560
+ if isinstance(payload, dict):
5561
+ payload = dict_to_lower(payload)
5562
+ if not ("size" in payload and "etag" in payload):
5563
+ if fileid := payload.get("fileid"):
4713
5564
  resp = yield self.fs_info(fileid, async_=async_, **request_kwargs)
4714
5565
  check_response(resp)
4715
5566
  if not (info_list := resp["data"]["infoList"]):
@@ -4750,15 +5601,948 @@ class P123Client(P123OpenClient):
4750
5601
  **request_kwargs,
4751
5602
  )
4752
5603
  check_response(resp)
4753
- return resp["data"]["downloadUrl"]
5604
+ return resp["data"]["downloadUrl"]
5605
+ return run_gen_step(gen_step, async_)
5606
+
5607
+ ########## File System API ##########
5608
+
5609
+ @overload
5610
+ def fs_abnormal_count(
5611
+ self,
5612
+ /,
5613
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5614
+ *,
5615
+ async_: Literal[False] = False,
5616
+ **request_kwargs,
5617
+ ) -> dict:
5618
+ ...
5619
+ @overload
5620
+ def fs_abnormal_count(
5621
+ self,
5622
+ /,
5623
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5624
+ *,
5625
+ async_: Literal[True],
5626
+ **request_kwargs,
5627
+ ) -> Coroutine[Any, Any, dict]:
5628
+ ...
5629
+ def fs_abnormal_count(
5630
+ self,
5631
+ /,
5632
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5633
+ *,
5634
+ async_: Literal[False, True] = False,
5635
+ **request_kwargs,
5636
+ ) -> dict | Coroutine[Any, Any, dict]:
5637
+ """获取异常文件数
5638
+
5639
+ GET https://www.123pan.com/b/api/file/abnormal/count
5640
+ """
5641
+ return self.request(
5642
+ "file/abnormal/count",
5643
+ base_url=base_url,
5644
+ async_=async_,
5645
+ **request_kwargs,
5646
+ )
5647
+
5648
+ @overload
5649
+ def fs_copy(
5650
+ self,
5651
+ payload: dict | int | str | Iterable[int | str],
5652
+ /,
5653
+ parent_id: int | str = 0,
5654
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5655
+ *,
5656
+ async_: Literal[False] = False,
5657
+ **request_kwargs,
5658
+ ) -> dict:
5659
+ ...
5660
+ @overload
5661
+ def fs_copy(
5662
+ self,
5663
+ payload: dict | int | str | Iterable[int | str],
5664
+ /,
5665
+ parent_id: int | str = 0,
5666
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5667
+ *,
5668
+ async_: Literal[True],
5669
+ **request_kwargs,
5670
+ ) -> Coroutine[Any, Any, dict]:
5671
+ ...
5672
+ def fs_copy(
5673
+ self,
5674
+ payload: dict | int | str | Iterable[int | str],
5675
+ /,
5676
+ parent_id: int | str = 0,
5677
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5678
+ *,
5679
+ async_: Literal[False, True] = False,
5680
+ **request_kwargs,
5681
+ ) -> dict | Coroutine[Any, Any, dict]:
5682
+ """复制
5683
+
5684
+ POST https://www.123pan.com/api/restful/goapi/v1/file/copy/async
5685
+
5686
+ :payload:
5687
+ - fileList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
5688
+
5689
+ .. code:: python
5690
+
5691
+ File = {
5692
+ "FileId": int | str,
5693
+ ...
5694
+ }
5695
+
5696
+ - targetFileId: int | str = 0
5697
+ """
5698
+ def gen_step():
5699
+ nonlocal payload
5700
+ if not isinstance(payload, dict):
5701
+ resp = yield self.fs_info(
5702
+ payload,
5703
+ base_url=base_url,
5704
+ async_=async_,
5705
+ **request_kwargs,
5706
+ )
5707
+ resp["payload"] = payload
5708
+ check_response(resp)
5709
+ info_list = resp["data"]["infoList"]
5710
+ if not info_list:
5711
+ raise FileNotFoundError(ENOENT, resp)
5712
+ payload = {"fileList": info_list}
5713
+ payload = dict_to_lower_merge(payload, targetFileId=parent_id)
5714
+ return self.request(
5715
+ "restful/goapi/v1/file/copy/async",
5716
+ "POST",
5717
+ json=payload,
5718
+ base_url=base_url,
5719
+ async_=async_,
5720
+ **request_kwargs,
5721
+ )
4754
5722
  return run_gen_step(gen_step, async_)
4755
5723
 
4756
5724
  @overload
4757
- def fs_copy(
5725
+ def fs_detail(
5726
+ self,
5727
+ payload: dict | int | str,
5728
+ /,
5729
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5730
+ *,
5731
+ async_: Literal[False] = False,
5732
+ **request_kwargs,
5733
+ ) -> dict:
5734
+ ...
5735
+ @overload
5736
+ def fs_detail(
5737
+ self,
5738
+ payload: dict | int | str,
5739
+ /,
5740
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5741
+ *,
5742
+ async_: Literal[True],
5743
+ **request_kwargs,
5744
+ ) -> Coroutine[Any, Any, dict]:
5745
+ ...
5746
+ def fs_detail(
5747
+ self,
5748
+ payload: dict | int | str,
5749
+ /,
5750
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5751
+ *,
5752
+ async_: Literal[False, True] = False,
5753
+ **request_kwargs,
5754
+ ) -> dict | Coroutine[Any, Any, dict]:
5755
+ """获取文件或目录详情(文件数、目录数、总大小)
5756
+
5757
+ GET https://www.123pan.com/api/file/detail
5758
+
5759
+ :payload:
5760
+ - fileID: int | str
5761
+ """
5762
+ if isinstance(payload, (int, str)):
5763
+ payload = {"fileID": payload}
5764
+ return self.request(
5765
+ "file/detail",
5766
+ params=payload,
5767
+ base_url=base_url,
5768
+ async_=async_,
5769
+ **request_kwargs,
5770
+ )
5771
+
5772
+ @overload
5773
+ def fs_delete(
5774
+ self,
5775
+ payload: dict | int | str | Iterable[int | str] = 0,
5776
+ /,
5777
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5778
+ *,
5779
+ async_: Literal[False] = False,
5780
+ **request_kwargs,
5781
+ ) -> dict:
5782
+ ...
5783
+ @overload
5784
+ def fs_delete(
5785
+ self,
5786
+ payload: dict | int | str | Iterable[int | str] = 0,
5787
+ /,
5788
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5789
+ *,
5790
+ async_: Literal[True],
5791
+ **request_kwargs,
5792
+ ) -> Coroutine[Any, Any, dict]:
5793
+ ...
5794
+ def fs_delete(
5795
+ self,
5796
+ payload: dict | int | str | Iterable[int | str] = 0,
5797
+ /,
5798
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5799
+ *,
5800
+ async_: Literal[False, True] = False,
5801
+ **request_kwargs,
5802
+ ) -> dict | Coroutine[Any, Any, dict]:
5803
+ """彻底删除
5804
+
5805
+ POST https://www.123pan.com/api/file/delete
5806
+
5807
+ .. hint::
5808
+ 彻底删除文件前,文件必须要在回收站中,否则无法删除
5809
+
5810
+ :payload:
5811
+ - fileIdList: list[FileID]
5812
+
5813
+ .. code:: python
5814
+
5815
+ FileID = {
5816
+ "FileId": int | str
5817
+ }
5818
+
5819
+ - event: str = "recycleDelete"
5820
+ """
5821
+ if isinstance(payload, (int, str)):
5822
+ payload = {"fileIdList": [{"FileId": payload}]}
5823
+ elif not isinstance(payload, dict):
5824
+ payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5825
+ payload = cast(dict, payload)
5826
+ payload.setdefault("event", "recycleDelete")
5827
+ return self.request(
5828
+ "file/delete",
5829
+ "POST",
5830
+ json=payload,
5831
+ base_url=base_url,
5832
+ async_=async_,
5833
+ **request_kwargs,
5834
+ )
5835
+
5836
+ @overload
5837
+ def fs_get_path(
5838
+ self,
5839
+ payload: dict | int,
5840
+ /,
5841
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5842
+ *,
5843
+ async_: Literal[False] = False,
5844
+ **request_kwargs,
5845
+ ) -> dict:
5846
+ ...
5847
+ @overload
5848
+ def fs_get_path(
5849
+ self,
5850
+ payload: dict | int,
5851
+ /,
5852
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5853
+ *,
5854
+ async_: Literal[True],
5855
+ **request_kwargs,
5856
+ ) -> Coroutine[Any, Any, dict]:
5857
+ ...
5858
+ def fs_get_path(
5859
+ self,
5860
+ payload: dict | int,
5861
+ /,
5862
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5863
+ *,
5864
+ async_: Literal[False, True] = False,
5865
+ **request_kwargs,
5866
+ ) -> dict | Coroutine[Any, Any, dict]:
5867
+ """获取某个 id 对应的祖先节点列表
5868
+
5869
+ POST https://www.123pan.com/api/file/get_path
5870
+
5871
+ :payload:
5872
+ - fileId: int 💡 文件 id
5873
+ """
5874
+ if isinstance(payload, int):
5875
+ payload = {"fileId": payload}
5876
+ return self.request(
5877
+ "file/get_path",
5878
+ "POST",
5879
+ json=payload,
5880
+ base_url=base_url,
5881
+ async_=async_,
5882
+ **request_kwargs,
5883
+ )
5884
+
5885
+ @overload
5886
+ def fs_info(
5887
+ self,
5888
+ payload: dict | int | str | Iterable[int | str] = 0,
5889
+ /,
5890
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5891
+ *,
5892
+ async_: Literal[False] = False,
5893
+ **request_kwargs,
5894
+ ) -> dict:
5895
+ ...
5896
+ @overload
5897
+ def fs_info(
5898
+ self,
5899
+ payload: dict | int | str | Iterable[int | str] = 0,
5900
+ /,
5901
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5902
+ *,
5903
+ async_: Literal[True],
5904
+ **request_kwargs,
5905
+ ) -> Coroutine[Any, Any, dict]:
5906
+ ...
5907
+ def fs_info(
5908
+ self,
5909
+ payload: dict | int | str | Iterable[int | str] = 0,
5910
+ /,
5911
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5912
+ *,
5913
+ async_: Literal[False, True] = False,
5914
+ **request_kwargs,
5915
+ ) -> dict | Coroutine[Any, Any, dict]:
5916
+ """获取文件信息
5917
+
5918
+ POST https://www.123pan.com/api/file/info
5919
+
5920
+ :payload:
5921
+ - fileIdList: list[FileID]
5922
+
5923
+ .. code:: python
5924
+
5925
+ FileID = {
5926
+ "FileId": int | str
5927
+ }
5928
+ """
5929
+ if isinstance(payload, (int, str)):
5930
+ payload = {"fileIdList": [{"FileId": payload}]}
5931
+ elif not isinstance(payload, dict):
5932
+ payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5933
+ return self.request(
5934
+ "file/info",
5935
+ "POST",
5936
+ json=payload,
5937
+ base_url=base_url,
5938
+ async_=async_,
5939
+ **request_kwargs,
5940
+ )
5941
+
5942
+ @overload # type: ignore
5943
+ def fs_list(
5944
+ self,
5945
+ payload: dict | int | str = 0,
5946
+ /,
5947
+ event: str = "homeListFile",
5948
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5949
+ *,
5950
+ async_: Literal[False] = False,
5951
+ **request_kwargs,
5952
+ ) -> dict:
5953
+ ...
5954
+ @overload
5955
+ def fs_list(
5956
+ self,
5957
+ payload: dict | int | str = 0,
5958
+ /,
5959
+ event: str = "homeListFile",
5960
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5961
+ *,
5962
+ async_: Literal[True],
5963
+ **request_kwargs,
5964
+ ) -> Coroutine[Any, Any, dict]:
5965
+ ...
5966
+ def fs_list(
5967
+ self,
5968
+ payload: dict | int | str = 0,
5969
+ /,
5970
+ event: str = "homeListFile",
5971
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5972
+ *,
5973
+ async_: Literal[False, True] = False,
5974
+ **request_kwargs,
5975
+ ) -> dict | Coroutine[Any, Any, dict]:
5976
+ """获取文件列表(可搜索)
5977
+
5978
+ GET https://www.123pan.com/api/file/list
5979
+
5980
+ .. note::
5981
+ 如果返回信息中,"Next" 字段的值为 "-1",代表最后一页(无需再翻页查询)
5982
+
5983
+ :payload:
5984
+ - driveId: int | str = 0
5985
+ - limit: int = 100 💡 分页大小,最多 100 个
5986
+ - next: int = 0 💡 下一批拉取开始的 id
5987
+ - orderBy: str = "file_name" 💡 排序依据
5988
+
5989
+ - "file_id": 文件 id,也可以写作 "fileId"
5990
+ - "file_name": 文件名
5991
+ - "size": 文件大小
5992
+ - "create_at": 创建时间
5993
+ - "update_at": 更新时间
5994
+ - "share_id": 分享 id
5995
+ - ...
5996
+
5997
+ - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
5998
+ - Page: int = <default> 💡 第几页,从 1 开始,可以是 0
5999
+ - parentFileId: int | str = 0 💡 父目录 id
6000
+ - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
6001
+ - inDirectSpace: "false" | "true" = "false"
6002
+ - event: str = "homeListFile" 💡 事件名称
6003
+
6004
+ - "homeListFile": 全部文件
6005
+ - "recycleListFile": 回收站
6006
+ - "syncFileList": 同步空间
6007
+
6008
+ - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6009
+ - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6010
+ - OnlyLookAbnormalFile: int = <default>
6011
+ """
6012
+ if isinstance(payload, (int, str)):
6013
+ payload = {"parentFileId": payload}
6014
+ payload = dict_to_lower_merge(payload, {
6015
+ "driveId": 0,
6016
+ "limit": 100,
6017
+ "next": 0,
6018
+ "orderBy": "file_name",
6019
+ "orderDirection": "asc",
6020
+ "parentFileId": 0,
6021
+ "inDirectSpace": "false",
6022
+ "event": event,
6023
+ })
6024
+ if not payload.get("trashed"):
6025
+ match payload["event"]:
6026
+ case "recycleListFile":
6027
+ payload["trashed"] = "true"
6028
+ case _:
6029
+ payload["trashed"] = "false"
6030
+ return self.request(
6031
+ "file/list",
6032
+ params=payload,
6033
+ base_url=base_url,
6034
+ async_=async_,
6035
+ **request_kwargs,
6036
+ )
6037
+
6038
+ @overload
6039
+ def fs_list_by_type(
6040
+ self,
6041
+ payload: dict | int = 1,
6042
+ /,
6043
+ event: str = "homeListFile",
6044
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6045
+ *,
6046
+ async_: Literal[False] = False,
6047
+ **request_kwargs,
6048
+ ) -> dict:
6049
+ ...
6050
+ @overload
6051
+ def fs_list_by_type(
6052
+ self,
6053
+ payload: dict | int = 1,
6054
+ /,
6055
+ event: str = "homeListFile",
6056
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6057
+ *,
6058
+ async_: Literal[True],
6059
+ **request_kwargs,
6060
+ ) -> Coroutine[Any, Any, dict]:
6061
+ ...
6062
+ def fs_list_by_type(
6063
+ self,
6064
+ payload: dict | int = 1,
6065
+ /,
6066
+ event: str = "homeListFile",
6067
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6068
+ *,
6069
+ async_: Literal[False, True] = False,
6070
+ **request_kwargs,
6071
+ ) -> dict | Coroutine[Any, Any, dict]:
6072
+ """按类型获取文件列表
6073
+
6074
+ GET https://www.123pan.com/api/restful/goapi/v1/file/category/list-by-type
6075
+
6076
+ :payload:
6077
+ - driveId: int | str = 0
6078
+ - limit: int = 100 💡 分页大小,最多 100 个
6079
+ - next: int = 0 💡 下一批拉取开始的 id
6080
+ - category: int = 1 💡 分类代码
6081
+
6082
+ - 1: 音频
6083
+ - 2: 视频
6084
+ - 3: 图片
6085
+ - 4: 音频
6086
+ - 5: 其它
6087
+
6088
+ - dateGranularity: int = <default> 💡 按时间分组展示
6089
+
6090
+ - 1: 日
6091
+ - 2: 月
6092
+ - 3: 年
6093
+
6094
+ - orderBy: str = "file_name" 💡 排序依据
6095
+
6096
+ - "file_id": 文件 id,也可以写作 "fileId"
6097
+ - "file_name": 文件名
6098
+ - "size": 文件大小
6099
+ - "create_at": 创建时间
6100
+ - "update_at": 更新时间
6101
+ - "trashed_at": 删除时间
6102
+ - "share_id": 分享 id
6103
+ - "remain_days": 剩余保留天数
6104
+ - ...
6105
+
6106
+ - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
6107
+ - Page: int = 1 💡 第几页,从 1 开始
6108
+ - parentFileId: int | str = 0 💡 父目录 id
6109
+ - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
6110
+ - inDirectSpace: "false" | "true" = "false"
6111
+ - event: str = "homeListFile" 💡 事件名称
6112
+
6113
+ - "homeListFile": 全部文件
6114
+ - "recycleListFile": 回收站
6115
+ - "syncFileList": 同步空间
6116
+
6117
+ - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6118
+
6119
+ .. note::
6120
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6121
+
6122
+ - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6123
+ - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
6124
+ """
6125
+ if not isinstance(payload, dict):
6126
+ payload = {"Page": payload}
6127
+ payload = dict_to_lower_merge(payload, {
6128
+ "driveId": 0,
6129
+ "limit": 100,
6130
+ "next": 0,
6131
+ "category": 1,
6132
+ "orderBy": "file_name",
6133
+ "orderDirection": "asc",
6134
+ "parentFileId": 0,
6135
+ "inDirectSpace": "false",
6136
+ "event": event,
6137
+ "OnlyLookAbnormalFile": 0,
6138
+ "Page": 1,
6139
+ })
6140
+ if not payload.get("trashed"):
6141
+ match payload["event"]:
6142
+ case "recycleListFile":
6143
+ payload["trashed"] = "true"
6144
+ case _:
6145
+ payload["trashed"] = "false"
6146
+ return self.request(
6147
+ "restful/goapi/v1/file/category/list-by-type",
6148
+ params=payload,
6149
+ base_url=base_url,
6150
+ async_=async_,
6151
+ **request_kwargs,
6152
+ )
6153
+
6154
+ @overload
6155
+ def fs_list_new(
6156
+ self,
6157
+ payload: dict | int | str = 0,
6158
+ /,
6159
+ event: str = "homeListFile",
6160
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6161
+ *,
6162
+ async_: Literal[False] = False,
6163
+ **request_kwargs,
6164
+ ) -> dict:
6165
+ ...
6166
+ @overload
6167
+ def fs_list_new(
6168
+ self,
6169
+ payload: dict | int | str = 0,
6170
+ /,
6171
+ event: str = "homeListFile",
6172
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6173
+ *,
6174
+ async_: Literal[True],
6175
+ **request_kwargs,
6176
+ ) -> Coroutine[Any, Any, dict]:
6177
+ ...
6178
+ def fs_list_new(
6179
+ self,
6180
+ payload: dict | int | str = 0,
6181
+ /,
6182
+ event: str = "homeListFile",
6183
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6184
+ *,
6185
+ async_: Literal[False, True] = False,
6186
+ **request_kwargs,
6187
+ ) -> dict | Coroutine[Any, Any, dict]:
6188
+ """获取文件列表(可搜索)
6189
+
6190
+ GET https://www.123pan.com/api/file/list/new
6191
+
6192
+ .. note::
6193
+ 如果返回信息中,"Next" 字段的值为 "-1",代表最后一页(无需再翻页查询)
6194
+
6195
+ :payload:
6196
+ - driveId: int | str = 0
6197
+ - limit: int = 100 💡 分页大小,最多 100 个
6198
+ - next: int = 0 💡 下一批拉取开始的 id
6199
+ - orderBy: str = "file_name" 💡 排序依据
6200
+
6201
+ - "file_id": 文件 id,也可以写作 "fileId"
6202
+ - "file_name": 文件名
6203
+ - "size": 文件大小
6204
+ - "create_at": 创建时间
6205
+ - "update_at": 更新时间
6206
+ - "trashed_at": 删除时间
6207
+ - "share_id": 分享 id
6208
+ - "remain_days": 剩余保留天数
6209
+ - ...
6210
+
6211
+ - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
6212
+ - Page: int = 1 💡 第几页,从 1 开始
6213
+ - parentFileId: int | str = 0 💡 父目录 id
6214
+ - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
6215
+ - inDirectSpace: "false" | "true" = "false"
6216
+ - event: str = "homeListFile" 💡 事件名称
6217
+
6218
+ - "homeListFile": 全部文件
6219
+ - "recycleListFile": 回收站
6220
+ - "syncFileList": 同步空间
6221
+
6222
+ - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6223
+
6224
+ .. note::
6225
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6226
+
6227
+ - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6228
+ - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
6229
+ - RequestSource: int = <default> 💡 浏览器中,在同步空间中为 1
6230
+ """
6231
+ if isinstance(payload, (int, str)):
6232
+ payload = {"parentFileId": payload}
6233
+ payload = dict_to_lower_merge(payload, {
6234
+ "driveId": 0,
6235
+ "limit": 100,
6236
+ "next": 0,
6237
+ "orderBy": "file_name",
6238
+ "orderDirection": "asc",
6239
+ "parentFileId": 0,
6240
+ "inDirectSpace": "false",
6241
+ "event": event,
6242
+ "OnlyLookAbnormalFile": 0,
6243
+ "Page": 1,
6244
+ })
6245
+ if not payload.get("trashed"):
6246
+ match payload["event"]:
6247
+ case "recycleListFile":
6248
+ payload["trashed"] = "true"
6249
+ case _:
6250
+ payload["trashed"] = "false"
6251
+ return self.request(
6252
+ "file/list/new",
6253
+ params=payload,
6254
+ base_url=base_url,
6255
+ async_=async_,
6256
+ **request_kwargs,
6257
+ )
6258
+
6259
+ @overload # type: ignore
6260
+ def fs_mkdir(
6261
+ self,
6262
+ name: str,
6263
+ /,
6264
+ parent_id: int | str = 0,
6265
+ duplicate: Literal[0, 1, 2] = 0,
6266
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6267
+ *,
6268
+ async_: Literal[False] = False,
6269
+ **request_kwargs,
6270
+ ) -> dict:
6271
+ ...
6272
+ @overload
6273
+ def fs_mkdir(
6274
+ self,
6275
+ name: str,
6276
+ /,
6277
+ parent_id: int | str = 0,
6278
+ duplicate: Literal[0, 1, 2] = 0,
6279
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6280
+ *,
6281
+ async_: Literal[True],
6282
+ **request_kwargs,
6283
+ ) -> Coroutine[Any, Any, dict]:
6284
+ ...
6285
+ def fs_mkdir(
6286
+ self,
6287
+ name: str,
6288
+ /,
6289
+ parent_id: int | str = 0,
6290
+ duplicate: Literal[0, 1, 2] = 0,
6291
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6292
+ *,
6293
+ async_: Literal[False, True] = False,
6294
+ **request_kwargs,
6295
+ ) -> dict | Coroutine[Any, Any, dict]:
6296
+ """创建目录
6297
+
6298
+ :param name: 目录名
6299
+ :param parent_id: 父目录 id
6300
+ :param duplicate: 处理同名:0: 复用 1: 保留两者 2: 替换
6301
+ :param async_: 是否异步
6302
+ :param request_kwargs: 其它请求参数
6303
+
6304
+ :return: 接口响应
6305
+ """
6306
+ payload = {"filename": name, "parentFileId": parent_id}
6307
+ if duplicate:
6308
+ payload["NotReuse"] = True
6309
+ payload["duplicate"] = duplicate
6310
+ return self.upload_request(
6311
+ payload,
6312
+ base_url=base_url,
6313
+ async_=async_,
6314
+ **request_kwargs,
6315
+ )
6316
+
6317
+ @overload
6318
+ def fs_move(
6319
+ self,
6320
+ payload: dict | int | str | Iterable[int | str],
6321
+ /,
6322
+ parent_id: int | str = 0,
6323
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6324
+ *,
6325
+ async_: Literal[False] = False,
6326
+ **request_kwargs,
6327
+ ) -> dict:
6328
+ ...
6329
+ @overload
6330
+ def fs_move(
6331
+ self,
6332
+ payload: dict | int | str | Iterable[int | str],
6333
+ /,
6334
+ parent_id: int | str = 0,
6335
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6336
+ *,
6337
+ async_: Literal[True],
6338
+ **request_kwargs,
6339
+ ) -> Coroutine[Any, Any, dict]:
6340
+ ...
6341
+ def fs_move(
6342
+ self,
6343
+ payload: dict | int | str | Iterable[int | str],
6344
+ /,
6345
+ parent_id: int | str = 0,
6346
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6347
+ *,
6348
+ async_: Literal[False, True] = False,
6349
+ **request_kwargs,
6350
+ ) -> dict | Coroutine[Any, Any, dict]:
6351
+ """移动
6352
+
6353
+ POST https://www.123pan.com/api/file/mod_pid
6354
+
6355
+ :payload:
6356
+ - fileIdList: list[FileID]
6357
+
6358
+ .. code:: python
6359
+
6360
+ FileID = {
6361
+ "FileId": int | str
6362
+ }
6363
+
6364
+ - parentFileId: int | str = 0
6365
+ - event: str = "fileMove"
6366
+ """
6367
+ if isinstance(payload, (int, str)):
6368
+ payload = {"fileIdList": [{"FileId": payload}]}
6369
+ elif not isinstance(payload, dict):
6370
+ payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
6371
+ payload = dict_to_lower_merge(payload, {"parentFileId": parent_id, "event": "fileMove"})
6372
+ return self.request(
6373
+ "file/mod_pid",
6374
+ "POST",
6375
+ json=payload,
6376
+ base_url=base_url,
6377
+ async_=async_,
6378
+ **request_kwargs,
6379
+ )
6380
+
6381
+ @overload
6382
+ def fs_refresh(
6383
+ self,
6384
+ payload: dict = {},
6385
+ /,
6386
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6387
+ *,
6388
+ async_: Literal[False] = False,
6389
+ **request_kwargs,
6390
+ ) -> dict:
6391
+ ...
6392
+ @overload
6393
+ def fs_refresh(
6394
+ self,
6395
+ payload: dict = {},
6396
+ /,
6397
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6398
+ *,
6399
+ async_: Literal[True],
6400
+ **request_kwargs,
6401
+ ) -> Coroutine[Any, Any, dict]:
6402
+ ...
6403
+ def fs_refresh(
6404
+ self,
6405
+ payload: dict = {},
6406
+ /,
6407
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6408
+ *,
6409
+ async_: Literal[False, True] = False,
6410
+ **request_kwargs,
6411
+ ) -> dict | Coroutine[Any, Any, dict]:
6412
+ """刷新列表和直链缓存
6413
+
6414
+ POST https://www.123pan.com/api/restful/goapi/v1/cdnLink/cache/refresh
6415
+ """
6416
+ return self.request(
6417
+ "restful/goapi/v1/cdnLink/cache/refresh",
6418
+ "POST",
6419
+ json=payload,
6420
+ base_url=base_url,
6421
+ async_=async_,
6422
+ **request_kwargs,
6423
+ )
6424
+
6425
+ @overload # type: ignore
6426
+ def fs_rename(
6427
+ self,
6428
+ payload: dict,
6429
+ /,
6430
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6431
+ *,
6432
+ async_: Literal[False] = False,
6433
+ **request_kwargs,
6434
+ ) -> dict:
6435
+ ...
6436
+ @overload
6437
+ def fs_rename(
6438
+ self,
6439
+ payload: dict,
6440
+ /,
6441
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6442
+ *,
6443
+ async_: Literal[True],
6444
+ **request_kwargs,
6445
+ ) -> Coroutine[Any, Any, dict]:
6446
+ ...
6447
+ def fs_rename(
6448
+ self,
6449
+ payload: dict,
6450
+ /,
6451
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6452
+ *,
6453
+ async_: Literal[False, True] = False,
6454
+ **request_kwargs,
6455
+ ) -> dict | Coroutine[Any, Any, dict]:
6456
+ """(单个)改名
6457
+
6458
+ POST https://www.123pan.com/api/file/rename
6459
+
6460
+ :payload:
6461
+ - FileId: int | str
6462
+ - fileName: str
6463
+ - driveId: int | str = 0
6464
+ - duplicate: 0 | 1 | 2 = 0 💡 处理同名:0: 提示/忽略 1: 保留两者 2: 替换
6465
+ - event: str = "fileRename"
6466
+ """
6467
+ payload = dict_to_lower_merge(payload, {
6468
+ "driveId": 0,
6469
+ "duplicate": 0,
6470
+ "event": "fileRename",
6471
+ })
6472
+ return self.request(
6473
+ "file/rename",
6474
+ "POST",
6475
+ json=payload,
6476
+ base_url=base_url,
6477
+ async_=async_,
6478
+ **request_kwargs,
6479
+ )
6480
+
6481
+ @overload
6482
+ def fs_star(
6483
+ self,
6484
+ payload: dict | int | str | Iterable[int | str],
6485
+ /,
6486
+ star: bool = True,
6487
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6488
+ *,
6489
+ async_: Literal[False] = False,
6490
+ **request_kwargs,
6491
+ ) -> dict:
6492
+ ...
6493
+ @overload
6494
+ def fs_star(
6495
+ self,
6496
+ payload: dict | int | str | Iterable[int | str],
6497
+ /,
6498
+ star: bool = True,
6499
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6500
+ *,
6501
+ async_: Literal[True],
6502
+ **request_kwargs,
6503
+ ) -> Coroutine[Any, Any, dict]:
6504
+ ...
6505
+ def fs_star(
6506
+ self,
6507
+ payload: dict | int | str | Iterable[int | str],
6508
+ /,
6509
+ star: bool = True,
6510
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6511
+ *,
6512
+ async_: Literal[False, True] = False,
6513
+ **request_kwargs,
6514
+ ) -> dict | Coroutine[Any, Any, dict]:
6515
+ """给文件或目录,设置或取消星标
6516
+
6517
+ POST https://www.123pan.com/api/restful/goapi/v1/file/starred
6518
+
6519
+ :payload:
6520
+ - fileIdList: list[int | str] 💡 id 列表
6521
+ - starredStatus: int = 255 💡 是否设置星标:1:取消 255:设置
6522
+ """
6523
+ if isinstance(payload, (int, str)):
6524
+ payload = {"fileIdList": [payload], "starredStatus": 255}
6525
+ elif not isinstance(payload, dict):
6526
+ if not isinstance(payload, (tuple, list)):
6527
+ payload = list(payload)
6528
+ payload = {"fileIdList": payload, "starredStatus": 255}
6529
+ else:
6530
+ payload.setdefault("starredStatus", 255 if star else 1)
6531
+ return self.request(
6532
+ "restful/goapi/v1/file/starred",
6533
+ "POST",
6534
+ json=payload,
6535
+ base_url=base_url,
6536
+ async_=async_,
6537
+ **request_kwargs,
6538
+ )
6539
+
6540
+ @overload
6541
+ def fs_star_list(
4758
6542
  self,
4759
- payload: dict | int | str | Iterable[int | str],
6543
+ payload: dict | int = 1,
4760
6544
  /,
4761
- parent_id: int | str = 0,
6545
+ event: str = "homeListFile",
4762
6546
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4763
6547
  *,
4764
6548
  async_: Literal[False] = False,
@@ -4766,73 +6550,98 @@ class P123Client(P123OpenClient):
4766
6550
  ) -> dict:
4767
6551
  ...
4768
6552
  @overload
4769
- def fs_copy(
6553
+ def fs_star_list(
4770
6554
  self,
4771
- payload: dict | int | str | Iterable[int | str],
6555
+ payload: dict | int = 1,
4772
6556
  /,
4773
- parent_id: int | str = 0,
6557
+ event: str = "homeListFile",
4774
6558
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4775
6559
  *,
4776
6560
  async_: Literal[True],
4777
6561
  **request_kwargs,
4778
6562
  ) -> Coroutine[Any, Any, dict]:
4779
6563
  ...
4780
- def fs_copy(
6564
+ def fs_star_list(
4781
6565
  self,
4782
- payload: dict | int | str | Iterable[int | str],
6566
+ payload: dict | int = 1,
4783
6567
  /,
4784
- parent_id: int | str = 0,
6568
+ event: str = "homeListFile",
4785
6569
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4786
6570
  *,
4787
6571
  async_: Literal[False, True] = False,
4788
6572
  **request_kwargs,
4789
6573
  ) -> dict | Coroutine[Any, Any, dict]:
4790
- """复制
6574
+ """罗列已星标的文件或目录
4791
6575
 
4792
- POST https://www.123pan.com/api/restful/goapi/v1/file/copy/async
6576
+ GET https://www.123pan.com/api/restful/goapi/v1/file/starred/list
4793
6577
 
4794
6578
  :payload:
4795
- - fileList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
6579
+ - driveId: int | str = 0
6580
+ - next: int = 0 💡 下一批拉取开始的 id
6581
+ - orderBy: str = "file_name" 💡 排序依据
4796
6582
 
4797
- .. code:: python
6583
+ - "file_id": 文件 id,也可以写作 "fileId"
6584
+ - "file_name": 文件名
6585
+ - "size": 文件大小
6586
+ - "create_at": 创建时间
6587
+ - "update_at": 更新时间
6588
+ - "trashed_at": 删除时间
6589
+ - "share_id": 分享 id
6590
+ - "remain_days": 剩余保留天数
6591
+ - ...
4798
6592
 
4799
- File = {
4800
- "FileId": int | str,
4801
- ...
4802
- }
6593
+ - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
6594
+ - Page: int = 1 💡 第几页,从 1 开始
6595
+ - pageSize: int = 100 💡 分页大小,最多 100 个
6596
+ - parentFileId: int | str = 0 💡 父目录 id
6597
+ - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
6598
+ - inDirectSpace: "false" | "true" = "false"
6599
+ - event: str = "homeListFile" 💡 事件名称
4803
6600
 
4804
- - targetFileId: int | str = 0
6601
+ - "homeListFile": 全部文件
6602
+ - "recycleListFile": 回收站
6603
+ - "syncFileList": 同步空间
6604
+
6605
+ - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6606
+
6607
+ .. note::
6608
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6609
+
6610
+ - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6611
+ - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
4805
6612
  """
4806
- def gen_step():
4807
- nonlocal payload
4808
- if not isinstance(payload, dict):
4809
- resp = yield self.fs_info(
4810
- payload,
4811
- base_url=base_url,
4812
- async_=async_,
4813
- **request_kwargs,
4814
- )
4815
- resp["payload"] = payload
4816
- check_response(resp)
4817
- info_list = resp["data"]["infoList"]
4818
- if not info_list:
4819
- raise FileNotFoundError(ENOENT, resp)
4820
- payload = {"fileList": info_list}
4821
- payload = dict_to_lower_merge(payload, targetFileId=parent_id)
4822
- return self.request(
4823
- "restful/goapi/v1/file/copy/async",
4824
- "POST",
4825
- json=payload,
4826
- base_url=base_url,
4827
- async_=async_,
4828
- **request_kwargs,
4829
- )
4830
- return run_gen_step(gen_step, async_)
6613
+ if not isinstance(payload, dict):
6614
+ payload = {"Page": payload}
6615
+ payload = dict_to_lower_merge(payload, {
6616
+ "driveId": 0,
6617
+ "next": 0,
6618
+ "orderBy": "file_name",
6619
+ "orderDirection": "asc",
6620
+ "Page": 1,
6621
+ "pageSize": 100,
6622
+ "parentFileId": 0,
6623
+ "inDirectSpace": "false",
6624
+ "event": event,
6625
+ "OnlyLookAbnormalFile": 0,
6626
+ })
6627
+ if not payload.get("trashed"):
6628
+ match payload["event"]:
6629
+ case "recycleListFile":
6630
+ payload["trashed"] = "true"
6631
+ case _:
6632
+ payload["trashed"] = "false"
6633
+ return self.request(
6634
+ "restful/goapi/v1/file/starred/list",
6635
+ params=payload,
6636
+ base_url=base_url,
6637
+ async_=async_,
6638
+ **request_kwargs,
6639
+ )
4831
6640
 
4832
6641
  @overload
4833
- def fs_detail(
6642
+ def fs_sync_log(
4834
6643
  self,
4835
- payload: dict | int | str,
6644
+ payload: dict | int = 1,
4836
6645
  /,
4837
6646
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4838
6647
  *,
@@ -4841,9 +6650,9 @@ class P123Client(P123OpenClient):
4841
6650
  ) -> dict:
4842
6651
  ...
4843
6652
  @overload
4844
- def fs_detail(
6653
+ def fs_sync_log(
4845
6654
  self,
4846
- payload: dict | int | str,
6655
+ payload: dict | int = 1,
4847
6656
  /,
4848
6657
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4849
6658
  *,
@@ -4851,37 +6660,40 @@ class P123Client(P123OpenClient):
4851
6660
  **request_kwargs,
4852
6661
  ) -> Coroutine[Any, Any, dict]:
4853
6662
  ...
4854
- def fs_detail(
6663
+ def fs_sync_log(
4855
6664
  self,
4856
- payload: dict | int | str,
6665
+ payload: dict | int = 1,
4857
6666
  /,
4858
6667
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4859
6668
  *,
4860
6669
  async_: Literal[False, True] = False,
4861
6670
  **request_kwargs,
4862
6671
  ) -> dict | Coroutine[Any, Any, dict]:
4863
- """获取文件或目录详情(文件数、目录数、总大小)
6672
+ """获取同步空间的操作记录
4864
6673
 
4865
- GET https://www.123pan.com/api/file/detail
6674
+ GET https://www.123pan.com/api/restful/goapi/v1/sync-disk/file/log
4866
6675
 
4867
6676
  :payload:
4868
- - fileID: int | str
6677
+ - page: int = 1 💡 第几页
6678
+ - pageSize: int = 100 💡 每页大小
6679
+ - searchData: str = <default> 💡 搜索关键字
4869
6680
  """
4870
- if isinstance(payload, (int, str)):
4871
- payload = {"fileID": payload}
6681
+ if not isinstance(payload, dict):
6682
+ payload = {"page": payload, "pageSize": 100}
4872
6683
  return self.request(
4873
- "file/detail",
6684
+ "restful/goapi/v1/sync-disk/file/log",
4874
6685
  params=payload,
4875
6686
  base_url=base_url,
4876
6687
  async_=async_,
4877
6688
  **request_kwargs,
4878
6689
  )
4879
6690
 
4880
- @overload
4881
- def fs_delete(
6691
+ @overload # type: ignore
6692
+ def fs_trash(
4882
6693
  self,
4883
- payload: dict | int | str | Iterable[int | str] = 0,
6694
+ payload: dict | int | str | Iterable[int | str],
4884
6695
  /,
6696
+ event: str = "intoRecycle",
4885
6697
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4886
6698
  *,
4887
6699
  async_: Literal[False] = False,
@@ -4889,51 +6701,64 @@ class P123Client(P123OpenClient):
4889
6701
  ) -> dict:
4890
6702
  ...
4891
6703
  @overload
4892
- def fs_delete(
6704
+ def fs_trash(
4893
6705
  self,
4894
- payload: dict | int | str | Iterable[int | str] = 0,
6706
+ payload: dict | int | str | Iterable[int | str],
4895
6707
  /,
6708
+ event: str = "intoRecycle",
4896
6709
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4897
6710
  *,
4898
6711
  async_: Literal[True],
4899
6712
  **request_kwargs,
4900
6713
  ) -> Coroutine[Any, Any, dict]:
4901
6714
  ...
4902
- def fs_delete(
6715
+ def fs_trash(
4903
6716
  self,
4904
- payload: dict | int | str | Iterable[int | str] = 0,
6717
+ payload: dict | int | str | Iterable[int | str],
4905
6718
  /,
6719
+ event: str = "intoRecycle",
4906
6720
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4907
6721
  *,
4908
6722
  async_: Literal[False, True] = False,
4909
6723
  **request_kwargs,
4910
6724
  ) -> dict | Coroutine[Any, Any, dict]:
4911
- """彻底删除
4912
-
4913
- POST https://www.123pan.com/api/file/delete
6725
+ """操作回收站
4914
6726
 
4915
- .. hint::
4916
- 彻底删除文件前,文件必须要在回收站中,否则无法删除
6727
+ POST https://www.123pan.com/api/file/trash
4917
6728
 
4918
6729
  :payload:
4919
- - fileIdList: list[FileID]
6730
+ - fileTrashInfoList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
4920
6731
 
4921
6732
  .. code:: python
4922
6733
 
4923
- FileID = {
4924
- "FileId": int | str
6734
+ File = {
6735
+ "FileId": int | str,
6736
+ ...
4925
6737
  }
4926
6738
 
4927
- - event: str = "recycleDelete"
6739
+ - driveId: int = 0
6740
+ - event: str = "intoRecycle" 💡 事件类型
6741
+
6742
+ - "intoRecycle": 移入回收站
6743
+ - "recycleRestore": 移出回收站
6744
+
6745
+ - operation: bool = <default>
6746
+ - operatePlace: int = <default>
6747
+ - RequestSource: int = <default> 💡 浏览器中,在同步空间中为 1
4928
6748
  """
4929
6749
  if isinstance(payload, (int, str)):
4930
- payload = {"fileIdList": [{"FileId": payload}]}
6750
+ payload = {"fileTrashInfoList": [{"FileId": payload}]}
4931
6751
  elif not isinstance(payload, dict):
4932
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
4933
- payload = cast(dict, payload)
4934
- payload.setdefault("event", "recycleDelete")
6752
+ payload = {"fileTrashInfoList": [{"FileId": fid} for fid in payload]}
6753
+ payload = dict_to_lower_merge(payload, {"driveId": 0, "event": event})
6754
+ if payload.get("operation") is None:
6755
+ match payload["event"]:
6756
+ case "recycleRestore":
6757
+ payload["operation"] = False
6758
+ case _:
6759
+ payload["operation"] = True
4935
6760
  return self.request(
4936
- "file/delete",
6761
+ "file/trash",
4937
6762
  "POST",
4938
6763
  json=payload,
4939
6764
  base_url=base_url,
@@ -4942,9 +6767,9 @@ class P123Client(P123OpenClient):
4942
6767
  )
4943
6768
 
4944
6769
  @overload
4945
- def fs_get_path(
6770
+ def fs_trash_clear(
4946
6771
  self,
4947
- payload: dict | int,
6772
+ payload: dict = {"event": "recycleClear"},
4948
6773
  /,
4949
6774
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4950
6775
  *,
@@ -4953,9 +6778,9 @@ class P123Client(P123OpenClient):
4953
6778
  ) -> dict:
4954
6779
  ...
4955
6780
  @overload
4956
- def fs_get_path(
6781
+ def fs_trash_clear(
4957
6782
  self,
4958
- payload: dict | int,
6783
+ payload: dict = {"event": "recycleClear"},
4959
6784
  /,
4960
6785
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4961
6786
  *,
@@ -4963,26 +6788,25 @@ class P123Client(P123OpenClient):
4963
6788
  **request_kwargs,
4964
6789
  ) -> Coroutine[Any, Any, dict]:
4965
6790
  ...
4966
- def fs_get_path(
6791
+ def fs_trash_clear(
4967
6792
  self,
4968
- payload: dict | int,
6793
+ payload: dict = {"event": "recycleClear"},
4969
6794
  /,
4970
6795
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4971
6796
  *,
4972
6797
  async_: Literal[False, True] = False,
4973
6798
  **request_kwargs,
4974
6799
  ) -> dict | Coroutine[Any, Any, dict]:
4975
- """获取某个 id 对应的祖先节点列表
6800
+ """清空回收站
4976
6801
 
4977
- POST https://www.123pan.com/api/file/get_path
6802
+ POST https://www.123pan.com/api/file/trash_delete_all
4978
6803
 
4979
6804
  :payload:
4980
- - fileId: int 💡 文件 id
6805
+ - event: str = "recycleClear"
4981
6806
  """
4982
- if isinstance(payload, int):
4983
- payload = {"fileId": payload}
6807
+ payload.setdefault("event", "recycleClear")
4984
6808
  return self.request(
4985
- "file/get_path",
6809
+ "file/trash_delete_all",
4986
6810
  "POST",
4987
6811
  json=payload,
4988
6812
  base_url=base_url,
@@ -4991,68 +6815,51 @@ class P123Client(P123OpenClient):
4991
6815
  )
4992
6816
 
4993
6817
  @overload
4994
- def fs_info(
4995
- self,
4996
- payload: dict | int | str | Iterable[int | str] = 0,
4997
- /,
6818
+ @staticmethod
6819
+ def fs_video_play_conf(
4998
6820
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6821
+ request: None | Callable = None,
4999
6822
  *,
5000
6823
  async_: Literal[False] = False,
5001
6824
  **request_kwargs,
5002
6825
  ) -> dict:
5003
6826
  ...
5004
6827
  @overload
5005
- def fs_info(
5006
- self,
5007
- payload: dict | int | str | Iterable[int | str] = 0,
5008
- /,
6828
+ @staticmethod
6829
+ def fs_video_play_conf(
5009
6830
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6831
+ request: None | Callable = None,
5010
6832
  *,
5011
6833
  async_: Literal[True],
5012
6834
  **request_kwargs,
5013
6835
  ) -> Coroutine[Any, Any, dict]:
5014
6836
  ...
5015
- def fs_info(
5016
- self,
5017
- payload: dict | int | str | Iterable[int | str] = 0,
5018
- /,
6837
+ @staticmethod
6838
+ def fs_video_play_conf(
5019
6839
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5020
- *,
5021
- async_: Literal[False, True] = False,
5022
- **request_kwargs,
5023
- ) -> dict | Coroutine[Any, Any, dict]:
5024
- """获取文件信息
5025
-
5026
- POST https://www.123pan.com/api/file/info
5027
-
5028
- :payload:
5029
- - fileIdList: list[FileID]
5030
-
5031
- .. code:: python
5032
-
5033
- FileID = {
5034
- "FileId": int | str
5035
- }
5036
- """
5037
- if isinstance(payload, (int, str)):
5038
- payload = {"fileIdList": [{"FileId": payload}]}
5039
- elif not isinstance(payload, dict):
5040
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5041
- return self.request(
5042
- "file/info",
5043
- "POST",
5044
- json=payload,
5045
- base_url=base_url,
5046
- async_=async_,
6840
+ request: None | Callable = None,
6841
+ *,
6842
+ async_: Literal[False, True] = False,
6843
+ **request_kwargs,
6844
+ ) -> dict | Coroutine[Any, Any, dict]:
6845
+ """获取视频播放列表的配置信息
6846
+
6847
+ GET https://www.123pan.com/api/video/play/conf
6848
+ """
6849
+ request_kwargs.setdefault("parse", default_parse)
6850
+ if request is None:
6851
+ request = get_default_request()
6852
+ request_kwargs["async_"] = async_
6853
+ return request(
6854
+ url=complete_url("/api/get/server/time", base_url),
5047
6855
  **request_kwargs,
5048
6856
  )
5049
6857
 
5050
- @overload # type: ignore
5051
- def fs_list(
6858
+ @overload
6859
+ def fs_video_play_list(
5052
6860
  self,
5053
6861
  payload: dict | int | str = 0,
5054
6862
  /,
5055
- event: str = "homeListFile",
5056
6863
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5057
6864
  *,
5058
6865
  async_: Literal[False] = False,
@@ -5060,95 +6867,54 @@ class P123Client(P123OpenClient):
5060
6867
  ) -> dict:
5061
6868
  ...
5062
6869
  @overload
5063
- def fs_list(
6870
+ def fs_video_play_list(
5064
6871
  self,
5065
6872
  payload: dict | int | str = 0,
5066
6873
  /,
5067
- event: str = "homeListFile",
5068
6874
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5069
6875
  *,
5070
6876
  async_: Literal[True],
5071
6877
  **request_kwargs,
5072
6878
  ) -> Coroutine[Any, Any, dict]:
5073
6879
  ...
5074
- def fs_list(
6880
+ def fs_video_play_list(
5075
6881
  self,
5076
6882
  payload: dict | int | str = 0,
5077
6883
  /,
5078
- event: str = "homeListFile",
5079
6884
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5080
6885
  *,
5081
6886
  async_: Literal[False, True] = False,
5082
6887
  **request_kwargs,
5083
6888
  ) -> dict | Coroutine[Any, Any, dict]:
5084
- """获取文件列表(可搜索)
5085
-
5086
- GET https://www.123pan.com/api/file/list
6889
+ """获取某个目录下的视频列表
5087
6890
 
5088
- .. note::
5089
- 如果返回信息中,"Next" 字段的值为 "-1",代表最后一页(无需再翻页查询)
6891
+ GET https://www.123pan.com/api/file/video/play/list
5090
6892
 
5091
6893
  :payload:
5092
- - driveId: int | str = 0
5093
- - limit: int = 100 💡 分页大小,最多 100 个
5094
- - next: int = 0 💡 下一批拉取开始的 id
5095
- - orderBy: str = "file_id" 💡 排序依据
5096
-
5097
- - "file_id": 文件 id
5098
- - "file_name": 文件名
5099
- - "size": 文件大小
5100
- - "create_at": 创建时间
5101
- - "update_at": 更新时间
5102
- - "share_id": 分享 id
5103
- - ...
5104
-
5105
- - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
5106
- - Page: int = <default> 💡 第几页,从 1 开始,可以是 0
5107
- - parentFileId: int | str = 0 💡 父目录 id
5108
- - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
5109
- - inDirectSpace: "false" | "true" = "false"
5110
- - event: str = "homeListFile" 💡 事件名称
5111
-
5112
- - "homeListFile": 全部文件
5113
- - "recycleListFile": 回收站
5114
- - "syncFileList": 同步空间
5115
-
5116
- - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
5117
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
5118
- - OnlyLookAbnormalFile: int = <default>
6894
+ - page: int = 1
6895
+ - page_size: int = 100
6896
+ - parent_file_id: int = 0
5119
6897
  """
5120
- if isinstance(payload, (int, str)):
5121
- payload = {"parentFileId": payload}
5122
- payload = dict_to_lower_merge(payload, {
5123
- "driveId": 0,
5124
- "limit": 100,
5125
- "next": 0,
5126
- "orderBy": "file_id",
5127
- "orderDirection": "asc",
5128
- "parentFileId": 0,
5129
- "inDirectSpace": "false",
5130
- "event": event,
5131
- })
5132
- if not payload.get("trashed"):
5133
- match payload["event"]:
5134
- case "recycleListFile":
5135
- payload["trashed"] = "true"
5136
- case _:
5137
- payload["trashed"] = "false"
6898
+ if not isinstance(payload, dict):
6899
+ payload = {"parent_file_id": payload}
6900
+ payload.setdefault("page", 1)
6901
+ payload.setdefault("page_size", 100)
5138
6902
  return self.request(
5139
- "file/list",
6903
+ "file/video/play/list",
5140
6904
  params=payload,
5141
6905
  base_url=base_url,
5142
6906
  async_=async_,
5143
6907
  **request_kwargs,
5144
6908
  )
5145
6909
 
6910
+ ########## Qrcode API ##########
6911
+
5146
6912
  @overload
5147
- def fs_list_new(
5148
- self,
5149
- payload: dict | int | str = 0,
6913
+ @staticmethod
6914
+ def login_passport(
6915
+ payload: dict,
5150
6916
  /,
5151
- event: str = "homeListFile",
6917
+ request: None | Callable = None,
5152
6918
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5153
6919
  *,
5154
6920
  async_: Literal[False] = False,
@@ -5156,211 +6922,141 @@ class P123Client(P123OpenClient):
5156
6922
  ) -> dict:
5157
6923
  ...
5158
6924
  @overload
5159
- def fs_list_new(
5160
- self,
5161
- payload: dict | int | str = 0,
6925
+ @staticmethod
6926
+ def login_passport(
6927
+ payload: dict,
5162
6928
  /,
5163
- event: str = "homeListFile",
6929
+ request: None | Callable = None,
5164
6930
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5165
6931
  *,
5166
6932
  async_: Literal[True],
5167
6933
  **request_kwargs,
5168
6934
  ) -> Coroutine[Any, Any, dict]:
5169
6935
  ...
5170
- def fs_list_new(
5171
- self,
5172
- payload: dict | int | str = 0,
6936
+ @staticmethod
6937
+ def login_passport(
6938
+ payload: dict,
5173
6939
  /,
5174
- event: str = "homeListFile",
6940
+ request: None | Callable = None,
5175
6941
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5176
6942
  *,
5177
6943
  async_: Literal[False, True] = False,
5178
6944
  **request_kwargs,
5179
6945
  ) -> dict | Coroutine[Any, Any, dict]:
5180
- """获取文件列表(可搜索)
6946
+ """使用账号和密码登录
5181
6947
 
5182
- GET https://www.123pan.com/api/file/list/new
6948
+ POST https://www.123pan.com/api/user/sign_in
5183
6949
 
5184
6950
  .. note::
5185
- 如果返回信息中,"Next" 字段的值为 "-1",代表最后一页(无需再翻页查询)
6951
+ 获取的 token 有效期 30 天
5186
6952
 
5187
6953
  :payload:
5188
- - driveId: int | str = 0
5189
- - limit: int = 100 💡 分页大小,最多 100 个
5190
- - next: int = 0 💡 下一批拉取开始的 id
5191
- - orderBy: str = "file_id" 💡 排序依据
5192
-
5193
- - "fileId": 文件 id
5194
- - "file_name": 文件名
5195
- - "size": 文件大小
5196
- - "create_at": 创建时间
5197
- - "update_at": 更新时间
5198
- - "share_id": 分享 id
5199
- - ...
5200
-
5201
- - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
5202
- - Page: int = 1 💡 第几页,从 1 开始
5203
- - parentFileId: int | str = 0 💡 父目录 id
5204
- - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
5205
- - inDirectSpace: "false" | "true" = "false"
5206
- - event: str = "homeListFile" 💡 事件名称
5207
-
5208
- - "homeListFile": 全部文件
5209
- - "recycleListFile": 回收站
5210
- - "syncFileList": 同步空间
5211
-
5212
- - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
5213
-
5214
- .. note::
5215
- 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
5216
-
5217
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
5218
- - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
5219
- - RequestSource: int = <default> 💡 浏览器中,在同步空间中为 1
6954
+ - passport: int | str 💡 手机号或邮箱
6955
+ - password: str 💡 密码
6956
+ - remember: bool = True 💡 是否记住密码(不用管)
5220
6957
  """
5221
- if isinstance(payload, (int, str)):
5222
- payload = {"parentFileId": payload}
5223
- payload = dict_to_lower_merge(payload, {
5224
- "driveId": 0,
5225
- "limit": 100,
5226
- "next": 0,
5227
- "orderBy": "file_id",
5228
- "orderDirection": "asc",
5229
- "parentFileId": 0,
5230
- "inDirectSpace": "false",
5231
- "event": event,
5232
- "OnlyLookAbnormalFile": 0,
5233
- "Page": 1,
5234
- })
5235
- if not payload.get("trashed"):
5236
- match payload["event"]:
5237
- case "recycleListFile":
5238
- payload["trashed"] = "true"
5239
- case _:
5240
- payload["trashed"] = "false"
5241
- return self.request(
5242
- "file/list/new",
5243
- params=payload,
5244
- base_url=base_url,
5245
- async_=async_,
5246
- **request_kwargs,
5247
- )
6958
+ api = complete_url("user/sign_in", base_url)
6959
+ request_kwargs.setdefault("parse", default_parse)
6960
+ if request is None:
6961
+ request = get_default_request()
6962
+ request_kwargs["async_"] = async_
6963
+ return request(url=api, method="POST", json=payload, **request_kwargs)
5248
6964
 
5249
- @overload # type: ignore
5250
- def fs_mkdir(
5251
- self,
5252
- name: str,
6965
+ @overload
6966
+ @staticmethod
6967
+ def login_qrcode_bind_wx_code(
6968
+ payload: dict,
5253
6969
  /,
5254
- parent_id: int | str = 0,
5255
- duplicate: Literal[0, 1, 2] = 0,
5256
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6970
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
6971
+ request: None | Callable = None,
5257
6972
  *,
5258
6973
  async_: Literal[False] = False,
5259
6974
  **request_kwargs,
5260
6975
  ) -> dict:
5261
6976
  ...
5262
6977
  @overload
5263
- def fs_mkdir(
5264
- self,
5265
- name: str,
6978
+ @staticmethod
6979
+ def login_qrcode_bind_wx_code(
6980
+ payload: dict,
5266
6981
  /,
5267
- parent_id: int | str = 0,
5268
- duplicate: Literal[0, 1, 2] = 0,
5269
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6982
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
6983
+ request: None | Callable = None,
5270
6984
  *,
5271
6985
  async_: Literal[True],
5272
6986
  **request_kwargs,
5273
6987
  ) -> Coroutine[Any, Any, dict]:
5274
6988
  ...
5275
- def fs_mkdir(
5276
- self,
5277
- name: str,
6989
+ @staticmethod
6990
+ def login_qrcode_bind_wx_code(
6991
+ payload: dict,
5278
6992
  /,
5279
- parent_id: int | str = 0,
5280
- duplicate: Literal[0, 1, 2] = 0,
5281
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6993
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
6994
+ request: None | Callable = None,
5282
6995
  *,
5283
6996
  async_: Literal[False, True] = False,
5284
6997
  **request_kwargs,
5285
6998
  ) -> dict | Coroutine[Any, Any, dict]:
5286
- """创建目录
6999
+ """绑定微信号
5287
7000
 
5288
- :param name: 目录名
5289
- :param parent_id: 父目录 id
5290
- :param duplicate: 处理同名:0: 复用 1: 保留两者 2: 替换
5291
- :param async_: 是否异步
5292
- :param request_kwargs: 其它请求参数
7001
+ POST https://login.123pan.com/api/user/qr-code/bind_wx_code
5293
7002
 
5294
- :return: 接口响应
7003
+ :payload:
7004
+ - uniID: str 💡 二维码 id
7005
+ - wxcode: str 💡 微信码
5295
7006
  """
5296
- payload = {"filename": name, "parentFileId": parent_id}
5297
- if duplicate:
5298
- payload["NotReuse"] = True
5299
- payload["duplicate"] = duplicate
5300
- return self.upload_request(
5301
- payload,
5302
- base_url=base_url,
5303
- async_=async_,
7007
+ request_kwargs.setdefault("parse", default_parse)
7008
+ if request is None:
7009
+ request = get_default_request()
7010
+ request_kwargs["async_"] = async_
7011
+ return request(
7012
+ url=complete_url("user/qr-code/bind_wx_code", base_url),
7013
+ method="POST",
7014
+ json=payload,
5304
7015
  **request_kwargs,
5305
7016
  )
5306
7017
 
5307
7018
  @overload
5308
- def fs_move(
7019
+ def login_qrcode_confirm(
5309
7020
  self,
5310
- payload: dict | int | str | Iterable[int | str],
7021
+ payload: dict | str,
5311
7022
  /,
5312
- parent_id: int | str = 0,
5313
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7023
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
5314
7024
  *,
5315
7025
  async_: Literal[False] = False,
5316
7026
  **request_kwargs,
5317
7027
  ) -> dict:
5318
7028
  ...
5319
7029
  @overload
5320
- def fs_move(
7030
+ def login_qrcode_confirm(
5321
7031
  self,
5322
- payload: dict | int | str | Iterable[int | str],
7032
+ payload: dict | str,
5323
7033
  /,
5324
- parent_id: int | str = 0,
5325
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7034
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
5326
7035
  *,
5327
7036
  async_: Literal[True],
5328
7037
  **request_kwargs,
5329
7038
  ) -> Coroutine[Any, Any, dict]:
5330
7039
  ...
5331
- def fs_move(
7040
+ def login_qrcode_confirm(
5332
7041
  self,
5333
- payload: dict | int | str | Iterable[int | str],
7042
+ payload: dict | str,
5334
7043
  /,
5335
- parent_id: int | str = 0,
5336
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7044
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
5337
7045
  *,
5338
7046
  async_: Literal[False, True] = False,
5339
7047
  **request_kwargs,
5340
7048
  ) -> dict | Coroutine[Any, Any, dict]:
5341
- """移动
7049
+ """确认扫码登录
5342
7050
 
5343
- POST https://www.123pan.com/api/file/mod_pid
7051
+ POST https://login.123pan.com/api/user/qr-code/login
5344
7052
 
5345
7053
  :payload:
5346
- - fileIdList: list[FileID]
5347
-
5348
- .. code:: python
5349
-
5350
- FileID = {
5351
- "FileId": int | str
5352
- }
5353
-
5354
- - parentFileId: int | str = 0
5355
- - event: str = "fileMove"
7054
+ - uniID: str 💡 二维码 id
5356
7055
  """
5357
- if isinstance(payload, (int, str)):
5358
- payload = {"fileIdList": [{"FileId": payload}]}
5359
- elif not isinstance(payload, dict):
5360
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5361
- payload = dict_to_lower_merge(payload, {"parentFileId": parent_id, "event": "fileMove"})
7056
+ if not isinstance(payload, dict):
7057
+ payload = {"uniID": payload}
5362
7058
  return self.request(
5363
- "file/mod_pid",
7059
+ "user/qr-code/login",
5364
7060
  "POST",
5365
7061
  json=payload,
5366
7062
  base_url=base_url,
@@ -5369,237 +7065,221 @@ class P123Client(P123OpenClient):
5369
7065
  )
5370
7066
 
5371
7067
  @overload
5372
- def fs_fresh(
5373
- self,
5374
- payload: dict = {},
7068
+ @staticmethod
7069
+ def login_qrcode_deny(
7070
+ payload: dict | str,
5375
7071
  /,
5376
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7072
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7073
+ request: None | Callable = None,
5377
7074
  *,
5378
7075
  async_: Literal[False] = False,
5379
7076
  **request_kwargs,
5380
7077
  ) -> dict:
5381
7078
  ...
5382
7079
  @overload
5383
- def fs_fresh(
5384
- self,
5385
- payload: dict = {},
7080
+ @staticmethod
7081
+ def login_qrcode_deny(
7082
+ payload: dict | str,
5386
7083
  /,
5387
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7084
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7085
+ request: None | Callable = None,
5388
7086
  *,
5389
7087
  async_: Literal[True],
5390
7088
  **request_kwargs,
5391
7089
  ) -> Coroutine[Any, Any, dict]:
5392
7090
  ...
5393
- def fs_fresh(
5394
- self,
5395
- payload: dict = {},
7091
+ @staticmethod
7092
+ def login_qrcode_deny(
7093
+ payload: dict | str,
5396
7094
  /,
5397
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7095
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7096
+ request: None | Callable = None,
5398
7097
  *,
5399
7098
  async_: Literal[False, True] = False,
5400
7099
  **request_kwargs,
5401
7100
  ) -> dict | Coroutine[Any, Any, dict]:
5402
- """刷新列表和直链缓存
7101
+ """更新扫码状态为:已取消(loginStatus=2)
5403
7102
 
5404
- POST https://www.123pan.com/api/restful/goapi/v1/cdnLink/cache/refresh
7103
+ POST https://login.123pan.com/api/user/qr-code/deny
7104
+
7105
+ :payload:
7106
+ - uniID: str 💡 二维码 id
5405
7107
  """
5406
- return self.request(
5407
- "restful/goapi/v1/cdnLink/cache/refresh",
5408
- "POST",
7108
+ if not isinstance(payload, dict):
7109
+ payload = {"uniID": payload}
7110
+ request_kwargs.setdefault("parse", default_parse)
7111
+ if request is None:
7112
+ request = get_default_request()
7113
+ request_kwargs["async_"] = async_
7114
+ return request(
7115
+ url=complete_url("user/qr-code/deny", base_url),
7116
+ method="POST",
5409
7117
  json=payload,
5410
- base_url=base_url,
5411
- async_=async_,
5412
7118
  **request_kwargs,
5413
7119
  )
5414
7120
 
5415
- @overload # type: ignore
5416
- def fs_rename(
5417
- self,
5418
- payload: dict,
5419
- /,
5420
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7121
+ @overload
7122
+ @staticmethod
7123
+ def login_qrcode_generate(
7124
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7125
+ request: None | Callable = None,
5421
7126
  *,
5422
7127
  async_: Literal[False] = False,
5423
7128
  **request_kwargs,
5424
7129
  ) -> dict:
5425
7130
  ...
5426
7131
  @overload
5427
- def fs_rename(
5428
- self,
5429
- payload: dict,
5430
- /,
5431
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7132
+ @staticmethod
7133
+ def login_qrcode_generate(
7134
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7135
+ request: None | Callable = None,
5432
7136
  *,
5433
7137
  async_: Literal[True],
5434
7138
  **request_kwargs,
5435
7139
  ) -> Coroutine[Any, Any, dict]:
5436
7140
  ...
5437
- def fs_rename(
5438
- self,
5439
- payload: dict,
5440
- /,
5441
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7141
+ @staticmethod
7142
+ def login_qrcode_generate(
7143
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7144
+ request: None | Callable = None,
5442
7145
  *,
5443
7146
  async_: Literal[False, True] = False,
5444
7147
  **request_kwargs,
5445
7148
  ) -> dict | Coroutine[Any, Any, dict]:
5446
- """(单个)改名
5447
-
5448
- POST https://www.123pan.com/api/file/rename
7149
+ """产生二维码
5449
7150
 
5450
- :payload:
5451
- - FileId: int | str
5452
- - fileName: str
5453
- - driveId: int | str = 0
5454
- - duplicate: 0 | 1 | 2 = 0 💡 处理同名:0: 提示/忽略 1: 保留两者 2: 替换
5455
- - event: str = "fileRename"
7151
+ GET https://login.123pan.com/api/user/qr-code/generate
5456
7152
  """
5457
- payload = dict_to_lower_merge(payload, {
5458
- "driveId": 0,
5459
- "duplicate": 0,
5460
- "event": "fileRename",
5461
- })
5462
- return self.request(
5463
- "file/rename",
5464
- "POST",
5465
- json=payload,
5466
- base_url=base_url,
5467
- async_=async_,
7153
+ request_kwargs.setdefault("parse", default_parse)
7154
+ if request is None:
7155
+ request = get_default_request()
7156
+ request_kwargs["async_"] = async_
7157
+ return request(
7158
+ url=complete_url("user/qr-code/generate", base_url),
5468
7159
  **request_kwargs,
5469
7160
  )
5470
7161
 
5471
7162
  @overload
5472
- def fs_sync_log(
5473
- self,
5474
- payload: dict | int = 1,
7163
+ @staticmethod
7164
+ def login_qrcode_result(
7165
+ payload: dict | str,
5475
7166
  /,
5476
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7167
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7168
+ request: None | Callable = None,
5477
7169
  *,
5478
7170
  async_: Literal[False] = False,
5479
7171
  **request_kwargs,
5480
7172
  ) -> dict:
5481
7173
  ...
5482
7174
  @overload
5483
- def fs_sync_log(
5484
- self,
5485
- payload: dict | int = 1,
7175
+ @staticmethod
7176
+ def login_qrcode_result(
7177
+ payload: dict | str,
5486
7178
  /,
5487
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7179
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7180
+ request: None | Callable = None,
5488
7181
  *,
5489
7182
  async_: Literal[True],
5490
7183
  **request_kwargs,
5491
7184
  ) -> Coroutine[Any, Any, dict]:
5492
7185
  ...
5493
- def fs_sync_log(
5494
- self,
5495
- payload: dict | int = 1,
7186
+ @staticmethod
7187
+ def login_qrcode_result(
7188
+ payload: dict | str,
5496
7189
  /,
5497
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7190
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7191
+ request: None | Callable = None,
5498
7192
  *,
5499
7193
  async_: Literal[False, True] = False,
5500
7194
  **request_kwargs,
5501
7195
  ) -> dict | Coroutine[Any, Any, dict]:
5502
- """获取同步空间的操作记录
7196
+ """获取扫码结果
5503
7197
 
5504
- GET https://www.123pan.com/api/restful/goapi/v1/sync-disk/file/log
7198
+ GET https://login.123pan.com/api/user/qr-code/result
7199
+
7200
+ .. note::
7201
+ 返回值中有个 "loginStatus" 字段,值为数字,分别表示的意思为:
7202
+
7203
+ - 0: 等待扫码
7204
+ - 1: 已扫码
7205
+ - 2: 已取消
7206
+ - 3: 已登录
7207
+ - 4: 已失效
5505
7208
 
5506
7209
  :payload:
5507
- - page: int = 1 💡 第几页
5508
- - pageSize: int = 100 💡 每页大小
5509
- - searchData: str = <default> 💡 搜索关键字
7210
+ - uniID: str 💡 二维码 id
5510
7211
  """
5511
7212
  if not isinstance(payload, dict):
5512
- payload = {"page": payload, "pageSize": 100}
5513
- return self.request(
5514
- "restful/goapi/v1/sync-disk/file/log",
7213
+ payload = {"uniID": payload}
7214
+ request_kwargs.setdefault("parse", default_parse)
7215
+ if request is None:
7216
+ request = get_default_request()
7217
+ request_kwargs["async_"] = async_
7218
+ return request(
7219
+ url=complete_url("user/qr-code/result", base_url),
5515
7220
  params=payload,
5516
- base_url=base_url,
5517
- async_=async_,
5518
7221
  **request_kwargs,
5519
7222
  )
5520
7223
 
5521
- @overload # type: ignore
5522
- def fs_trash(
5523
- self,
5524
- payload: dict | int | str | Iterable[int | str],
7224
+ @overload
7225
+ @staticmethod
7226
+ def login_qrcode_scan(
7227
+ payload: dict | str,
5525
7228
  /,
5526
- event: str = "intoRecycle",
5527
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7229
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7230
+ request: None | Callable = None,
5528
7231
  *,
5529
7232
  async_: Literal[False] = False,
5530
7233
  **request_kwargs,
5531
7234
  ) -> dict:
5532
7235
  ...
5533
7236
  @overload
5534
- def fs_trash(
5535
- self,
5536
- payload: dict | int | str | Iterable[int | str],
7237
+ @staticmethod
7238
+ def login_qrcode_scan(
7239
+ payload: dict | str,
5537
7240
  /,
5538
- event: str = "intoRecycle",
5539
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7241
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7242
+ request: None | Callable = None,
5540
7243
  *,
5541
7244
  async_: Literal[True],
5542
7245
  **request_kwargs,
5543
7246
  ) -> Coroutine[Any, Any, dict]:
5544
7247
  ...
5545
- def fs_trash(
5546
- self,
5547
- payload: dict | int | str | Iterable[int | str],
7248
+ @staticmethod
7249
+ def login_qrcode_scan(
7250
+ payload: dict | str,
5548
7251
  /,
5549
- event: str = "intoRecycle",
5550
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7252
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7253
+ request: None | Callable = None,
5551
7254
  *,
5552
7255
  async_: Literal[False, True] = False,
5553
7256
  **request_kwargs,
5554
7257
  ) -> dict | Coroutine[Any, Any, dict]:
5555
- """操作回收站
7258
+ """更新扫码状态为:已扫码(loginStatus=1)
5556
7259
 
5557
- POST https://www.123pan.com/api/file/trash
7260
+ POST https://login.123pan.com/api/user/qr-code/scan
5558
7261
 
5559
7262
  :payload:
5560
- - fileTrashInfoList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
5561
-
5562
- .. code:: python
5563
-
5564
- File = {
5565
- "FileId": int | str,
5566
- ...
5567
- }
5568
-
5569
- - driveId: int = 0
5570
- - event: str = "intoRecycle" 💡 事件类型
5571
-
5572
- - "intoRecycle": 移入回收站
5573
- - "recycleRestore": 移出回收站
5574
-
5575
- - operation: bool = <default>
5576
- - operatePlace: int = <default>
5577
- - RequestSource: int = <default>
7263
+ - uniID: str 💡 二维码 id
7264
+ - scanPlatform: int = 4 💡 扫码的平台代码,微信是 4
5578
7265
  """
5579
- if isinstance(payload, (int, str)):
5580
- payload = {"fileTrashInfoList": [{"FileId": payload}]}
5581
- elif not isinstance(payload, dict):
5582
- payload = {"fileTrashInfoList": [{"FileId": fid} for fid in payload]}
5583
- payload = dict_to_lower_merge(payload, {"driveId": 0, "event": event})
5584
- if payload.get("operation") is None:
5585
- match payload["event"]:
5586
- case "recycleRestore":
5587
- payload["operation"] = False
5588
- case _:
5589
- payload["operation"] = True
5590
- return self.request(
5591
- "file/trash",
5592
- "POST",
7266
+ if not isinstance(payload, dict):
7267
+ payload = {"uniID": payload}
7268
+ payload.setdefault("scanPlatform", 4)
7269
+ request_kwargs.setdefault("parse", default_parse)
7270
+ if request is None:
7271
+ request = get_default_request()
7272
+ request_kwargs["async_"] = async_
7273
+ return request(
7274
+ url=complete_url("user/qr-code/scan", base_url),
7275
+ method="POST",
5593
7276
  json=payload,
5594
- base_url=base_url,
5595
- async_=async_,
5596
7277
  **request_kwargs,
5597
7278
  )
5598
7279
 
5599
7280
  @overload
5600
- def fs_trash_clear(
7281
+ def logout(
5601
7282
  self,
5602
- payload: dict = {"event": "recycleClear"},
5603
7283
  /,
5604
7284
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5605
7285
  *,
@@ -5608,9 +7288,8 @@ class P123Client(P123OpenClient):
5608
7288
  ) -> dict:
5609
7289
  ...
5610
7290
  @overload
5611
- def fs_trash_clear(
7291
+ def logout(
5612
7292
  self,
5613
- payload: dict = {"event": "recycleClear"},
5614
7293
  /,
5615
7294
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5616
7295
  *,
@@ -5618,32 +7297,28 @@ class P123Client(P123OpenClient):
5618
7297
  **request_kwargs,
5619
7298
  ) -> Coroutine[Any, Any, dict]:
5620
7299
  ...
5621
- def fs_trash_clear(
7300
+ def logout(
5622
7301
  self,
5623
- payload: dict = {"event": "recycleClear"},
5624
7302
  /,
5625
7303
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5626
7304
  *,
5627
7305
  async_: Literal[False, True] = False,
5628
7306
  **request_kwargs,
5629
7307
  ) -> dict | Coroutine[Any, Any, dict]:
5630
- """清空回收站
5631
-
5632
- POST https://www.123pan.com/api/file/trash_delete_all
7308
+ """退出登录
5633
7309
 
5634
- :payload:
5635
- - event: str = "recycleClear"
7310
+ POST https://www.123pan.com/api/user/logout
5636
7311
  """
5637
- payload.setdefault("event", "recycleClear")
5638
7312
  return self.request(
5639
- "file/trash_delete_all",
7313
+ "user/logout",
5640
7314
  "POST",
5641
- json=payload,
5642
7315
  base_url=base_url,
5643
7316
  async_=async_,
5644
7317
  **request_kwargs,
5645
7318
  )
5646
7319
 
7320
+ ########## Offline Download API ##########
7321
+
5647
7322
  @overload
5648
7323
  def offline_task_delete(
5649
7324
  self,
@@ -5681,7 +7356,7 @@ class P123Client(P123OpenClient):
5681
7356
 
5682
7357
  :payload:
5683
7358
  - task_ids: list[int] 💡 任务 id 列表
5684
- - status_arr: list[0|1|2] = [] 💡 状态列表:0:等待 1:运行 2:完成
7359
+ - status_arr: list[ 0 | 1 | 2 | 3 | 4 ] = [] 💡 状态列表:0:进行中 1:下载失败 2:下载成功 3:重试中
5685
7360
  """
5686
7361
  if isinstance(payload, int):
5687
7362
  payload = {"task_ids": [payload], "status_arr": []}
@@ -5736,12 +7411,12 @@ class P123Client(P123OpenClient):
5736
7411
  :payload:
5737
7412
  - current_page: int = 1
5738
7413
  - page_size: 100
5739
- - status_arr: list[0|1|2] = [0, 1] 💡 状态列表:0:等待 1:运行 2:完成
7414
+ - status_arr: list[ 0 | 1 | 2 | 3 | 4 ] = [0, 1, 2, 3, 4] 💡 状态列表:0:进行中 1:下载失败 2:下载成功 3:重试中
5740
7415
  """
5741
7416
  if isinstance(payload, int):
5742
- payload = {"current_page": payload, "page_size": 100, "status_arr": [0, 1]}
7417
+ payload = {"current_page": payload, "page_size": 100, "status_arr": [0, 1, 2, 3, 4]}
5743
7418
  else:
5744
- payload = {"current_page": 1, "page_size": 100, "status_arr": [0, 1], **payload}
7419
+ payload = {"current_page": 1, "page_size": 100, "status_arr": [0, 1, 2, 3, 4], **payload}
5745
7420
  return self.request(
5746
7421
  "offline_download/task/list",
5747
7422
  "POST",
@@ -5908,6 +7583,8 @@ class P123Client(P123OpenClient):
5908
7583
  **request_kwargs,
5909
7584
  )
5910
7585
 
7586
+ ########## Share API ##########
7587
+
5911
7588
  @overload
5912
7589
  def share_cancel(
5913
7590
  self,
@@ -6684,6 +8361,8 @@ class P123Client(P123OpenClient):
6684
8361
  **request_kwargs,
6685
8362
  )
6686
8363
 
8364
+ ########## Upload API ##########
8365
+
6687
8366
  @overload
6688
8367
  def upload_auth(
6689
8368
  self,
@@ -7407,6 +9086,8 @@ class P123Client(P123OpenClient):
7407
9086
  )
7408
9087
  return run_gen_step(gen_step, async_)
7409
9088
 
9089
+ ########## User API ##########
9090
+
7410
9091
  @overload
7411
9092
  def user_device_list(
7412
9093
  self,
@@ -7496,11 +9177,9 @@ class P123Client(P123OpenClient):
7496
9177
  )
7497
9178
 
7498
9179
  @overload
7499
- @staticmethod
7500
- def user_login(
7501
- payload: dict,
9180
+ def user_referral_info(
9181
+ self,
7502
9182
  /,
7503
- request: None | Callable = None,
7504
9183
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7505
9184
  *,
7506
9185
  async_: Literal[False] = False,
@@ -7508,45 +9187,72 @@ class P123Client(P123OpenClient):
7508
9187
  ) -> dict:
7509
9188
  ...
7510
9189
  @overload
7511
- @staticmethod
7512
- def user_login(
7513
- payload: dict,
9190
+ def user_referral_info(
9191
+ self,
7514
9192
  /,
7515
- request: None | Callable = None,
7516
9193
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7517
9194
  *,
7518
9195
  async_: Literal[True],
7519
9196
  **request_kwargs,
7520
9197
  ) -> Coroutine[Any, Any, dict]:
7521
9198
  ...
7522
- @staticmethod
7523
- def user_login(
7524
- payload: dict,
9199
+ def user_referral_info(
9200
+ self,
7525
9201
  /,
7526
- request: None | Callable = None,
7527
9202
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7528
9203
  *,
7529
9204
  async_: Literal[False, True] = False,
7530
9205
  **request_kwargs,
7531
9206
  ) -> dict | Coroutine[Any, Any, dict]:
7532
- """使用账号和密码登录
9207
+ """用户拉人头信息
7533
9208
 
7534
- POST https://www.123pan.com/api/user/sign_in
9209
+ GET https://www.123pan.com/api/referral/my-info
9210
+ """
9211
+ return self.request(
9212
+ "referral/my-info",
9213
+ base_url=base_url,
9214
+ async_=async_,
9215
+ **request_kwargs,
9216
+ )
7535
9217
 
7536
- .. note::
7537
- 获取的 token 有效期 30 天
9218
+ @overload
9219
+ def user_report_info(
9220
+ self,
9221
+ /,
9222
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
9223
+ *,
9224
+ async_: Literal[False] = False,
9225
+ **request_kwargs,
9226
+ ) -> dict:
9227
+ ...
9228
+ @overload
9229
+ def user_report_info(
9230
+ self,
9231
+ /,
9232
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
9233
+ *,
9234
+ async_: Literal[True],
9235
+ **request_kwargs,
9236
+ ) -> Coroutine[Any, Any, dict]:
9237
+ ...
9238
+ def user_report_info(
9239
+ self,
9240
+ /,
9241
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
9242
+ *,
9243
+ async_: Literal[False, True] = False,
9244
+ **request_kwargs,
9245
+ ) -> dict | Coroutine[Any, Any, dict]:
9246
+ """用户推送消息配置
7538
9247
 
7539
- :payload:
7540
- - passport: int | str 💡 手机号或邮箱
7541
- - password: str 💡 密码
7542
- - remember: bool = True 💡 是否记住密码(不用管)
9248
+ GET https://www.123pan.com/b/api/restful/goapi/v1/user/report/info
7543
9249
  """
7544
- api = complete_url("user/sign_in", base_url)
7545
- request_kwargs.setdefault("parse", default_parse)
7546
- if request is None:
7547
- request = get_default_request()
7548
- request_kwargs["async_"] = async_
7549
- return request(url=api, method="POST", json=payload, **request_kwargs)
9250
+ return self.request(
9251
+ "restful/goapi/v1/user/report/info",
9252
+ base_url=base_url,
9253
+ async_=async_,
9254
+ **request_kwargs,
9255
+ )
7550
9256
 
7551
9257
  @overload
7552
9258
  def user_use_history(
@@ -7596,8 +9302,31 @@ class P123Client(P123OpenClient):
7596
9302
  **request_kwargs,
7597
9303
  )
7598
9304
 
7599
- # TODO: 添加扫码登录接口,以及通过扫码登录的方法
7600
- # TODO: 添加 同步空间 和 直链空间 的操作接口
7601
- # TODO: 添加 图床 的操作接口
7602
- # TODO: 添加 视频转码 的操作接口
9305
+
9306
+ with temp_globals():
9307
+ CRE_CLIENT_API_search: Final = re_compile(r"^ +((?:GET|POST|PUT|DELETE|PATCH) .*)", MULTILINE).search
9308
+ for name in dir(P123Client):
9309
+ method = getattr(P123Client, name)
9310
+ if not (callable(method) and method.__doc__):
9311
+ continue
9312
+ match = CRE_CLIENT_API_search(method.__doc__)
9313
+ if match is not None:
9314
+ api = match[1]
9315
+ name = "P123Client." + name
9316
+ CLIENT_METHOD_API_MAP[name] = api
9317
+ try:
9318
+ CLIENT_API_METHODS_MAP[api].append(name)
9319
+ except KeyError:
9320
+ CLIENT_API_METHODS_MAP[api] = [name]
9321
+
9322
+
7603
9323
  # TODO: 对于某些工具的接口封装,例如 重复文件清理
9324
+ # TODO: 同步空间
9325
+ # TODO: 直链空间
9326
+ # TODO: 图床
9327
+ # TODO: 视频转码
9328
+ # TODO: 移入保险箱
9329
+ # TODO: 申诉和申诉列表
9330
+ # TODO: 歌单(以及批量加入歌单)
9331
+ # TODO: 最近查看
9332
+ # TODO: 消息通知列表和操作