p123client 0.0.6.9__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
@@ -1262,7 +1579,7 @@ class P123OpenClient:
1262
1579
 
1263
1580
  :payload:
1264
1581
  - limit: int = 100 💡 分页大小,最多 100
1265
- - orderBy: str = "file_id" 💡 排序依据
1582
+ - orderBy: str = "file_name" 💡 排序依据
1266
1583
 
1267
1584
  - "file_id": 文件 id
1268
1585
  - "file_name": 文件名
@@ -1287,7 +1604,7 @@ class P123OpenClient:
1287
1604
  payload = {"parentFileId": payload}
1288
1605
  payload = dict_to_lower_merge(payload, {
1289
1606
  "limit": 100,
1290
- "orderBy": "file_id",
1607
+ "orderBy": "file_name",
1291
1608
  "orderDirection": "asc",
1292
1609
  "page": 1,
1293
1610
  "parentFileId": 0,
@@ -1617,6 +1934,8 @@ class P123OpenClient:
1617
1934
  payload = {"fileIDs": payload}
1618
1935
  return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
1619
1936
 
1937
+ ########## Offline Download API ##########
1938
+
1620
1939
  @overload
1621
1940
  def offline_download(
1622
1941
  self,
@@ -1726,6 +2045,8 @@ class P123OpenClient:
1726
2045
  payload = {"taskID": payload}
1727
2046
  return self.request(api, params=payload, async_=async_, **request_kwargs)
1728
2047
 
2048
+ ########## Oss API ##########
2049
+
1729
2050
  @overload
1730
2051
  def oss_copy(
1731
2052
  self,
@@ -2864,6 +3185,8 @@ class P123OpenClient:
2864
3185
  }) from e
2865
3186
  return run_gen_step(gen_step, async_)
2866
3187
 
3188
+ ########## Share API ##########
3189
+
2867
3190
  @overload
2868
3191
  def share_create(
2869
3192
  self,
@@ -3072,6 +3395,8 @@ class P123OpenClient:
3072
3395
  payload = {"limit": payload}
3073
3396
  return self.request(api, params=payload, async_=async_, **request_kwargs)
3074
3397
 
3398
+ ########## Transcode API ##########
3399
+
3075
3400
  @overload
3076
3401
  def transcode_delete(
3077
3402
  self,
@@ -3618,6 +3943,8 @@ class P123OpenClient:
3618
3943
  api = complete_url("/api/v1/transcode/video", base_url)
3619
3944
  return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
3620
3945
 
3946
+ ########## Upload API ##########
3947
+
3621
3948
  @overload
3622
3949
  def upload_create(
3623
3950
  self,
@@ -4220,6 +4547,8 @@ class P123OpenClient:
4220
4547
  }) from e
4221
4548
  return run_gen_step(gen_step, async_)
4222
4549
 
4550
+ ########## User API ##########
4551
+
4223
4552
  @overload
4224
4553
  def user_info(
4225
4554
  self,
@@ -4274,6 +4603,8 @@ class P123OpenClient:
4274
4603
  api = complete_url("/api/v1/user/info", base_url)
4275
4604
  return self.request(api, async_=async_, **request_kwargs)
4276
4605
 
4606
+ ########## API Aliases ##########
4607
+
4277
4608
  login_open = login
4278
4609
  login_access_token_open = login_access_token
4279
4610
  login_auth_open = login_auth
@@ -4341,19 +4672,36 @@ class P123OpenClient:
4341
4672
 
4342
4673
 
4343
4674
  class P123Client(P123OpenClient):
4675
+ """123 的客户端对象
4344
4676
 
4677
+ :param passport: 手机号或邮箱
4678
+ :param password: 密码
4679
+ :param token: 123 的访问令牌
4680
+ """
4345
4681
  def __init__(
4346
4682
  self,
4347
4683
  /,
4348
- passport: int | str = "",
4684
+ passport: int | str | PathLike = "",
4349
4685
  password: str = "",
4350
- token: str = "",
4686
+ token: None | str | PathLike = None,
4351
4687
  ):
4352
- self.passport = passport
4353
- self.password = password
4354
- self.token = token
4355
- 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:
4356
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()
4357
4705
 
4358
4706
  @overload # type: ignore
4359
4707
  def login(
@@ -4412,193 +4760,236 @@ class P123Client(P123OpenClient):
4412
4760
  else:
4413
4761
  password = self.password
4414
4762
  def gen_step():
4415
- resp = yield self.user_login(
4416
- {"passport": passport, "password": password, "remember": remember},
4417
- base_url=base_url,
4418
- async_=async_,
4419
- **request_kwargs,
4420
- )
4421
- check_response(resp)
4422
- self.token = resp["data"]["token"]
4423
- 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
4424
4781
  return run_gen_step(gen_step, async_)
4425
4782
 
4426
4783
  @overload
4427
- @staticmethod
4428
- def app_dydomain(
4429
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4430
- 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,
4431
4789
  *,
4432
4790
  async_: Literal[False] = False,
4433
4791
  **request_kwargs,
4434
- ) -> dict:
4792
+ ) -> Self:
4435
4793
  ...
4436
4794
  @overload
4437
- @staticmethod
4438
- def app_dydomain(
4439
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4440
- 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,
4441
4800
  *,
4442
4801
  async_: Literal[True],
4443
4802
  **request_kwargs,
4444
- ) -> Coroutine[Any, Any, dict]:
4803
+ ) -> Coroutine[Any, Any, Self]:
4445
4804
  ...
4446
- @staticmethod
4447
- def app_dydomain(
4448
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4449
- 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,
4450
4810
  *,
4451
4811
  async_: Literal[False, True] = False,
4452
4812
  **request_kwargs,
4453
- ) -> dict | Coroutine[Any, Any, dict]:
4454
- """获取 123 网盘的各种域名
4813
+ ) -> Self | Coroutine[Any, Any, Self]:
4814
+ """再执行一次登录
4455
4815
 
4456
- 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: 客户端实例
4457
4827
  """
4458
- request_kwargs["url"] = complete_url("/api/dydomain", base_url)
4459
- request_kwargs.setdefault("parse", default_parse)
4460
- if request is None:
4461
- request = get_default_request()
4462
- request_kwargs["async_"] = async_
4463
- 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_)
4464
4847
 
4465
4848
  @overload
4466
- @staticmethod
4467
- def app_server_time(
4468
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4469
- request: None | Callable = None,
4849
+ def login_qrcode_auto(
4850
+ self,
4851
+ /,
4852
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4470
4853
  *,
4471
4854
  async_: Literal[False] = False,
4472
4855
  **request_kwargs,
4473
4856
  ) -> dict:
4474
4857
  ...
4475
4858
  @overload
4476
- @staticmethod
4477
- def app_server_time(
4478
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4479
- request: None | Callable = None,
4859
+ def login_qrcode_auto(
4860
+ self,
4861
+ /,
4862
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4480
4863
  *,
4481
4864
  async_: Literal[True],
4482
4865
  **request_kwargs,
4483
4866
  ) -> Coroutine[Any, Any, dict]:
4484
4867
  ...
4485
- @staticmethod
4486
- def app_server_time(
4487
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4488
- request: None | Callable = None,
4868
+ def login_qrcode_auto(
4869
+ self,
4870
+ /,
4871
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4489
4872
  *,
4490
4873
  async_: Literal[False, True] = False,
4491
4874
  **request_kwargs,
4492
4875
  ) -> dict | Coroutine[Any, Any, dict]:
4493
- """获取 123 网盘的服务器时间戳
4876
+ """执行一次自动扫码,但并不因此更新 `self.token`
4494
4877
 
4495
- GET https://www.123pan.com/api/get/server/time
4878
+ :param base_url: 接口的基地址
4879
+ :param async_: 是否异步
4880
+ :param request_kwargs: 其它请求参数
4881
+
4882
+ :return: 接口响应
4496
4883
  """
4497
- request_kwargs["url"] = complete_url("/api/get/server/time", base_url)
4498
- request_kwargs.setdefault("parse", default_parse)
4499
- if request is None:
4500
- request = get_default_request()
4501
- request_kwargs["async_"] = async_
4502
- 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_)
4503
4907
 
4504
4908
  @overload
4505
- def download_info(
4506
- self,
4507
- payload: dict | int | str,
4909
+ @classmethod
4910
+ def login_with_qrcode(
4911
+ cls,
4508
4912
  /,
4509
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4913
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4510
4914
  *,
4511
4915
  async_: Literal[False] = False,
4512
4916
  **request_kwargs,
4513
4917
  ) -> dict:
4514
4918
  ...
4515
4919
  @overload
4516
- def download_info(
4517
- self,
4518
- payload: dict | int | str,
4920
+ @classmethod
4921
+ def login_with_qrcode(
4922
+ cls,
4519
4923
  /,
4520
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4924
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4521
4925
  *,
4522
4926
  async_: Literal[True],
4523
4927
  **request_kwargs,
4524
4928
  ) -> Coroutine[Any, Any, dict]:
4525
4929
  ...
4526
- def download_info(
4527
- self,
4528
- payload: dict | int | str,
4930
+ @classmethod
4931
+ def login_with_qrcode(
4932
+ cls,
4529
4933
  /,
4530
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4934
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4531
4935
  *,
4532
4936
  async_: Literal[False, True] = False,
4533
4937
  **request_kwargs,
4534
4938
  ) -> dict | Coroutine[Any, Any, dict]:
4535
- """获取下载信息
4536
-
4537
- POST https://www.123pan.com/api/file/download_info
4939
+ """二维码扫码登录
4538
4940
 
4539
- .. hint::
4540
- 即使文件已经被删除,只要还有 S3KeyFlag 和 Etag (即 MD5) 就依然可以下载
4541
-
4542
- 你完全可以构造这样的查询参数
4543
-
4544
- .. code:: python
4545
-
4546
- payload = {
4547
- "Etag": "...", # 必填,文件的 MD5
4548
- "FileID": 0, # 可以随便填
4549
- "FileName": "a", # 随便填一个名字
4550
- "S3KeyFlag": str # 必填,格式为 f"{UID}-0",UID 就是上传此文件的用户的 UID,如果此文件是由你上传的,则可从 `P123Client.user_info` 的响应中获取
4551
- "Size": 0, # 可以随便填,填了可能搜索更准确
4552
- }
4553
-
4554
- .. note::
4555
- 获取的直链有效期是 24 小时
4941
+ :param base_url: 接口的基地址
4942
+ :param async_: 是否异步
4943
+ :param request_kwargs: 其它请求参数
4556
4944
 
4557
- :payload:
4558
- - Etag: str 💡 文件的 MD5 散列值
4559
- - S3KeyFlag: str
4560
- - FileName: str = <default> 💡 默认用 Etag(即 MD5)作为文件名
4561
- - FileID: int | str = 0
4562
- - Size: int = <default>
4563
- - Type: int = 0
4564
- - driveId: int | str = 0
4565
- - ...
4945
+ :return: 接口响应
4566
4946
  """
4567
4947
  def gen_step():
4568
- nonlocal payload
4569
- update_headers_in_kwargs(request_kwargs, platform="android")
4570
- if not isinstance(payload, dict):
4571
- resp = yield self.fs_info(
4572
- 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,
4573
4963
  base_url=base_url,
4574
4964
  async_=async_,
4575
4965
  **request_kwargs,
4576
4966
  )
4577
- resp["payload"] = payload
4578
4967
  check_response(resp)
4579
- if not (info_list := resp["data"]["infoList"]):
4580
- raise FileNotFoundError(ENOENT, resp)
4581
- payload = cast(dict, info_list[0])
4582
- if payload["Type"]:
4583
- raise IsADirectoryError(EISDIR, resp)
4584
- payload = dict_to_lower_merge(
4585
- payload, {"driveId": 0, "Type": 0, "FileID": 0})
4586
- if "filename" not in payload:
4587
- payload["filename"] = payload["etag"]
4588
- return self.request(
4589
- "file/download_info",
4590
- "POST",
4591
- json=payload,
4592
- base_url=base_url,
4593
- async_=async_,
4594
- **request_kwargs,
4595
- )
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}")
4596
4985
  return run_gen_step(gen_step, async_)
4597
4986
 
4987
+ ########## App API ##########
4988
+
4598
4989
  @overload
4599
- def download_info_batch(
4990
+ def app_config(
4600
4991
  self,
4601
- payload: dict | int | str | Iterable[int | str],
4992
+ payload: dict | str = "OfflineDownload",
4602
4993
  /,
4603
4994
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4604
4995
  *,
@@ -4607,9 +4998,9 @@ class P123Client(P123OpenClient):
4607
4998
  ) -> dict:
4608
4999
  ...
4609
5000
  @overload
4610
- def download_info_batch(
5001
+ def app_config(
4611
5002
  self,
4612
- payload: dict | int | str | Iterable[int | str],
5003
+ payload: dict | str = "OfflineDownload",
4613
5004
  /,
4614
5005
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4615
5006
  *,
@@ -4617,37 +5008,26 @@ class P123Client(P123OpenClient):
4617
5008
  **request_kwargs,
4618
5009
  ) -> Coroutine[Any, Any, dict]:
4619
5010
  ...
4620
- def download_info_batch(
5011
+ def app_config(
4621
5012
  self,
4622
- payload: dict | int | str | Iterable[int | str],
5013
+ payload: dict | str = "OfflineDownload",
4623
5014
  /,
4624
5015
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4625
5016
  *,
4626
5017
  async_: Literal[False, True] = False,
4627
5018
  **request_kwargs,
4628
5019
  ) -> dict | Coroutine[Any, Any, dict]:
4629
- """获取批量下载信息
4630
-
4631
- POST https://www.123pan.com/api/file/batch_download_info
5020
+ """获取配置信息
4632
5021
 
4633
- .. warning::
4634
- 会把一些文件或目录以 zip 包的形式下载,但非会员有流量限制,所以还是推荐用 `P123Client.download_info` 逐个获取下载链接并下载
5022
+ POST https://www.123pan.com/api/config/get
4635
5023
 
4636
5024
  :payload:
4637
- - fileIdList: list[FileID]
4638
-
4639
- .. code:: python
4640
-
4641
- FileID = {
4642
- "FileId": int | str
4643
- }
5025
+ - business_key: str 💡 配置键名(字段)
4644
5026
  """
4645
- if isinstance(payload, (int, str)):
4646
- payload = {"fileIdList": [{"FileId": payload}]}
4647
- elif not isinstance(payload, dict):
4648
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5027
+ if not isinstance(payload, dict):
5028
+ payload = {"business_key": payload}
4649
5029
  return self.request(
4650
- "file/batch_download_info",
5030
+ "config/get",
4651
5031
  "POST",
4652
5032
  json=payload,
4653
5033
  base_url=base_url,
@@ -4656,65 +5036,533 @@ class P123Client(P123OpenClient):
4656
5036
  )
4657
5037
 
4658
5038
  @overload
4659
- def download_url(
4660
- self,
4661
- payload: dict | int | str,
4662
- /,
5039
+ @staticmethod
5040
+ def app_dydomain(
5041
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5042
+ request: None | Callable = None,
4663
5043
  *,
4664
5044
  async_: Literal[False] = False,
4665
5045
  **request_kwargs,
4666
- ) -> str:
5046
+ ) -> dict:
4667
5047
  ...
4668
5048
  @overload
4669
- def download_url(
4670
- self,
4671
- payload: dict | int | str,
4672
- /,
5049
+ @staticmethod
5050
+ def app_dydomain(
5051
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5052
+ request: None | Callable = None,
4673
5053
  *,
4674
5054
  async_: Literal[True],
4675
5055
  **request_kwargs,
4676
- ) -> Coroutine[Any, Any, str]:
5056
+ ) -> Coroutine[Any, Any, dict]:
4677
5057
  ...
4678
- def download_url(
4679
- self,
4680
- payload: dict | int | str,
4681
- /,
5058
+ @staticmethod
5059
+ def app_dydomain(
5060
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5061
+ request: None | Callable = None,
4682
5062
  *,
4683
5063
  async_: Literal[False, True] = False,
4684
5064
  **request_kwargs,
4685
- ) -> str | Coroutine[Any, Any, str]:
4686
- """获取下载链接
4687
-
4688
- .. note::
4689
- `payload` 支持多种格式的输入,按下面的规则按顺序进行判断:
4690
-
4691
- 1. 如果是 `int` 或 `str`,则视为文件 id,必须在你的网盘中存在此文件
4692
- 2. 如果是 `dict`(不区分大小写),有 "S3KeyFlag", "Etag" 和 "Size" 的值,则直接获取链接,文件不必在你网盘中
4693
- 3. 如果是 `dict`(不区分大小写),有 "Etag" 和 "Size" 的值,则会先秒传(临时文件路径为 /.tempfile)再获取链接,文件不必在你网盘中
4694
- 4. 如果是 `dict`(不区分大小写),有 "FileID",则会先获取信息,再获取链接,必须在你的网盘中存在此文件
4695
- 5. 否则会报错 ValueError
4696
-
4697
- :params payload: 文件 id 或者文件信息,文件信息必须包含的信息如下:
5065
+ ) -> dict | Coroutine[Any, Any, dict]:
5066
+ """获取 123 网盘的各种域名
4698
5067
 
4699
- - FileID: int | str 💡 下载链接
4700
- - S3KeyFlag: str 💡 s3 存储名
4701
- - Etag: str 💡 文件的 MD5 散列值
4702
- - Size: int 💡 文件大小
4703
- - 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
+ )
4704
5078
 
4705
- :params async_: 是否异步
4706
- :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
4707
5108
 
4708
- :return: 下载链接
5109
+ GET https://www.123pan.com/api/v3/3rd/app-id
4709
5110
  """
4710
- def gen_step():
4711
- nonlocal payload
4712
- if isinstance(payload, dict):
4713
- payload = dict_to_lower(payload)
4714
- if not ("size" in payload and "etag" in payload):
4715
- if fileid := payload.get("fileid"):
4716
- resp = yield self.fs_info(fileid, async_=async_, **request_kwargs)
4717
- check_response(resp)
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"):
5564
+ resp = yield self.fs_info(fileid, async_=async_, **request_kwargs)
5565
+ check_response(resp)
4718
5566
  if not (info_list := resp["data"]["infoList"]):
4719
5567
  raise P123OSError(ENOENT, resp)
4720
5568
  info = info_list[0]
@@ -4753,15 +5601,948 @@ class P123Client(P123OpenClient):
4753
5601
  **request_kwargs,
4754
5602
  )
4755
5603
  check_response(resp)
4756
- 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
+ )
4757
5722
  return run_gen_step(gen_step, async_)
4758
5723
 
4759
5724
  @overload
4760
- 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(
4761
6542
  self,
4762
- payload: dict | int | str | Iterable[int | str],
6543
+ payload: dict | int = 1,
4763
6544
  /,
4764
- parent_id: int | str = 0,
6545
+ event: str = "homeListFile",
4765
6546
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4766
6547
  *,
4767
6548
  async_: Literal[False] = False,
@@ -4769,73 +6550,98 @@ class P123Client(P123OpenClient):
4769
6550
  ) -> dict:
4770
6551
  ...
4771
6552
  @overload
4772
- def fs_copy(
6553
+ def fs_star_list(
4773
6554
  self,
4774
- payload: dict | int | str | Iterable[int | str],
6555
+ payload: dict | int = 1,
4775
6556
  /,
4776
- parent_id: int | str = 0,
6557
+ event: str = "homeListFile",
4777
6558
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4778
6559
  *,
4779
6560
  async_: Literal[True],
4780
6561
  **request_kwargs,
4781
6562
  ) -> Coroutine[Any, Any, dict]:
4782
6563
  ...
4783
- def fs_copy(
6564
+ def fs_star_list(
4784
6565
  self,
4785
- payload: dict | int | str | Iterable[int | str],
6566
+ payload: dict | int = 1,
4786
6567
  /,
4787
- parent_id: int | str = 0,
6568
+ event: str = "homeListFile",
4788
6569
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4789
6570
  *,
4790
6571
  async_: Literal[False, True] = False,
4791
6572
  **request_kwargs,
4792
6573
  ) -> dict | Coroutine[Any, Any, dict]:
4793
- """复制
6574
+ """罗列已星标的文件或目录
4794
6575
 
4795
- 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
4796
6577
 
4797
6578
  :payload:
4798
- - fileList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
6579
+ - driveId: int | str = 0
6580
+ - next: int = 0 💡 下一批拉取开始的 id
6581
+ - orderBy: str = "file_name" 💡 排序依据
4799
6582
 
4800
- .. 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
+ - ...
4801
6592
 
4802
- File = {
4803
- "FileId": int | str,
4804
- ...
4805
- }
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" 💡 事件名称
4806
6600
 
4807
- - 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
4808
6612
  """
4809
- def gen_step():
4810
- nonlocal payload
4811
- if not isinstance(payload, dict):
4812
- resp = yield self.fs_info(
4813
- payload,
4814
- base_url=base_url,
4815
- async_=async_,
4816
- **request_kwargs,
4817
- )
4818
- resp["payload"] = payload
4819
- check_response(resp)
4820
- info_list = resp["data"]["infoList"]
4821
- if not info_list:
4822
- raise FileNotFoundError(ENOENT, resp)
4823
- payload = {"fileList": info_list}
4824
- payload = dict_to_lower_merge(payload, targetFileId=parent_id)
4825
- return self.request(
4826
- "restful/goapi/v1/file/copy/async",
4827
- "POST",
4828
- json=payload,
4829
- base_url=base_url,
4830
- async_=async_,
4831
- **request_kwargs,
4832
- )
4833
- 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
+ )
4834
6640
 
4835
6641
  @overload
4836
- def fs_detail(
6642
+ def fs_sync_log(
4837
6643
  self,
4838
- payload: dict | int | str,
6644
+ payload: dict | int = 1,
4839
6645
  /,
4840
6646
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4841
6647
  *,
@@ -4844,9 +6650,9 @@ class P123Client(P123OpenClient):
4844
6650
  ) -> dict:
4845
6651
  ...
4846
6652
  @overload
4847
- def fs_detail(
6653
+ def fs_sync_log(
4848
6654
  self,
4849
- payload: dict | int | str,
6655
+ payload: dict | int = 1,
4850
6656
  /,
4851
6657
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4852
6658
  *,
@@ -4854,37 +6660,40 @@ class P123Client(P123OpenClient):
4854
6660
  **request_kwargs,
4855
6661
  ) -> Coroutine[Any, Any, dict]:
4856
6662
  ...
4857
- def fs_detail(
6663
+ def fs_sync_log(
4858
6664
  self,
4859
- payload: dict | int | str,
6665
+ payload: dict | int = 1,
4860
6666
  /,
4861
6667
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4862
6668
  *,
4863
6669
  async_: Literal[False, True] = False,
4864
6670
  **request_kwargs,
4865
6671
  ) -> dict | Coroutine[Any, Any, dict]:
4866
- """获取文件或目录详情(文件数、目录数、总大小)
6672
+ """获取同步空间的操作记录
4867
6673
 
4868
- GET https://www.123pan.com/api/file/detail
6674
+ GET https://www.123pan.com/api/restful/goapi/v1/sync-disk/file/log
4869
6675
 
4870
6676
  :payload:
4871
- - fileID: int | str
6677
+ - page: int = 1 💡 第几页
6678
+ - pageSize: int = 100 💡 每页大小
6679
+ - searchData: str = <default> 💡 搜索关键字
4872
6680
  """
4873
- if isinstance(payload, (int, str)):
4874
- payload = {"fileID": payload}
6681
+ if not isinstance(payload, dict):
6682
+ payload = {"page": payload, "pageSize": 100}
4875
6683
  return self.request(
4876
- "file/detail",
6684
+ "restful/goapi/v1/sync-disk/file/log",
4877
6685
  params=payload,
4878
6686
  base_url=base_url,
4879
6687
  async_=async_,
4880
6688
  **request_kwargs,
4881
6689
  )
4882
6690
 
4883
- @overload
4884
- def fs_delete(
6691
+ @overload # type: ignore
6692
+ def fs_trash(
4885
6693
  self,
4886
- payload: dict | int | str | Iterable[int | str] = 0,
6694
+ payload: dict | int | str | Iterable[int | str],
4887
6695
  /,
6696
+ event: str = "intoRecycle",
4888
6697
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4889
6698
  *,
4890
6699
  async_: Literal[False] = False,
@@ -4892,51 +6701,64 @@ class P123Client(P123OpenClient):
4892
6701
  ) -> dict:
4893
6702
  ...
4894
6703
  @overload
4895
- def fs_delete(
6704
+ def fs_trash(
4896
6705
  self,
4897
- payload: dict | int | str | Iterable[int | str] = 0,
6706
+ payload: dict | int | str | Iterable[int | str],
4898
6707
  /,
6708
+ event: str = "intoRecycle",
4899
6709
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4900
6710
  *,
4901
6711
  async_: Literal[True],
4902
6712
  **request_kwargs,
4903
6713
  ) -> Coroutine[Any, Any, dict]:
4904
6714
  ...
4905
- def fs_delete(
6715
+ def fs_trash(
4906
6716
  self,
4907
- payload: dict | int | str | Iterable[int | str] = 0,
6717
+ payload: dict | int | str | Iterable[int | str],
4908
6718
  /,
6719
+ event: str = "intoRecycle",
4909
6720
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4910
6721
  *,
4911
6722
  async_: Literal[False, True] = False,
4912
6723
  **request_kwargs,
4913
6724
  ) -> dict | Coroutine[Any, Any, dict]:
4914
- """彻底删除
4915
-
4916
- POST https://www.123pan.com/api/file/delete
6725
+ """操作回收站
4917
6726
 
4918
- .. hint::
4919
- 彻底删除文件前,文件必须要在回收站中,否则无法删除
6727
+ POST https://www.123pan.com/api/file/trash
4920
6728
 
4921
6729
  :payload:
4922
- - fileIdList: list[FileID]
6730
+ - fileTrashInfoList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
4923
6731
 
4924
6732
  .. code:: python
4925
6733
 
4926
- FileID = {
4927
- "FileId": int | str
6734
+ File = {
6735
+ "FileId": int | str,
6736
+ ...
4928
6737
  }
4929
6738
 
4930
- - 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
4931
6748
  """
4932
6749
  if isinstance(payload, (int, str)):
4933
- payload = {"fileIdList": [{"FileId": payload}]}
6750
+ payload = {"fileTrashInfoList": [{"FileId": payload}]}
4934
6751
  elif not isinstance(payload, dict):
4935
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
4936
- payload = cast(dict, payload)
4937
- 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
4938
6760
  return self.request(
4939
- "file/delete",
6761
+ "file/trash",
4940
6762
  "POST",
4941
6763
  json=payload,
4942
6764
  base_url=base_url,
@@ -4945,9 +6767,9 @@ class P123Client(P123OpenClient):
4945
6767
  )
4946
6768
 
4947
6769
  @overload
4948
- def fs_get_path(
6770
+ def fs_trash_clear(
4949
6771
  self,
4950
- payload: dict | int,
6772
+ payload: dict = {"event": "recycleClear"},
4951
6773
  /,
4952
6774
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4953
6775
  *,
@@ -4956,9 +6778,9 @@ class P123Client(P123OpenClient):
4956
6778
  ) -> dict:
4957
6779
  ...
4958
6780
  @overload
4959
- def fs_get_path(
6781
+ def fs_trash_clear(
4960
6782
  self,
4961
- payload: dict | int,
6783
+ payload: dict = {"event": "recycleClear"},
4962
6784
  /,
4963
6785
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4964
6786
  *,
@@ -4966,26 +6788,25 @@ class P123Client(P123OpenClient):
4966
6788
  **request_kwargs,
4967
6789
  ) -> Coroutine[Any, Any, dict]:
4968
6790
  ...
4969
- def fs_get_path(
6791
+ def fs_trash_clear(
4970
6792
  self,
4971
- payload: dict | int,
6793
+ payload: dict = {"event": "recycleClear"},
4972
6794
  /,
4973
6795
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4974
6796
  *,
4975
6797
  async_: Literal[False, True] = False,
4976
6798
  **request_kwargs,
4977
6799
  ) -> dict | Coroutine[Any, Any, dict]:
4978
- """获取某个 id 对应的祖先节点列表
6800
+ """清空回收站
4979
6801
 
4980
- POST https://www.123pan.com/api/file/get_path
6802
+ POST https://www.123pan.com/api/file/trash_delete_all
4981
6803
 
4982
6804
  :payload:
4983
- - fileId: int 💡 文件 id
6805
+ - event: str = "recycleClear"
4984
6806
  """
4985
- if isinstance(payload, int):
4986
- payload = {"fileId": payload}
6807
+ payload.setdefault("event", "recycleClear")
4987
6808
  return self.request(
4988
- "file/get_path",
6809
+ "file/trash_delete_all",
4989
6810
  "POST",
4990
6811
  json=payload,
4991
6812
  base_url=base_url,
@@ -4994,68 +6815,51 @@ class P123Client(P123OpenClient):
4994
6815
  )
4995
6816
 
4996
6817
  @overload
4997
- def fs_info(
4998
- self,
4999
- payload: dict | int | str | Iterable[int | str] = 0,
5000
- /,
6818
+ @staticmethod
6819
+ def fs_video_play_conf(
5001
6820
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6821
+ request: None | Callable = None,
5002
6822
  *,
5003
6823
  async_: Literal[False] = False,
5004
6824
  **request_kwargs,
5005
6825
  ) -> dict:
5006
6826
  ...
5007
6827
  @overload
5008
- def fs_info(
5009
- self,
5010
- payload: dict | int | str | Iterable[int | str] = 0,
5011
- /,
6828
+ @staticmethod
6829
+ def fs_video_play_conf(
5012
6830
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6831
+ request: None | Callable = None,
5013
6832
  *,
5014
6833
  async_: Literal[True],
5015
6834
  **request_kwargs,
5016
6835
  ) -> Coroutine[Any, Any, dict]:
5017
6836
  ...
5018
- def fs_info(
5019
- self,
5020
- payload: dict | int | str | Iterable[int | str] = 0,
5021
- /,
6837
+ @staticmethod
6838
+ def fs_video_play_conf(
5022
6839
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6840
+ request: None | Callable = None,
5023
6841
  *,
5024
- async_: Literal[False, True] = False,
5025
- **request_kwargs,
5026
- ) -> dict | Coroutine[Any, Any, dict]:
5027
- """获取文件信息
5028
-
5029
- POST https://www.123pan.com/api/file/info
5030
-
5031
- :payload:
5032
- - fileIdList: list[FileID]
5033
-
5034
- .. code:: python
5035
-
5036
- FileID = {
5037
- "FileId": int | str
5038
- }
5039
- """
5040
- if isinstance(payload, (int, str)):
5041
- payload = {"fileIdList": [{"FileId": payload}]}
5042
- elif not isinstance(payload, dict):
5043
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5044
- return self.request(
5045
- "file/info",
5046
- "POST",
5047
- json=payload,
5048
- base_url=base_url,
5049
- async_=async_,
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),
5050
6855
  **request_kwargs,
5051
6856
  )
5052
6857
 
5053
- @overload # type: ignore
5054
- def fs_list(
6858
+ @overload
6859
+ def fs_video_play_list(
5055
6860
  self,
5056
6861
  payload: dict | int | str = 0,
5057
6862
  /,
5058
- event: str = "homeListFile",
5059
6863
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5060
6864
  *,
5061
6865
  async_: Literal[False] = False,
@@ -5063,95 +6867,54 @@ class P123Client(P123OpenClient):
5063
6867
  ) -> dict:
5064
6868
  ...
5065
6869
  @overload
5066
- def fs_list(
6870
+ def fs_video_play_list(
5067
6871
  self,
5068
6872
  payload: dict | int | str = 0,
5069
6873
  /,
5070
- event: str = "homeListFile",
5071
6874
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5072
6875
  *,
5073
6876
  async_: Literal[True],
5074
6877
  **request_kwargs,
5075
6878
  ) -> Coroutine[Any, Any, dict]:
5076
6879
  ...
5077
- def fs_list(
6880
+ def fs_video_play_list(
5078
6881
  self,
5079
6882
  payload: dict | int | str = 0,
5080
6883
  /,
5081
- event: str = "homeListFile",
5082
6884
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5083
6885
  *,
5084
6886
  async_: Literal[False, True] = False,
5085
6887
  **request_kwargs,
5086
6888
  ) -> dict | Coroutine[Any, Any, dict]:
5087
- """获取文件列表(可搜索)
5088
-
5089
- GET https://www.123pan.com/api/file/list
6889
+ """获取某个目录下的视频列表
5090
6890
 
5091
- .. note::
5092
- 如果返回信息中,"Next" 字段的值为 "-1",代表最后一页(无需再翻页查询)
6891
+ GET https://www.123pan.com/api/file/video/play/list
5093
6892
 
5094
6893
  :payload:
5095
- - driveId: int | str = 0
5096
- - limit: int = 100 💡 分页大小,最多 100 个
5097
- - next: int = 0 💡 下一批拉取开始的 id
5098
- - orderBy: str = "file_id" 💡 排序依据
5099
-
5100
- - "file_id": 文件 id,也可以写作 "fileId"
5101
- - "file_name": 文件名
5102
- - "size": 文件大小
5103
- - "create_at": 创建时间
5104
- - "update_at": 更新时间
5105
- - "share_id": 分享 id
5106
- - ...
5107
-
5108
- - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
5109
- - Page: int = <default> 💡 第几页,从 1 开始,可以是 0
5110
- - parentFileId: int | str = 0 💡 父目录 id
5111
- - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
5112
- - inDirectSpace: "false" | "true" = "false"
5113
- - event: str = "homeListFile" 💡 事件名称
5114
-
5115
- - "homeListFile": 全部文件
5116
- - "recycleListFile": 回收站
5117
- - "syncFileList": 同步空间
5118
-
5119
- - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
5120
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
5121
- - OnlyLookAbnormalFile: int = <default>
6894
+ - page: int = 1
6895
+ - page_size: int = 100
6896
+ - parent_file_id: int = 0
5122
6897
  """
5123
- if isinstance(payload, (int, str)):
5124
- payload = {"parentFileId": payload}
5125
- payload = dict_to_lower_merge(payload, {
5126
- "driveId": 0,
5127
- "limit": 100,
5128
- "next": 0,
5129
- "orderBy": "file_id",
5130
- "orderDirection": "asc",
5131
- "parentFileId": 0,
5132
- "inDirectSpace": "false",
5133
- "event": event,
5134
- })
5135
- if not payload.get("trashed"):
5136
- match payload["event"]:
5137
- case "recycleListFile":
5138
- payload["trashed"] = "true"
5139
- case _:
5140
- 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)
5141
6902
  return self.request(
5142
- "file/list",
6903
+ "file/video/play/list",
5143
6904
  params=payload,
5144
6905
  base_url=base_url,
5145
6906
  async_=async_,
5146
6907
  **request_kwargs,
5147
6908
  )
5148
6909
 
6910
+ ########## Qrcode API ##########
6911
+
5149
6912
  @overload
5150
- def fs_list_new(
5151
- self,
5152
- payload: dict | int | str = 0,
6913
+ @staticmethod
6914
+ def login_passport(
6915
+ payload: dict,
5153
6916
  /,
5154
- event: str = "homeListFile",
6917
+ request: None | Callable = None,
5155
6918
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5156
6919
  *,
5157
6920
  async_: Literal[False] = False,
@@ -5159,211 +6922,141 @@ class P123Client(P123OpenClient):
5159
6922
  ) -> dict:
5160
6923
  ...
5161
6924
  @overload
5162
- def fs_list_new(
5163
- self,
5164
- payload: dict | int | str = 0,
6925
+ @staticmethod
6926
+ def login_passport(
6927
+ payload: dict,
5165
6928
  /,
5166
- event: str = "homeListFile",
6929
+ request: None | Callable = None,
5167
6930
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5168
6931
  *,
5169
6932
  async_: Literal[True],
5170
6933
  **request_kwargs,
5171
6934
  ) -> Coroutine[Any, Any, dict]:
5172
6935
  ...
5173
- def fs_list_new(
5174
- self,
5175
- payload: dict | int | str = 0,
6936
+ @staticmethod
6937
+ def login_passport(
6938
+ payload: dict,
5176
6939
  /,
5177
- event: str = "homeListFile",
6940
+ request: None | Callable = None,
5178
6941
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5179
6942
  *,
5180
6943
  async_: Literal[False, True] = False,
5181
6944
  **request_kwargs,
5182
6945
  ) -> dict | Coroutine[Any, Any, dict]:
5183
- """获取文件列表(可搜索)
6946
+ """使用账号和密码登录
5184
6947
 
5185
- GET https://www.123pan.com/api/file/list/new
6948
+ POST https://www.123pan.com/api/user/sign_in
5186
6949
 
5187
6950
  .. note::
5188
- 如果返回信息中,"Next" 字段的值为 "-1",代表最后一页(无需再翻页查询)
6951
+ 获取的 token 有效期 30 天
5189
6952
 
5190
6953
  :payload:
5191
- - driveId: int | str = 0
5192
- - limit: int = 100 💡 分页大小,最多 100 个
5193
- - next: int = 0 💡 下一批拉取开始的 id
5194
- - orderBy: str = "file_id" 💡 排序依据
5195
-
5196
- - "file_id": 文件 id,也可以写作 "fileId"
5197
- - "file_name": 文件名
5198
- - "size": 文件大小
5199
- - "create_at": 创建时间
5200
- - "update_at": 更新时间
5201
- - "share_id": 分享 id
5202
- - ...
5203
-
5204
- - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
5205
- - Page: int = 1 💡 第几页,从 1 开始
5206
- - parentFileId: int | str = 0 💡 父目录 id
5207
- - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
5208
- - inDirectSpace: "false" | "true" = "false"
5209
- - event: str = "homeListFile" 💡 事件名称
5210
-
5211
- - "homeListFile": 全部文件
5212
- - "recycleListFile": 回收站
5213
- - "syncFileList": 同步空间
5214
-
5215
- - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
5216
-
5217
- .. note::
5218
- 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
5219
-
5220
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
5221
- - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
5222
- - RequestSource: int = <default> 💡 浏览器中,在同步空间中为 1
6954
+ - passport: int | str 💡 手机号或邮箱
6955
+ - password: str 💡 密码
6956
+ - remember: bool = True 💡 是否记住密码(不用管)
5223
6957
  """
5224
- if isinstance(payload, (int, str)):
5225
- payload = {"parentFileId": payload}
5226
- payload = dict_to_lower_merge(payload, {
5227
- "driveId": 0,
5228
- "limit": 100,
5229
- "next": 0,
5230
- "orderBy": "file_id",
5231
- "orderDirection": "asc",
5232
- "parentFileId": 0,
5233
- "inDirectSpace": "false",
5234
- "event": event,
5235
- "OnlyLookAbnormalFile": 0,
5236
- "Page": 1,
5237
- })
5238
- if not payload.get("trashed"):
5239
- match payload["event"]:
5240
- case "recycleListFile":
5241
- payload["trashed"] = "true"
5242
- case _:
5243
- payload["trashed"] = "false"
5244
- return self.request(
5245
- "file/list/new",
5246
- params=payload,
5247
- base_url=base_url,
5248
- async_=async_,
5249
- **request_kwargs,
5250
- )
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)
5251
6964
 
5252
- @overload # type: ignore
5253
- def fs_mkdir(
5254
- self,
5255
- name: str,
6965
+ @overload
6966
+ @staticmethod
6967
+ def login_qrcode_bind_wx_code(
6968
+ payload: dict,
5256
6969
  /,
5257
- parent_id: int | str = 0,
5258
- duplicate: Literal[0, 1, 2] = 0,
5259
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6970
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
6971
+ request: None | Callable = None,
5260
6972
  *,
5261
6973
  async_: Literal[False] = False,
5262
6974
  **request_kwargs,
5263
6975
  ) -> dict:
5264
6976
  ...
5265
6977
  @overload
5266
- def fs_mkdir(
5267
- self,
5268
- name: str,
6978
+ @staticmethod
6979
+ def login_qrcode_bind_wx_code(
6980
+ payload: dict,
5269
6981
  /,
5270
- parent_id: int | str = 0,
5271
- duplicate: Literal[0, 1, 2] = 0,
5272
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6982
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
6983
+ request: None | Callable = None,
5273
6984
  *,
5274
6985
  async_: Literal[True],
5275
6986
  **request_kwargs,
5276
6987
  ) -> Coroutine[Any, Any, dict]:
5277
6988
  ...
5278
- def fs_mkdir(
5279
- self,
5280
- name: str,
6989
+ @staticmethod
6990
+ def login_qrcode_bind_wx_code(
6991
+ payload: dict,
5281
6992
  /,
5282
- parent_id: int | str = 0,
5283
- duplicate: Literal[0, 1, 2] = 0,
5284
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
6993
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
6994
+ request: None | Callable = None,
5285
6995
  *,
5286
6996
  async_: Literal[False, True] = False,
5287
6997
  **request_kwargs,
5288
6998
  ) -> dict | Coroutine[Any, Any, dict]:
5289
- """创建目录
6999
+ """绑定微信号
5290
7000
 
5291
- :param name: 目录名
5292
- :param parent_id: 父目录 id
5293
- :param duplicate: 处理同名:0: 复用 1: 保留两者 2: 替换
5294
- :param async_: 是否异步
5295
- :param request_kwargs: 其它请求参数
7001
+ POST https://login.123pan.com/api/user/qr-code/bind_wx_code
5296
7002
 
5297
- :return: 接口响应
7003
+ :payload:
7004
+ - uniID: str 💡 二维码 id
7005
+ - wxcode: str 💡 微信码
5298
7006
  """
5299
- payload = {"filename": name, "parentFileId": parent_id}
5300
- if duplicate:
5301
- payload["NotReuse"] = True
5302
- payload["duplicate"] = duplicate
5303
- return self.upload_request(
5304
- payload,
5305
- base_url=base_url,
5306
- 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,
5307
7015
  **request_kwargs,
5308
7016
  )
5309
7017
 
5310
7018
  @overload
5311
- def fs_move(
7019
+ def login_qrcode_confirm(
5312
7020
  self,
5313
- payload: dict | int | str | Iterable[int | str],
7021
+ payload: dict | str,
5314
7022
  /,
5315
- parent_id: int | str = 0,
5316
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7023
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
5317
7024
  *,
5318
7025
  async_: Literal[False] = False,
5319
7026
  **request_kwargs,
5320
7027
  ) -> dict:
5321
7028
  ...
5322
7029
  @overload
5323
- def fs_move(
7030
+ def login_qrcode_confirm(
5324
7031
  self,
5325
- payload: dict | int | str | Iterable[int | str],
7032
+ payload: dict | str,
5326
7033
  /,
5327
- parent_id: int | str = 0,
5328
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7034
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
5329
7035
  *,
5330
7036
  async_: Literal[True],
5331
7037
  **request_kwargs,
5332
7038
  ) -> Coroutine[Any, Any, dict]:
5333
7039
  ...
5334
- def fs_move(
7040
+ def login_qrcode_confirm(
5335
7041
  self,
5336
- payload: dict | int | str | Iterable[int | str],
7042
+ payload: dict | str,
5337
7043
  /,
5338
- parent_id: int | str = 0,
5339
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7044
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
5340
7045
  *,
5341
7046
  async_: Literal[False, True] = False,
5342
7047
  **request_kwargs,
5343
7048
  ) -> dict | Coroutine[Any, Any, dict]:
5344
- """移动
7049
+ """确认扫码登录
5345
7050
 
5346
- POST https://www.123pan.com/api/file/mod_pid
7051
+ POST https://login.123pan.com/api/user/qr-code/login
5347
7052
 
5348
7053
  :payload:
5349
- - fileIdList: list[FileID]
5350
-
5351
- .. code:: python
5352
-
5353
- FileID = {
5354
- "FileId": int | str
5355
- }
5356
-
5357
- - parentFileId: int | str = 0
5358
- - event: str = "fileMove"
7054
+ - uniID: str 💡 二维码 id
5359
7055
  """
5360
- if isinstance(payload, (int, str)):
5361
- payload = {"fileIdList": [{"FileId": payload}]}
5362
- elif not isinstance(payload, dict):
5363
- payload = {"fileIdList": [{"FileId": fid} for fid in payload]}
5364
- payload = dict_to_lower_merge(payload, {"parentFileId": parent_id, "event": "fileMove"})
7056
+ if not isinstance(payload, dict):
7057
+ payload = {"uniID": payload}
5365
7058
  return self.request(
5366
- "file/mod_pid",
7059
+ "user/qr-code/login",
5367
7060
  "POST",
5368
7061
  json=payload,
5369
7062
  base_url=base_url,
@@ -5372,237 +7065,221 @@ class P123Client(P123OpenClient):
5372
7065
  )
5373
7066
 
5374
7067
  @overload
5375
- def fs_fresh(
5376
- self,
5377
- payload: dict = {},
7068
+ @staticmethod
7069
+ def login_qrcode_deny(
7070
+ payload: dict | str,
5378
7071
  /,
5379
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7072
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7073
+ request: None | Callable = None,
5380
7074
  *,
5381
7075
  async_: Literal[False] = False,
5382
7076
  **request_kwargs,
5383
7077
  ) -> dict:
5384
7078
  ...
5385
7079
  @overload
5386
- def fs_fresh(
5387
- self,
5388
- payload: dict = {},
7080
+ @staticmethod
7081
+ def login_qrcode_deny(
7082
+ payload: dict | str,
5389
7083
  /,
5390
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7084
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7085
+ request: None | Callable = None,
5391
7086
  *,
5392
7087
  async_: Literal[True],
5393
7088
  **request_kwargs,
5394
7089
  ) -> Coroutine[Any, Any, dict]:
5395
7090
  ...
5396
- def fs_fresh(
5397
- self,
5398
- payload: dict = {},
7091
+ @staticmethod
7092
+ def login_qrcode_deny(
7093
+ payload: dict | str,
5399
7094
  /,
5400
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7095
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7096
+ request: None | Callable = None,
5401
7097
  *,
5402
7098
  async_: Literal[False, True] = False,
5403
7099
  **request_kwargs,
5404
7100
  ) -> dict | Coroutine[Any, Any, dict]:
5405
- """刷新列表和直链缓存
7101
+ """更新扫码状态为:已取消(loginStatus=2)
5406
7102
 
5407
- 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
5408
7107
  """
5409
- return self.request(
5410
- "restful/goapi/v1/cdnLink/cache/refresh",
5411
- "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",
5412
7117
  json=payload,
5413
- base_url=base_url,
5414
- async_=async_,
5415
7118
  **request_kwargs,
5416
7119
  )
5417
7120
 
5418
- @overload # type: ignore
5419
- def fs_rename(
5420
- self,
5421
- payload: dict,
5422
- /,
5423
- 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,
5424
7126
  *,
5425
7127
  async_: Literal[False] = False,
5426
7128
  **request_kwargs,
5427
7129
  ) -> dict:
5428
7130
  ...
5429
7131
  @overload
5430
- def fs_rename(
5431
- self,
5432
- payload: dict,
5433
- /,
5434
- 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,
5435
7136
  *,
5436
7137
  async_: Literal[True],
5437
7138
  **request_kwargs,
5438
7139
  ) -> Coroutine[Any, Any, dict]:
5439
7140
  ...
5440
- def fs_rename(
5441
- self,
5442
- payload: dict,
5443
- /,
5444
- 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,
5445
7145
  *,
5446
7146
  async_: Literal[False, True] = False,
5447
7147
  **request_kwargs,
5448
7148
  ) -> dict | Coroutine[Any, Any, dict]:
5449
- """(单个)改名
5450
-
5451
- POST https://www.123pan.com/api/file/rename
7149
+ """产生二维码
5452
7150
 
5453
- :payload:
5454
- - FileId: int | str
5455
- - fileName: str
5456
- - driveId: int | str = 0
5457
- - duplicate: 0 | 1 | 2 = 0 💡 处理同名:0: 提示/忽略 1: 保留两者 2: 替换
5458
- - event: str = "fileRename"
7151
+ GET https://login.123pan.com/api/user/qr-code/generate
5459
7152
  """
5460
- payload = dict_to_lower_merge(payload, {
5461
- "driveId": 0,
5462
- "duplicate": 0,
5463
- "event": "fileRename",
5464
- })
5465
- return self.request(
5466
- "file/rename",
5467
- "POST",
5468
- json=payload,
5469
- base_url=base_url,
5470
- 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),
5471
7159
  **request_kwargs,
5472
7160
  )
5473
7161
 
5474
7162
  @overload
5475
- def fs_sync_log(
5476
- self,
5477
- payload: dict | int = 1,
7163
+ @staticmethod
7164
+ def login_qrcode_result(
7165
+ payload: dict | str,
5478
7166
  /,
5479
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7167
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7168
+ request: None | Callable = None,
5480
7169
  *,
5481
7170
  async_: Literal[False] = False,
5482
7171
  **request_kwargs,
5483
7172
  ) -> dict:
5484
7173
  ...
5485
7174
  @overload
5486
- def fs_sync_log(
5487
- self,
5488
- payload: dict | int = 1,
7175
+ @staticmethod
7176
+ def login_qrcode_result(
7177
+ payload: dict | str,
5489
7178
  /,
5490
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7179
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7180
+ request: None | Callable = None,
5491
7181
  *,
5492
7182
  async_: Literal[True],
5493
7183
  **request_kwargs,
5494
7184
  ) -> Coroutine[Any, Any, dict]:
5495
7185
  ...
5496
- def fs_sync_log(
5497
- self,
5498
- payload: dict | int = 1,
7186
+ @staticmethod
7187
+ def login_qrcode_result(
7188
+ payload: dict | str,
5499
7189
  /,
5500
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7190
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7191
+ request: None | Callable = None,
5501
7192
  *,
5502
7193
  async_: Literal[False, True] = False,
5503
7194
  **request_kwargs,
5504
7195
  ) -> dict | Coroutine[Any, Any, dict]:
5505
- """获取同步空间的操作记录
7196
+ """获取扫码结果
5506
7197
 
5507
- 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: 已失效
5508
7208
 
5509
7209
  :payload:
5510
- - page: int = 1 💡 第几页
5511
- - pageSize: int = 100 💡 每页大小
5512
- - searchData: str = <default> 💡 搜索关键字
7210
+ - uniID: str 💡 二维码 id
5513
7211
  """
5514
7212
  if not isinstance(payload, dict):
5515
- payload = {"page": payload, "pageSize": 100}
5516
- return self.request(
5517
- "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),
5518
7220
  params=payload,
5519
- base_url=base_url,
5520
- async_=async_,
5521
7221
  **request_kwargs,
5522
7222
  )
5523
7223
 
5524
- @overload # type: ignore
5525
- def fs_trash(
5526
- self,
5527
- payload: dict | int | str | Iterable[int | str],
7224
+ @overload
7225
+ @staticmethod
7226
+ def login_qrcode_scan(
7227
+ payload: dict | str,
5528
7228
  /,
5529
- event: str = "intoRecycle",
5530
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7229
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7230
+ request: None | Callable = None,
5531
7231
  *,
5532
7232
  async_: Literal[False] = False,
5533
7233
  **request_kwargs,
5534
7234
  ) -> dict:
5535
7235
  ...
5536
7236
  @overload
5537
- def fs_trash(
5538
- self,
5539
- payload: dict | int | str | Iterable[int | str],
7237
+ @staticmethod
7238
+ def login_qrcode_scan(
7239
+ payload: dict | str,
5540
7240
  /,
5541
- event: str = "intoRecycle",
5542
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7241
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7242
+ request: None | Callable = None,
5543
7243
  *,
5544
7244
  async_: Literal[True],
5545
7245
  **request_kwargs,
5546
7246
  ) -> Coroutine[Any, Any, dict]:
5547
7247
  ...
5548
- def fs_trash(
5549
- self,
5550
- payload: dict | int | str | Iterable[int | str],
7248
+ @staticmethod
7249
+ def login_qrcode_scan(
7250
+ payload: dict | str,
5551
7251
  /,
5552
- event: str = "intoRecycle",
5553
- base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7252
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
7253
+ request: None | Callable = None,
5554
7254
  *,
5555
7255
  async_: Literal[False, True] = False,
5556
7256
  **request_kwargs,
5557
7257
  ) -> dict | Coroutine[Any, Any, dict]:
5558
- """操作回收站
7258
+ """更新扫码状态为:已扫码(loginStatus=1)
5559
7259
 
5560
- POST https://www.123pan.com/api/file/trash
7260
+ POST https://login.123pan.com/api/user/qr-code/scan
5561
7261
 
5562
7262
  :payload:
5563
- - fileTrashInfoList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
5564
-
5565
- .. code:: python
5566
-
5567
- File = {
5568
- "FileId": int | str,
5569
- ...
5570
- }
5571
-
5572
- - driveId: int = 0
5573
- - event: str = "intoRecycle" 💡 事件类型
5574
-
5575
- - "intoRecycle": 移入回收站
5576
- - "recycleRestore": 移出回收站
5577
-
5578
- - operation: bool = <default>
5579
- - operatePlace: int = <default>
5580
- - RequestSource: int = <default>
7263
+ - uniID: str 💡 二维码 id
7264
+ - scanPlatform: int = 4 💡 扫码的平台代码,微信是 4
5581
7265
  """
5582
- if isinstance(payload, (int, str)):
5583
- payload = {"fileTrashInfoList": [{"FileId": payload}]}
5584
- elif not isinstance(payload, dict):
5585
- payload = {"fileTrashInfoList": [{"FileId": fid} for fid in payload]}
5586
- payload = dict_to_lower_merge(payload, {"driveId": 0, "event": event})
5587
- if payload.get("operation") is None:
5588
- match payload["event"]:
5589
- case "recycleRestore":
5590
- payload["operation"] = False
5591
- case _:
5592
- payload["operation"] = True
5593
- return self.request(
5594
- "file/trash",
5595
- "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",
5596
7276
  json=payload,
5597
- base_url=base_url,
5598
- async_=async_,
5599
7277
  **request_kwargs,
5600
7278
  )
5601
7279
 
5602
7280
  @overload
5603
- def fs_trash_clear(
7281
+ def logout(
5604
7282
  self,
5605
- payload: dict = {"event": "recycleClear"},
5606
7283
  /,
5607
7284
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5608
7285
  *,
@@ -5611,9 +7288,8 @@ class P123Client(P123OpenClient):
5611
7288
  ) -> dict:
5612
7289
  ...
5613
7290
  @overload
5614
- def fs_trash_clear(
7291
+ def logout(
5615
7292
  self,
5616
- payload: dict = {"event": "recycleClear"},
5617
7293
  /,
5618
7294
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5619
7295
  *,
@@ -5621,32 +7297,28 @@ class P123Client(P123OpenClient):
5621
7297
  **request_kwargs,
5622
7298
  ) -> Coroutine[Any, Any, dict]:
5623
7299
  ...
5624
- def fs_trash_clear(
7300
+ def logout(
5625
7301
  self,
5626
- payload: dict = {"event": "recycleClear"},
5627
7302
  /,
5628
7303
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5629
7304
  *,
5630
7305
  async_: Literal[False, True] = False,
5631
7306
  **request_kwargs,
5632
7307
  ) -> dict | Coroutine[Any, Any, dict]:
5633
- """清空回收站
5634
-
5635
- POST https://www.123pan.com/api/file/trash_delete_all
7308
+ """退出登录
5636
7309
 
5637
- :payload:
5638
- - event: str = "recycleClear"
7310
+ POST https://www.123pan.com/api/user/logout
5639
7311
  """
5640
- payload.setdefault("event", "recycleClear")
5641
7312
  return self.request(
5642
- "file/trash_delete_all",
7313
+ "user/logout",
5643
7314
  "POST",
5644
- json=payload,
5645
7315
  base_url=base_url,
5646
7316
  async_=async_,
5647
7317
  **request_kwargs,
5648
7318
  )
5649
7319
 
7320
+ ########## Offline Download API ##########
7321
+
5650
7322
  @overload
5651
7323
  def offline_task_delete(
5652
7324
  self,
@@ -5684,7 +7356,7 @@ class P123Client(P123OpenClient):
5684
7356
 
5685
7357
  :payload:
5686
7358
  - task_ids: list[int] 💡 任务 id 列表
5687
- - status_arr: list[0|1|2] = [] 💡 状态列表:0:等待 1:运行 2:完成
7359
+ - status_arr: list[ 0 | 1 | 2 | 3 | 4 ] = [] 💡 状态列表:0:进行中 1:下载失败 2:下载成功 3:重试中
5688
7360
  """
5689
7361
  if isinstance(payload, int):
5690
7362
  payload = {"task_ids": [payload], "status_arr": []}
@@ -5739,12 +7411,12 @@ class P123Client(P123OpenClient):
5739
7411
  :payload:
5740
7412
  - current_page: int = 1
5741
7413
  - page_size: 100
5742
- - 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:重试中
5743
7415
  """
5744
7416
  if isinstance(payload, int):
5745
- 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]}
5746
7418
  else:
5747
- 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}
5748
7420
  return self.request(
5749
7421
  "offline_download/task/list",
5750
7422
  "POST",
@@ -5911,6 +7583,8 @@ class P123Client(P123OpenClient):
5911
7583
  **request_kwargs,
5912
7584
  )
5913
7585
 
7586
+ ########## Share API ##########
7587
+
5914
7588
  @overload
5915
7589
  def share_cancel(
5916
7590
  self,
@@ -6687,6 +8361,8 @@ class P123Client(P123OpenClient):
6687
8361
  **request_kwargs,
6688
8362
  )
6689
8363
 
8364
+ ########## Upload API ##########
8365
+
6690
8366
  @overload
6691
8367
  def upload_auth(
6692
8368
  self,
@@ -7410,6 +9086,8 @@ class P123Client(P123OpenClient):
7410
9086
  )
7411
9087
  return run_gen_step(gen_step, async_)
7412
9088
 
9089
+ ########## User API ##########
9090
+
7413
9091
  @overload
7414
9092
  def user_device_list(
7415
9093
  self,
@@ -7499,11 +9177,9 @@ class P123Client(P123OpenClient):
7499
9177
  )
7500
9178
 
7501
9179
  @overload
7502
- @staticmethod
7503
- def user_login(
7504
- payload: dict,
9180
+ def user_referral_info(
9181
+ self,
7505
9182
  /,
7506
- request: None | Callable = None,
7507
9183
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7508
9184
  *,
7509
9185
  async_: Literal[False] = False,
@@ -7511,45 +9187,72 @@ class P123Client(P123OpenClient):
7511
9187
  ) -> dict:
7512
9188
  ...
7513
9189
  @overload
7514
- @staticmethod
7515
- def user_login(
7516
- payload: dict,
9190
+ def user_referral_info(
9191
+ self,
7517
9192
  /,
7518
- request: None | Callable = None,
7519
9193
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7520
9194
  *,
7521
9195
  async_: Literal[True],
7522
9196
  **request_kwargs,
7523
9197
  ) -> Coroutine[Any, Any, dict]:
7524
9198
  ...
7525
- @staticmethod
7526
- def user_login(
7527
- payload: dict,
9199
+ def user_referral_info(
9200
+ self,
7528
9201
  /,
7529
- request: None | Callable = None,
7530
9202
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7531
9203
  *,
7532
9204
  async_: Literal[False, True] = False,
7533
9205
  **request_kwargs,
7534
9206
  ) -> dict | Coroutine[Any, Any, dict]:
7535
- """使用账号和密码登录
9207
+ """用户拉人头信息
7536
9208
 
7537
- 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
+ )
7538
9217
 
7539
- .. note::
7540
- 获取的 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
+ """用户推送消息配置
7541
9247
 
7542
- :payload:
7543
- - passport: int | str 💡 手机号或邮箱
7544
- - password: str 💡 密码
7545
- - remember: bool = True 💡 是否记住密码(不用管)
9248
+ GET https://www.123pan.com/b/api/restful/goapi/v1/user/report/info
7546
9249
  """
7547
- api = complete_url("user/sign_in", base_url)
7548
- request_kwargs.setdefault("parse", default_parse)
7549
- if request is None:
7550
- request = get_default_request()
7551
- request_kwargs["async_"] = async_
7552
- 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
+ )
7553
9256
 
7554
9257
  @overload
7555
9258
  def user_use_history(
@@ -7599,9 +9302,31 @@ class P123Client(P123OpenClient):
7599
9302
  **request_kwargs,
7600
9303
  )
7601
9304
 
7602
- # TODO: 添加扫码登录接口,以及通过扫码登录的方法,特别是用已登录的设备扫描一个新的 token 出来
7603
- # TODO: 添加 同步空间 和 直链空间 的操作接口
7604
- # TODO: 添加 图床 的操作接口
7605
- # 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
+
7606
9323
  # TODO: 对于某些工具的接口封装,例如 重复文件清理
7607
- # TODO: 开放接口有更新了,从此开始 https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xogi45g7okqk7svr
9324
+ # TODO: 同步空间
9325
+ # TODO: 直链空间
9326
+ # TODO: 图床
9327
+ # TODO: 视频转码
9328
+ # TODO: 移入保险箱
9329
+ # TODO: 申诉和申诉列表
9330
+ # TODO: 歌单(以及批量加入歌单)
9331
+ # TODO: 最近查看
9332
+ # TODO: 消息通知列表和操作