p123client 0.0.6.9.4__py3-none-any.whl → 0.0.6.10__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,13 +5,14 @@ from __future__ import annotations
5
5
 
6
6
  __all__ = ["check_response", "P123OpenClient", "P123Client"]
7
7
 
8
- from base64 import b64decode
8
+ import errno
9
+
10
+ from base64 import urlsafe_b64decode
9
11
  from collections.abc import (
10
12
  AsyncIterable, Awaitable, Buffer, Callable, Coroutine,
11
13
  ItemsView, Iterable, Iterator, Mapping, MutableMapping,
12
14
  )
13
15
  from contextlib import contextmanager
14
- from errno import EAUTH, EIO, EISDIR, ENOENT
15
16
  from functools import partial
16
17
  from hashlib import md5
17
18
  from http.cookiejar import CookieJar
@@ -21,9 +22,11 @@ from os import fsdecode, fstat, isatty, PathLike
21
22
  from os.path import basename
22
23
  from pathlib import Path, PurePath
23
24
  from re import compile as re_compile, MULTILINE
25
+ from string import digits, ascii_uppercase
24
26
  from sys import _getframe
25
27
  from tempfile import TemporaryFile
26
28
  from typing import cast, overload, Any, Final, Literal, Self
29
+ from urllib.parse import parse_qsl, urlsplit
27
30
  from uuid import uuid4
28
31
  from warnings import warn
29
32
 
@@ -39,11 +42,14 @@ from http_request import (
39
42
  encode_multipart_data, encode_multipart_data_async, SupportsGeturl,
40
43
  )
41
44
  from iterutils import run_gen_step
45
+ from orjson import loads
42
46
  from property import locked_cacheproperty
43
47
  from yarl import URL
44
48
 
45
49
  from .const import CLIENT_API_METHODS_MAP, CLIENT_METHOD_API_MAP
46
- from .exception import P123OSError, P123BrokenUpload, P123LoginError, P123AuthenticationError
50
+ from .exception import (
51
+ P123OSError, P123BrokenUpload, P123LoginError, P123AuthenticationError, P123FileNotFoundError,
52
+ )
47
53
 
48
54
 
49
55
  # 默认使用的域名
@@ -66,7 +72,6 @@ def get_default_request():
66
72
 
67
73
 
68
74
  def default_parse(_, content: Buffer, /):
69
- from orjson import loads
70
75
  if isinstance(content, (bytes, bytearray, memoryview)):
71
76
  return loads(content)
72
77
  else:
@@ -179,15 +184,23 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
179
184
  """
180
185
  def check(resp, /) -> dict:
181
186
  if not isinstance(resp, dict):
182
- raise P123OSError(EIO, resp)
187
+ raise P123OSError(errno.EIO, resp)
183
188
  code = resp.get("code", 0)
184
189
  if code in (0, 200):
185
190
  return resp
186
191
  match code:
187
- case 401:
188
- raise P123AuthenticationError(EAUTH, resp)
192
+ case 1: # 内部错误
193
+ raise P123AuthenticationError(errno.EIO, resp)
194
+ case 401: # access_token 失效
195
+ raise P123AuthenticationError(errno.EAUTH, resp)
196
+ case 429: # 请求太频繁
197
+ raise P123OSError(errno.EBUSY, resp)
198
+ case 5066: # 文件不存在
199
+ raise P123FileNotFoundError(errno.ENOENT, resp)
200
+ case 5113: # 流量超限
201
+ raise P123OSError(errno.EIO, resp)
189
202
  case _:
190
- raise P123OSError(EIO, resp)
203
+ raise P123OSError(errno.EIO, resp)
191
204
  if isawaitable(resp):
192
205
  async def check_await() -> dict:
193
206
  return check(await resp)
@@ -204,6 +217,9 @@ class P123OpenClient:
204
217
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced
205
218
  """
206
219
 
220
+ client_id: str = ""
221
+ client_secret: str = ""
222
+ refresh_token: str = ""
207
223
  token_path: None | PurePath = None
208
224
 
209
225
  def __init__(
@@ -211,14 +227,16 @@ class P123OpenClient:
211
227
  client_id: str | PathLike = "",
212
228
  client_secret: str = "",
213
229
  token: None | str | PathLike = None,
230
+ refresh_token: str = "",
214
231
  ):
215
232
  if isinstance(client_id, PathLike):
216
233
  token = client_id
217
234
  else:
218
235
  self.client_id = client_id
219
- self.client_secret = client_secret
236
+ self.client_secret = client_secret
237
+ self.refresh_token = refresh_token
220
238
  if token is None:
221
- if client_id and client_secret:
239
+ if client_id and client_secret or refresh_token:
222
240
  self.login_open()
223
241
  elif isinstance(token, str):
224
242
  self.token = token.removeprefix("Bearer ")
@@ -228,7 +246,7 @@ class P123OpenClient:
228
246
  else:
229
247
  self.token_path = Path(fsdecode(token))
230
248
  self._read_token()
231
- if not self.token and client_id and client_secret:
249
+ if not self.token and (client_id and client_secret or refresh_token):
232
250
  self.login_open()
233
251
 
234
252
  def __del__(self, /):
@@ -322,8 +340,7 @@ class P123OpenClient:
322
340
 
323
341
  @locked_cacheproperty
324
342
  def token_user_info(self, /) -> dict:
325
- from orjson import loads
326
- return loads(b64decode(self.token.split(".", 2)[1] + "=="))
343
+ return loads(urlsafe_b64decode(self.token.split(".", 2)[1] + "=="))
327
344
 
328
345
  @locked_cacheproperty
329
346
  def user_id(self, /) -> dict:
@@ -403,7 +420,8 @@ class P123OpenClient:
403
420
  /,
404
421
  client_id: str = "",
405
422
  client_secret: str = "",
406
- base_url: str | Callable[[], str] = "https://www.123pan.com",
423
+ refresh_token: str = "",
424
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
407
425
  *,
408
426
  async_: Literal[False] = False,
409
427
  **request_kwargs,
@@ -415,7 +433,8 @@ class P123OpenClient:
415
433
  /,
416
434
  client_id: str = "",
417
435
  client_secret: str = "",
418
- base_url: str | Callable[[], str] = "https://www.123pan.com",
436
+ refresh_token: str = "",
437
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
419
438
  *,
420
439
  async_: Literal[True],
421
440
  **request_kwargs,
@@ -426,7 +445,8 @@ class P123OpenClient:
426
445
  /,
427
446
  client_id: str = "",
428
447
  client_secret: str = "",
429
- base_url: str | Callable[[], str] = "https://www.123pan.com",
448
+ refresh_token: str = "",
449
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
430
450
  *,
431
451
  async_: Literal[False, True] = False,
432
452
  **request_kwargs,
@@ -435,6 +455,7 @@ class P123OpenClient:
435
455
 
436
456
  :param client_id: 应用标识,创建应用时分配的 appId
437
457
  :param client_secret: 应用密钥,创建应用时分配的 secretId
458
+ :param refresh_token: 刷新令牌
438
459
  :param base_url: 接口的基地址
439
460
  :param async_: 是否异步
440
461
  :param request_kwargs: 其它请求参数
@@ -445,222 +466,323 @@ class P123OpenClient:
445
466
  self.client_id = client_id
446
467
  else:
447
468
  client_id = self.client_id
469
+ if client_secret:
470
+ self.client_secret = client_secret
471
+ else:
472
+ client_secret = self.client_secret
473
+ if refresh_token:
474
+ self.refresh_token = refresh_token
475
+ else:
476
+ refresh_token = self.refresh_token
477
+ def gen_step():
478
+ if refresh_token:
479
+ resp = yield self.login_with_refresh_token(
480
+ refresh_token,
481
+ base_url=base_url,
482
+ async_=async_,
483
+ **request_kwargs,
484
+ )
485
+ self.token = resp["access_token"]
486
+ self.refresh_token = resp["refresh_token"]
487
+ return resp
488
+ else:
489
+ resp = yield self.login_token_open( # type: ignore
490
+ {"clientID": client_id, "clientSecret": client_secret},
491
+ base_url=base_url,
492
+ async_=async_,
493
+ **request_kwargs,
494
+ )
495
+ check_response(resp)
496
+ self.token = resp["data"]["accessToken"]
497
+ return resp
498
+ return run_gen_step(gen_step, async_)
499
+
500
+ @overload
501
+ def login_another_oauth(
502
+ self,
503
+ /,
504
+ redirect_uri: str,
505
+ client_id: str = "",
506
+ client_secret: str = "",
507
+ replace: bool | Self = False,
508
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
509
+ *,
510
+ async_: Literal[False] = False,
511
+ **request_kwargs,
512
+ ) -> Self:
513
+ ...
514
+ @overload
515
+ def login_another_oauth(
516
+ self,
517
+ /,
518
+ redirect_uri: str,
519
+ client_id: str = "",
520
+ client_secret: str = "",
521
+ replace: bool | Self = False,
522
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
523
+ *,
524
+ async_: Literal[True],
525
+ **request_kwargs,
526
+ ) -> Coroutine[Any, Any, Self]:
527
+ ...
528
+ def login_another_oauth(
529
+ self,
530
+ /,
531
+ redirect_uri: str,
532
+ client_id: str = "",
533
+ client_secret: str = "",
534
+ replace: bool | Self = False,
535
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
536
+ *,
537
+ async_: Literal[False, True] = False,
538
+ **request_kwargs,
539
+ ) -> Self | Coroutine[Any, Any, Self]:
540
+ """第三方应用授权登录
541
+
542
+ :param redirect_uri: 回调链接
543
+ :param client_id: 应用标识,创建应用时分配的 appId
544
+ :param client_secret: 应用密钥,创建应用时分配的 secretId
545
+ :param replace: 替换某个 client 对象的 token
546
+
547
+ - 如果为 P123Client, 则更新到此对象
548
+ - 如果为 True,则更新到 `self``
549
+ - 如果为 False,否则返回新的 ``P123Client`` 对象
550
+
551
+ :param async_: 是否异步
552
+ :param request_kwargs: 其它请求参数
553
+
554
+ :return: 接口响应
555
+ """
448
556
  if client_id:
557
+ self.client_id = client_id
558
+ else:
559
+ client_id = self.client_id
560
+ if client_secret:
449
561
  self.client_secret = client_secret
450
562
  else:
451
563
  client_secret = self.client_secret
452
564
  def gen_step():
453
- resp = yield self.login_access_token_open( # type: ignore
454
- {"clientID": client_id, "clientSecret": client_secret},
565
+ resp = yield self.login_with_oauth(
566
+ client_id,
567
+ client_secret,
568
+ redirect_uri=redirect_uri,
569
+ token=self.token,
455
570
  base_url=base_url,
456
571
  async_=async_,
457
572
  **request_kwargs,
458
573
  )
459
- check_response(resp)
460
- self.token = resp["data"]["accessToken"]
461
- return resp
574
+ token = resp["access_token"]
575
+ refresh_token = resp["refresh_token"]
576
+ if replace is False:
577
+ return type(self)(
578
+ client_id=client_id,
579
+ client_secret=client_secret,
580
+ token=token,
581
+ refresh_token=refresh_token,
582
+ )
583
+ elif replace is True:
584
+ inst = self
585
+ else:
586
+ inst = replace
587
+ inst.token = token
588
+ inst.refresh_token = refresh_token
589
+ return inst
462
590
  return run_gen_step(gen_step, async_)
463
591
 
464
592
  @overload
465
- @staticmethod
466
- def login_access_token(
467
- payload: dict,
593
+ def login_another_refresh_token(
594
+ self,
468
595
  /,
469
- base_url: str | Callable[[], str] = "https://www.123pan.com",
470
- request: None | Callable = None,
596
+ refresh_token: str = "",
597
+ replace: bool | Self = False,
598
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
471
599
  *,
472
600
  async_: Literal[False] = False,
473
601
  **request_kwargs,
474
- ) -> dict:
602
+ ) -> Self:
475
603
  ...
476
604
  @overload
477
- @staticmethod
478
- def login_access_token(
479
- payload: dict,
605
+ def login_another_refresh_token(
606
+ self,
480
607
  /,
481
- base_url: str | Callable[[], str] = "https://www.123pan.com",
482
- request: None | Callable = None,
608
+ refresh_token: str = "",
609
+ replace: bool | Self = False,
610
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
483
611
  *,
484
612
  async_: Literal[True],
485
613
  **request_kwargs,
486
- ) -> Coroutine[Any, Any, dict]:
614
+ ) -> Coroutine[Any, Any, Self]:
487
615
  ...
488
- @staticmethod
489
- def login_access_token(
490
- payload: dict,
616
+ def login_another_refresh_token(
617
+ self,
491
618
  /,
492
- base_url: str | Callable[[], str] = "https://www.123pan.com",
493
- request: None | Callable = None,
619
+ refresh_token: str = "",
620
+ replace: bool | Self = False,
621
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
494
622
  *,
495
623
  async_: Literal[False, True] = False,
496
624
  **request_kwargs,
497
- ) -> dict | Coroutine[Any, Any, dict]:
498
- """获取access_token
499
-
500
- POST https://open-api.123pan.com/api/v1/access_token
501
-
502
- .. attention::
503
- 此接口有访问频率限制。请获取到 `access_token` 后本地保存使用,并在 `access_token `过期前及时重新获取。`access_token` 有效期根据返回的 "expiredAt" 字段判断。
504
-
505
- .. note::
506
- 通过这种方式授权得到的 `access_token`,各个接口分别允许一个较低的 QPS
507
-
508
- /接入指南/开发者接入/开发须知
625
+ ) -> Self | Coroutine[Any, Any, Self]:
626
+ """登录以获取 access_token
509
627
 
510
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/txgcvbfgh0gtuad5
628
+ :param refresh_token: 刷新令牌
629
+ :param replace: 替换某个 client 对象的 token
511
630
 
512
- .. admonition:: Reference
513
- /接入指南/开发者接入/获取access_token
631
+ - 如果为 P123Client, 则更新到此对象
632
+ - 如果为 True,则更新到 `self``
633
+ - 如果为 False,否则返回新的 ``P123Client`` 对象
514
634
 
515
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gn1nai4x0v0ry9ki
635
+ :param base_url: 接口的基地址
636
+ :param async_: 是否异步
637
+ :param request_kwargs: 其它请求参数
516
638
 
517
- :payload:
518
- - clientID: str 💡 应用标识,创建应用时分配的 appId
519
- - clientSecret: str 💡 应用密钥,创建应用时分配的 secretId
639
+ :return: 接口的响应信息
520
640
  """
521
- request_kwargs.setdefault("parse", default_parse)
522
- if request is None:
523
- request = get_default_request()
524
- request_kwargs["async_"] = async_
525
- return request(
526
- url=complete_url("/api/v1/access_token", base_url),
527
- method="POST",
528
- json=payload,
529
- **request_kwargs,
530
- )
641
+ if refresh_token:
642
+ self.refresh_token = refresh_token
643
+ else:
644
+ refresh_token = self.refresh_token
645
+ def gen_step():
646
+ nonlocal refresh_token
647
+ resp = yield self.login_with_refresh_token(
648
+ refresh_token,
649
+ base_url=base_url,
650
+ async_=async_,
651
+ **request_kwargs,
652
+ )
653
+ token = resp["access_token"]
654
+ refresh_token = resp["refresh_token"]
655
+ if replace is False:
656
+ return type(self)(
657
+ token=token,
658
+ refresh_token=refresh_token,
659
+ )
660
+ elif replace is True:
661
+ inst = self
662
+ else:
663
+ inst = replace
664
+ inst.token = token
665
+ inst.refresh_token = refresh_token
666
+ return inst
667
+ return run_gen_step(gen_step, async_)
531
668
 
532
669
  @overload
533
- @staticmethod
534
- def login_auth(
535
- payload: dict,
670
+ def login_with_oauth(
671
+ cls,
536
672
  /,
537
- base_url: str | Callable[[], str] = "https://www.123pan.com",
538
- request: None | Callable = None,
673
+ client_id: str,
674
+ client_secret: str,
675
+ redirect_uri: str,
676
+ token: str,
677
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
539
678
  *,
540
679
  async_: Literal[False] = False,
541
680
  **request_kwargs,
542
681
  ) -> dict:
543
682
  ...
544
683
  @overload
545
- @staticmethod
546
- def login_auth(
547
- payload: dict,
684
+ def login_with_oauth(
685
+ cls,
548
686
  /,
549
- base_url: str | Callable[[], str] = "https://www.123pan.com",
550
- request: None | Callable = None,
687
+ client_id: str,
688
+ client_secret: str,
689
+ redirect_uri: str,
690
+ token: str,
691
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
551
692
  *,
552
693
  async_: Literal[True],
553
694
  **request_kwargs,
554
695
  ) -> Coroutine[Any, Any, dict]:
555
696
  ...
556
- @staticmethod
557
- def login_auth(
558
- payload: dict,
697
+ def login_with_oauth(
698
+ cls,
559
699
  /,
560
- base_url: str | Callable[[], str] = "https://www.123pan.com",
561
- request: None | Callable = None,
700
+ client_id: str,
701
+ client_secret: str,
702
+ redirect_uri: str,
703
+ token: str,
704
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
562
705
  *,
563
706
  async_: Literal[False, True] = False,
564
707
  **request_kwargs,
565
708
  ) -> dict | Coroutine[Any, Any, dict]:
566
- """授权地址
567
-
568
- GET https://www.123pan.com/auth
709
+ """第三方应用授权登录
569
710
 
570
- .. admonition:: Reference
571
- /接入指南/第三方挂载应用接入/授权地址
572
-
573
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gr7ggimkcysm18ap
711
+ :param client_id: 应用标识,创建应用时分配的 appId
712
+ :param client_secret: 应用密钥,创建应用时分配的 secretId
713
+ :param redirect_uri: 回调链接
714
+ :param token: 访问令牌
715
+ :param async_: 是否异步
716
+ :param request_kwargs: 其它请求参数
574
717
 
575
- :payload:
576
- - client_id: str 💡 应用标识,创建应用时分配的 appId
577
- - redirect_uri: str 💡 应用注册的回调地址
578
- - scope: str = "user:base,file:all:read,file:all:write" 💡 权限
579
- - state: str = "" 💡 自定义参数,任意取值
718
+ :return: 接口响应
580
719
  """
581
- request_kwargs.setdefault("parse", default_parse)
582
- payload = dict_to_lower_merge(payload, scope="user:base,file:all:read,file:all:write")
583
- if request is None:
584
- request = get_default_request()
585
- request_kwargs["async_"] = async_
586
- return request(
587
- url=complete_url("/auth", base_url),
588
- params=payload,
589
- **request_kwargs,
590
- )
720
+ def gen_step():
721
+ resp = yield cls.login_oauth_authorize(
722
+ {"accessToken": token, "client_id": client_id, "redirect_uri": redirect_uri},
723
+ base_url=base_url,
724
+ async_=async_,
725
+ **request_kwargs,
726
+ )
727
+ check_response(resp)
728
+ authorization_code = resp["data"]["code"]
729
+ return cls.login_oauth_token(
730
+ {
731
+ "client_id": client_id,
732
+ "client_secret": client_secret,
733
+ "code": authorization_code,
734
+ "grant_type": "authorization_code",
735
+ "redirect_uri": redirect_uri,
736
+ },
737
+ base_url=base_url,
738
+ async_=async_,
739
+ **request_kwargs,
740
+ )
741
+ return run_gen_step(gen_step, async_)
591
742
 
592
743
  @overload
593
- @staticmethod
594
- def login_refresh_token(
595
- payload: dict,
744
+ def login_with_refresh_token(
745
+ cls,
596
746
  /,
597
- base_url: str | Callable[[], str] = "https://www.123pan.com",
598
- request: None | Callable = None,
747
+ refresh_token: str,
748
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
599
749
  *,
600
750
  async_: Literal[False] = False,
601
751
  **request_kwargs,
602
752
  ) -> dict:
603
753
  ...
604
754
  @overload
605
- @staticmethod
606
- def login_refresh_token(
607
- payload: dict,
755
+ def login_with_refresh_token(
756
+ cls,
608
757
  /,
609
- base_url: str | Callable[[], str] = "https://www.123pan.com",
610
- request: None | Callable = None,
758
+ refresh_token: str,
759
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
611
760
  *,
612
761
  async_: Literal[True],
613
762
  **request_kwargs,
614
763
  ) -> Coroutine[Any, Any, dict]:
615
764
  ...
616
- @staticmethod
617
- def login_refresh_token(
618
- payload: dict,
765
+ def login_with_refresh_token(
766
+ cls,
619
767
  /,
620
- base_url: str | Callable[[], str] = "https://www.123pan.com",
621
- request: None | Callable = None,
768
+ refresh_token: str,
769
+ base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
622
770
  *,
623
771
  async_: Literal[False, True] = False,
624
772
  **request_kwargs,
625
773
  ) -> dict | Coroutine[Any, Any, dict]:
626
- """授权code获取access_token
627
-
628
- POST https://open-api.123pan.com/api/v1/oauth2/access_token
629
-
630
- .. note::
631
- 通过这种方式授权得到的 `access_token`,各个接口分别允许更高的 QPS
632
-
633
- /接入指南/第三方挂载应用接入/授权须知
634
-
635
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/kf05anzt1r0qnudd
774
+ """通过刷新令牌登录
636
775
 
637
- .. admonition:: Reference
638
- /接入指南/第三方挂载应用接入/授权code获取access_token
639
-
640
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gammzlhe6k4qtwd9
776
+ :param refresh_token: 刷新令牌
777
+ :param async_: 是否异步
778
+ :param request_kwargs: 其它请求参数
641
779
 
642
- :payload:
643
- - client_id: str 💡 应用标识,创建应用时分配的 appId
644
- - client_secret: str 💡 应用密钥,创建应用时分配的 secretId
645
- - code: str = <default> 💡 授权码
646
- - grant_type: "authorization_code" | "refresh_token" = <default> 💡 身份类型
647
- - redirect_uri: str = <default> 💡 应用注册的回调地址,`grant_type` 为 "authorization_code" 时必携带
648
- - refresh_token: str = <default> 💡 刷新 token,单次请求有效
780
+ :return: 接口响应
649
781
  """
650
- request_kwargs.setdefault("parse", default_parse)
651
- payload = dict_to_lower(payload)
652
- if not payload.get("grant_type"):
653
- if payload.get("refresh_token"):
654
- payload["grant_type"] = "refresh_token"
655
- else:
656
- payload["grant_type"] = "authorization_code"
657
- if request is None:
658
- request = get_default_request()
659
- request_kwargs["async_"] = async_
660
- return request(
661
- url=complete_url("/api/v1/oauth2/access_token", base_url),
662
- method="POST",
663
- params=payload,
782
+ return cls.login_oauth_token(
783
+ {"grant_type": "refresh_token", "refresh_token": refresh_token},
784
+ base_url=base_url,
785
+ async_=async_,
664
786
  **request_kwargs,
665
787
  )
666
788
 
@@ -699,6 +821,7 @@ class P123OpenClient:
699
821
  GET https://open-api.123pan.com/api/v1/developer/config/forbide-ip/list
700
822
 
701
823
  .. admonition:: Reference
824
+
702
825
  /API列表/直链/IP黑名单配置/ip黑名单列表
703
826
 
704
827
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/mxldrm9d5gpw5h2d
@@ -745,6 +868,7 @@ class P123OpenClient:
745
868
  POST https://open-api.123pan.com/api/v1/developer/config/forbide-ip/switch
746
869
 
747
870
  .. admonition:: Reference
871
+
748
872
  /API列表/直链/IP黑名单配置/开启关闭ip黑名单
749
873
 
750
874
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xwx77dbzrkxquuxm
@@ -796,6 +920,7 @@ class P123OpenClient:
796
920
  POST https://open-api.123pan.com/api/v1/developer/config/forbide-ip/update
797
921
 
798
922
  .. admonition:: Reference
923
+
799
924
  /API列表/直链/IP黑名单配置/更新ip黑名单列表
800
925
 
801
926
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tt3s54slh87q8wuh
@@ -851,6 +976,7 @@ class P123OpenClient:
851
976
  POST https://open-api.123pan.com/api/v1/direct-link/disable
852
977
 
853
978
  .. admonition:: Reference
979
+
854
980
  /API列表/直链/禁用直链空间
855
981
 
856
982
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ccgz6fwf25nd9psl
@@ -899,6 +1025,7 @@ class P123OpenClient:
899
1025
  POST https://open-api.123pan.com/api/v1/direct-link/enable
900
1026
 
901
1027
  .. admonition:: Reference
1028
+
902
1029
  /API列表/直链/启用直链空间
903
1030
 
904
1031
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/cl3gvdmho288d376
@@ -947,6 +1074,7 @@ class P123OpenClient:
947
1074
  GET https://open-api.123pan.com/api/v1/direct-link/log
948
1075
 
949
1076
  .. admonition:: Reference
1077
+
950
1078
  /API列表/直链/获取直链日志
951
1079
 
952
1080
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/agmqpmu0dm0iogc9
@@ -1011,18 +1139,18 @@ class P123OpenClient:
1011
1139
  :return:
1012
1140
  响应数据的 data 字段是一个字典,键值如下:
1013
1141
 
1014
- +---------------------+--------+----------+--------------------------------------------------------------+
1015
- | 名称 | 类型 | 是否必填 | 说明 |
1016
- +=====================+========+==========+==============================================================+
1017
- | list | array | 必填 | 响应列表 |
1018
- +---------------------+--------+----------+--------------------------------------------------------------|
1019
- | list[*].resolutions | string | 必填 | 分辨率 |
1020
- +---------------------+--------+----------+--------------------------------------------------------------|
1021
- | list[*].address | string | 必填 | 播放地址。请将播放地址放入支持的 hls 协议的播放器中进行播放。|
1022
- | | | | 示例在线播放地址: https://m3u8-player.com/ |
1023
- | | | | 请注意:转码链接播放过程中将会消耗您的直链流量。 |
1024
- | | | | 如果您开启了直链鉴权,也需要将转码链接根据鉴权指引进行签名。 |
1025
- +---------------------+--------+----------+--------------------------------------------------------------+
1142
+ +-------------------------+--------+----------+----------------------------------------------------------------+
1143
+ | 名称 | 类型 | 是否必填 | 说明 |
1144
+ +=========================+========+==========+================================================================+
1145
+ | ``list`` | array | 必填 | 响应列表 |
1146
+ +-------------------------+--------+----------+----------------------------------------------------------------+
1147
+ | ``list[*].resolutions`` | string | 必填 | 分辨率 |
1148
+ +-------------------------+--------+----------+----------------------------------------------------------------+
1149
+ | ``list[*].address`` | string | 必填 | | 播放地址。请将播放地址放入支持的 hls 协议的播放器中进行播放。|
1150
+ | | | | | 示例在线播放地址: https://m3u8-player.com/ |
1151
+ | | | | | 请注意:转码链接播放过程中将会消耗您的直链流量。 |
1152
+ | | | | | 如果您开启了直链鉴权,也需要将转码链接根据鉴权指引进行签名。 |
1153
+ +-------------------------+--------+----------+----------------------------------------------------------------+
1026
1154
  """
1027
1155
  api = complete_url("/api/v1/direct-link/get/m3u8", base_url)
1028
1156
  if not isinstance(payload, dict):
@@ -1065,6 +1193,7 @@ class P123OpenClient:
1065
1193
  GET https://open-api.123pan.com/api/v1/direct-link/offline/logs
1066
1194
 
1067
1195
  .. admonition:: Reference
1196
+
1068
1197
  /API列表/直链/获取直链离线日志
1069
1198
 
1070
1199
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/yz4bdynw9yx5erqb
@@ -1229,6 +1358,7 @@ class P123OpenClient:
1229
1358
  GET https://open-api.123pan.com/api/v1/direct-link/url
1230
1359
 
1231
1360
  .. admonition:: Reference
1361
+
1232
1362
  /API列表/直链/获取直链链接
1233
1363
 
1234
1364
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tdxfsmtemp4gu4o2
@@ -1279,6 +1409,7 @@ class P123OpenClient:
1279
1409
  GET https://open-api.123pan.com/api/v1/file/download_info
1280
1410
 
1281
1411
  .. admonition:: Reference
1412
+
1282
1413
  /API列表/文件管理/下载
1283
1414
 
1284
1415
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/fnf60phsushn8ip2
@@ -1333,6 +1464,7 @@ class P123OpenClient:
1333
1464
  彻底删除文件前,文件必须要在回收站中,否则无法删除
1334
1465
 
1335
1466
  .. admonition:: Reference
1467
+
1336
1468
  /API列表/文件管理/删除/彻底删除文件
1337
1469
 
1338
1470
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/sg2gvfk5i3dwoxtg
@@ -1385,6 +1517,7 @@ class P123OpenClient:
1385
1517
  GET https://open-api.123pan.com/api/v1/file/detail
1386
1518
 
1387
1519
  .. admonition:: Reference
1520
+
1388
1521
  /API列表/文件管理/文件详情/获取单个文件详情
1389
1522
 
1390
1523
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/owapsz373dzwiqbp
@@ -1436,6 +1569,7 @@ class P123OpenClient:
1436
1569
  POST https://open-api.123pan.com/api/v1/file/infos
1437
1570
 
1438
1571
  .. admonition:: Reference
1572
+
1439
1573
  /API列表/文件管理/文件详情/获取多个文件详情
1440
1574
 
1441
1575
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/cqqayfuxybegrlru
@@ -1488,6 +1622,7 @@ class P123OpenClient:
1488
1622
  GET https://open-api.123pan.com/api/v2/file/list
1489
1623
 
1490
1624
  .. admonition:: Reference
1625
+
1491
1626
  /API列表/文件管理/文件列表/获取文件列表(推荐)
1492
1627
 
1493
1628
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/zrip9b0ye81zimv4
@@ -1505,7 +1640,7 @@ class P123OpenClient:
1505
1640
  其它则代表下一页开始的文件 id,携带到请求参数中,可查询下一页。
1506
1641
 
1507
1642
  .. caution::
1508
- 此接口查询结果包含回收站的文件,需自行根据字段 `trashed` 判断处理
1643
+ 此接口查询结果包含回收站的文件,需自行根据字段 ``trashed`` 判断处理
1509
1644
 
1510
1645
  :payload:
1511
1646
  - businessType: int = <default> 💡 业务类型:2:转码空间
@@ -1513,7 +1648,7 @@ class P123OpenClient:
1513
1648
  - lastFileId: int = <default> 💡 上一页的最后一条记录的 FileID,翻页查询时需要填写
1514
1649
  - limit: int = 100 💡 分页大小,最多 100
1515
1650
  - parentFileId: int | str = 0 💡 父目录 id,根目录是 0
1516
- - searchData: str = <default> 💡 搜索关键字,将无视 `parentFileId`,而进行全局查找
1651
+ - searchData: str = <default> 💡 搜索关键字,将无视 ``parentFileId``,而进行全局查找
1517
1652
  - searchMode: 0 | 1 = 0 💡 搜索模式
1518
1653
 
1519
1654
  - 0: 模糊搜索(将会根据搜索项分词,查找出相似的匹配项)
@@ -1570,12 +1705,13 @@ class P123OpenClient:
1570
1705
  GET https://open-api.123pan.com/api/v1/file/list
1571
1706
 
1572
1707
  .. admonition:: Reference
1708
+
1573
1709
  /API列表/文件管理/文件列表/获取文件列表(旧)
1574
1710
 
1575
1711
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/hosdqqax0knovnm2
1576
1712
 
1577
1713
  .. note::
1578
- 是否有下一页需要自行判断。如果返回的列表大小 < `limit`,或者根据返回值里的 "total",如果 = `page * limit`,都说明没有下一页
1714
+ 是否有下一页需要自行判断。如果返回的列表大小 < ``limit``,或者根据返回值里的 "total",如果 = ``page * limit``,都说明没有下一页
1579
1715
 
1580
1716
  :payload:
1581
1717
  - limit: int = 100 💡 分页大小,最多 100
@@ -1597,7 +1733,7 @@ class P123OpenClient:
1597
1733
  - page: int = 1 💡 第几页,从 1 开始(可传 0 或不传,视为 1)
1598
1734
  - parentFileId: int | str = 0 💡 父目录 id,根目录是 0
1599
1735
  - trashed: "false" | "true" = "false" 💡 是否查看回收站的文件
1600
- - searchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
1736
+ - searchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
1601
1737
  """
1602
1738
  api = complete_url("/api/v1/file/list", base_url)
1603
1739
  if isinstance(payload, (int, str)):
@@ -1651,6 +1787,7 @@ class P123OpenClient:
1651
1787
  POST https://open-api.123pan.com/upload/v1/file/mkdir
1652
1788
 
1653
1789
  .. admonition:: Reference
1790
+
1654
1791
  /API列表/文件管理/上传/创建目录
1655
1792
 
1656
1793
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ouyvcxqg3185zzk4
@@ -1704,6 +1841,7 @@ class P123OpenClient:
1704
1841
  POST https://open-api.123pan.com/api/v1/file/move
1705
1842
 
1706
1843
  .. admonition:: Reference
1844
+
1707
1845
  /API列表/文件管理/移动
1708
1846
 
1709
1847
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/rsyfsn1gnpgo4m4f
@@ -1758,6 +1896,7 @@ class P123OpenClient:
1758
1896
  POST https://open-api.123pan.com/api/v1/file/recover
1759
1897
 
1760
1898
  .. admonition:: Reference
1899
+
1761
1900
  /API列表/文件管理/删除/从回收站恢复文件
1762
1901
 
1763
1902
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/kx9f8b6wk6g55uwy
@@ -1775,164 +1914,470 @@ class P123OpenClient:
1775
1914
  return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
1776
1915
 
1777
1916
  @overload
1778
- def fs_rename(
1779
- self,
1780
- payload: dict | str | tuple[int | str, str] | Iterable[str | tuple[int | str, str]],
1917
+ def fs_rename(
1918
+ self,
1919
+ payload: dict | str | tuple[int | str, str] | Iterable[str | tuple[int | str, str]],
1920
+ /,
1921
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1922
+ *,
1923
+ async_: Literal[False] = False,
1924
+ **request_kwargs,
1925
+ ) -> dict:
1926
+ ...
1927
+ @overload
1928
+ def fs_rename(
1929
+ self,
1930
+ payload: dict | str | tuple[int | str, str] | Iterable[str | tuple[int | str, str]],
1931
+ /,
1932
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1933
+ *,
1934
+ async_: Literal[True],
1935
+ **request_kwargs,
1936
+ ) -> Coroutine[Any, Any, dict]:
1937
+ ...
1938
+ def fs_rename(
1939
+ self,
1940
+ payload: dict | str | tuple[int | str, str] | Iterable[str | tuple[int | str, str]],
1941
+ /,
1942
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1943
+ *,
1944
+ async_: Literal[False, True] = False,
1945
+ **request_kwargs,
1946
+ ) -> dict | Coroutine[Any, Any, dict]:
1947
+ """批量文件重命名
1948
+
1949
+ POST https://open-api.123pan.com/api/v1/file/rename
1950
+
1951
+ .. admonition:: Reference
1952
+
1953
+ /API列表/文件管理/重命名/批量文件重命名
1954
+
1955
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/imhguepnr727aquk
1956
+
1957
+ :payload:
1958
+ - renameList: list[str] 💡 列表,每个成员的格式为 f"{fileId}|{fileName}",最多 30 个
1959
+ """
1960
+ api = complete_url("/api/v1/file/rename", base_url)
1961
+ if not isinstance(payload, dict):
1962
+ if isinstance(payload, str):
1963
+ payload = [payload]
1964
+ elif isinstance(payload, tuple):
1965
+ payload = ["%s|%s" % payload]
1966
+ else:
1967
+ payload = [s if isinstance(s, str) else "%s|%s" % s for s in payload]
1968
+ payload = {"renameList": payload}
1969
+ return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
1970
+
1971
+ @overload
1972
+ def fs_rename_one(
1973
+ self,
1974
+ payload: dict | str | tuple[int | str, str],
1975
+ /,
1976
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1977
+ *,
1978
+ async_: Literal[False] = False,
1979
+ **request_kwargs,
1980
+ ) -> dict:
1981
+ ...
1982
+ @overload
1983
+ def fs_rename_one(
1984
+ self,
1985
+ payload: dict | str | tuple[int | str, str],
1986
+ /,
1987
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1988
+ *,
1989
+ async_: Literal[True],
1990
+ **request_kwargs,
1991
+ ) -> Coroutine[Any, Any, dict]:
1992
+ ...
1993
+ def fs_rename_one(
1994
+ self,
1995
+ payload: dict | str | tuple[int | str, str],
1996
+ /,
1997
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
1998
+ *,
1999
+ async_: Literal[False, True] = False,
2000
+ **request_kwargs,
2001
+ ) -> dict | Coroutine[Any, Any, dict]:
2002
+ """单个文件重命名
2003
+
2004
+ PUT https://open-api.123pan.com/api/v1/file/name
2005
+
2006
+ .. admonition:: Reference
2007
+
2008
+ /API列表/文件管理/重命名/单个文件重命名
2009
+
2010
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ha6mfe9tteht5skc
2011
+
2012
+ :payload:
2013
+ - fileId: int 💡 文件 id
2014
+ - fileName: str 💡 文件名
2015
+ """
2016
+ api = complete_url("/api/v1/file/name", base_url)
2017
+ if not isinstance(payload, dict):
2018
+ fid: int | str
2019
+ if isinstance(payload, str):
2020
+ fid, name = payload.split("|", 1)
2021
+ else:
2022
+ fid, name = payload
2023
+ payload = {"fileId": fid, "fileName": name}
2024
+ return self.request(api, "PUT", json=payload, async_=async_, **request_kwargs)
2025
+
2026
+ @overload
2027
+ def fs_trash(
2028
+ self,
2029
+ payload: dict | int | str | Iterable[int | str],
2030
+ /,
2031
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2032
+ *,
2033
+ async_: Literal[False] = False,
2034
+ **request_kwargs,
2035
+ ) -> dict:
2036
+ ...
2037
+ @overload
2038
+ def fs_trash(
2039
+ self,
2040
+ payload: dict | int | str | Iterable[int | str],
2041
+ /,
2042
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2043
+ *,
2044
+ async_: Literal[True],
2045
+ **request_kwargs,
2046
+ ) -> Coroutine[Any, Any, dict]:
2047
+ ...
2048
+ def fs_trash(
2049
+ self,
2050
+ payload: dict | int | str | Iterable[int | str],
2051
+ /,
2052
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2053
+ *,
2054
+ async_: Literal[False, True] = False,
2055
+ **request_kwargs,
2056
+ ) -> dict | Coroutine[Any, Any, dict]:
2057
+ """删除文件至回收站
2058
+
2059
+ POST https://open-api.123pan.com/api/v1/file/trash
2060
+
2061
+ .. admonition:: Reference
2062
+
2063
+ /API列表/文件管理/删除/删除文件至回收站
2064
+
2065
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/en07662k2kki4bo6
2066
+
2067
+ :payload:
2068
+ - fileIDs: list[int] 💡 文件 id 列表,最多 100 个
2069
+ """
2070
+ api = complete_url("/api/v1/file/trash", base_url)
2071
+ if not isinstance(payload, dict):
2072
+ if isinstance(payload, (int, str)):
2073
+ payload = [payload]
2074
+ elif not isinstance(payload, (tuple, list)):
2075
+ payload = list(payload)
2076
+ payload = {"fileIDs": payload}
2077
+ return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
2078
+
2079
+ ########## Login API ##########
2080
+
2081
+ @overload
2082
+ @staticmethod
2083
+ def login_token(
2084
+ payload: dict,
2085
+ /,
2086
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2087
+ request: None | Callable = None,
2088
+ *,
2089
+ async_: Literal[False] = False,
2090
+ **request_kwargs,
2091
+ ) -> dict:
2092
+ ...
2093
+ @overload
2094
+ @staticmethod
2095
+ def login_token(
2096
+ payload: dict,
2097
+ /,
2098
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2099
+ request: None | Callable = None,
2100
+ *,
2101
+ async_: Literal[True],
2102
+ **request_kwargs,
2103
+ ) -> Coroutine[Any, Any, dict]:
2104
+ ...
2105
+ @staticmethod
2106
+ def login_token(
2107
+ payload: dict,
2108
+ /,
2109
+ base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2110
+ request: None | Callable = None,
2111
+ *,
2112
+ async_: Literal[False, True] = False,
2113
+ **request_kwargs,
2114
+ ) -> dict | Coroutine[Any, Any, dict]:
2115
+ """获取access_token
2116
+
2117
+ POST https://open-api.123pan.com/api/v1/access_token
2118
+
2119
+ .. attention::
2120
+ 此接口有访问频率限制。请获取到 ``access_token`` 后本地保存使用,并在 `access_token `过期前及时重新获取。``access_token`` 有效期根据返回的 "expiredAt" 字段判断。
2121
+
2122
+ .. note::
2123
+ 通过这种方式授权得到的 ``access_token``,各个接口分别允许一个较低的 QPS
2124
+
2125
+ /接入指南/开发者接入/开发须知
2126
+
2127
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/txgcvbfgh0gtuad5
2128
+
2129
+ .. admonition:: Reference
2130
+
2131
+ /接入指南/开发者接入/获取access_token
2132
+
2133
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gn1nai4x0v0ry9ki
2134
+
2135
+ :payload:
2136
+ - clientID: str 💡 应用标识,创建应用时分配的 appId
2137
+ - clientSecret: str 💡 应用密钥,创建应用时分配的 secretId
2138
+ """
2139
+ request_kwargs.setdefault("parse", default_parse)
2140
+ if request is None:
2141
+ if headers := request_kwargs.get("headers"):
2142
+ headers = dict(headers, platform="open_platform")
2143
+ else:
2144
+ headers = {"platform": "open_platform"}
2145
+ request_kwargs["headers"] = headers
2146
+ request = get_default_request()
2147
+ request_kwargs["async_"] = async_
2148
+ return request(
2149
+ url=complete_url("/api/v1/access_token", base_url),
2150
+ method="POST",
2151
+ json=payload,
2152
+ **request_kwargs,
2153
+ )
2154
+
2155
+ @overload
2156
+ @staticmethod
2157
+ def login_oauth_authorize(
2158
+ payload: dict,
1781
2159
  /,
1782
2160
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2161
+ request: None | Callable = None,
1783
2162
  *,
1784
2163
  async_: Literal[False] = False,
1785
2164
  **request_kwargs,
1786
2165
  ) -> dict:
1787
2166
  ...
1788
2167
  @overload
1789
- def fs_rename(
1790
- self,
1791
- payload: dict | str | tuple[int | str, str] | Iterable[str | tuple[int | str, str]],
2168
+ @staticmethod
2169
+ def login_oauth_authorize(
2170
+ payload: dict,
1792
2171
  /,
1793
2172
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2173
+ request: None | Callable = None,
1794
2174
  *,
1795
2175
  async_: Literal[True],
1796
2176
  **request_kwargs,
1797
2177
  ) -> Coroutine[Any, Any, dict]:
1798
2178
  ...
1799
- def fs_rename(
1800
- self,
1801
- payload: dict | str | tuple[int | str, str] | Iterable[str | tuple[int | str, str]],
2179
+ @staticmethod
2180
+ def login_oauth_authorize(
2181
+ payload: dict,
1802
2182
  /,
1803
2183
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2184
+ request: None | Callable = None,
1804
2185
  *,
1805
2186
  async_: Literal[False, True] = False,
1806
2187
  **request_kwargs,
1807
2188
  ) -> dict | Coroutine[Any, Any, dict]:
1808
- """批量文件重命名
2189
+ """授权以获取和 ``accessToken`` 绑定的 ``code``
1809
2190
 
1810
- POST https://open-api.123pan.com/api/v1/file/rename
2191
+ GET https://open-api.123pan.com/api/v1/oauth2/user/authorize
1811
2192
 
1812
2193
  .. admonition:: Reference
1813
- /API列表/文件管理/重命名/批量文件重命名
1814
2194
 
1815
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/imhguepnr727aquk
2195
+ /接入指南/第三方挂载应用接入/授权地址
2196
+
2197
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gr7ggimkcysm18ap
1816
2198
 
1817
2199
  :payload:
1818
- - renameList: list[str] 💡 列表,每个成员的格式为 f"{fileId}|{fileName}",最多 30 个
1819
- """
1820
- api = complete_url("/api/v1/file/rename", base_url)
1821
- if not isinstance(payload, dict):
1822
- if isinstance(payload, str):
1823
- payload = [payload]
1824
- elif isinstance(payload, tuple):
1825
- payload = ["%s|%s" % payload]
2200
+ - accessToken: str 💡 访问令牌
2201
+ - client_id: str 💡 应用标识,创建应用时分配的 appId
2202
+ - redirect_uri: str 💡 回调链接
2203
+ - scope: str = "user:base,file:all:read,file:all:write" 💡 权限
2204
+ - response_type: str = "code"
2205
+ - state: str = <default>
2206
+ """
2207
+ def parse(resp, _, /):
2208
+ url = resp.headers["location"]
2209
+ data = dict(parse_qsl(urlsplit(url).query))
2210
+ if "code" in data:
2211
+ code = 0
2212
+ message = "ok"
1826
2213
  else:
1827
- payload = [s if isinstance(s, str) else "%s|%s" % s for s in payload]
1828
- payload = {"renameList": payload}
1829
- return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
2214
+ code = 1
2215
+ message = data.get("error_description") or "error"
2216
+ return {
2217
+ "code": code,
2218
+ "message": message,
2219
+ "url": url,
2220
+ "data": data,
2221
+ "headers": dict(resp.headers),
2222
+ }
2223
+ request_kwargs.setdefault("parse", parse)
2224
+ for key in ("allow_redirects", "follow_redirects", "redirect"):
2225
+ request_kwargs[key] = False
2226
+ payload = dict_to_lower_merge(
2227
+ payload,
2228
+ response_type="code",
2229
+ scope="user:base,file:all:read,file:all:write",
2230
+ )
2231
+ if request is None:
2232
+ request = get_default_request()
2233
+ request_kwargs["async_"] = async_
2234
+ return request(
2235
+ url=complete_url("/api/v1/oauth2/user/authorize", base_url),
2236
+ params=payload,
2237
+ **request_kwargs,
2238
+ )
1830
2239
 
1831
2240
  @overload
1832
- def fs_rename_one(
1833
- self,
1834
- payload: dict | str | tuple[int | str, str],
2241
+ @staticmethod
2242
+ def login_oauth_token(
2243
+ payload: dict,
1835
2244
  /,
1836
2245
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2246
+ request: None | Callable = None,
1837
2247
  *,
1838
2248
  async_: Literal[False] = False,
1839
2249
  **request_kwargs,
1840
2250
  ) -> dict:
1841
2251
  ...
1842
2252
  @overload
1843
- def fs_rename_one(
1844
- self,
1845
- payload: dict | str | tuple[int | str, str],
2253
+ @staticmethod
2254
+ def login_oauth_token(
2255
+ payload: dict,
1846
2256
  /,
1847
2257
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2258
+ request: None | Callable = None,
1848
2259
  *,
1849
2260
  async_: Literal[True],
1850
2261
  **request_kwargs,
1851
2262
  ) -> Coroutine[Any, Any, dict]:
1852
2263
  ...
1853
- def fs_rename_one(
1854
- self,
1855
- payload: dict | str | tuple[int | str, str],
2264
+ @staticmethod
2265
+ def login_oauth_token(
2266
+ payload: dict,
1856
2267
  /,
1857
2268
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2269
+ request: None | Callable = None,
1858
2270
  *,
1859
2271
  async_: Literal[False, True] = False,
1860
2272
  **request_kwargs,
1861
2273
  ) -> dict | Coroutine[Any, Any, dict]:
1862
- """单个文件重命名
2274
+ """通过 ``authorization_code`` 或 ``refresh_token`` 获取新的 ``access_token`` 和 ``refresh_token``
1863
2275
 
1864
- PUT https://open-api.123pan.com/api/v1/file/name
2276
+ POST https://open-api.123pan.com/api/v1/oauth2/access_token
2277
+
2278
+ .. note::
2279
+ 通过这种方式授权得到的 ``access_token``,各个接口分别允许更高的 QPS
2280
+
2281
+ /接入指南/第三方挂载应用接入/授权须知
2282
+
2283
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/kf05anzt1r0qnudd
1865
2284
 
1866
2285
  .. admonition:: Reference
1867
- /API列表/文件管理/重命名/单个文件重命名
1868
2286
 
1869
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ha6mfe9tteht5skc
2287
+ /接入指南/第三方挂载应用接入/授权code获取access_token
2288
+
2289
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gammzlhe6k4qtwd9
1870
2290
 
1871
2291
  :payload:
1872
- - fileId: int 💡 文件 id
1873
- - fileName: str 💡 文件名
2292
+ - client_id: str 💡 应用标识,创建应用时分配的 appId
2293
+ - client_secret: str 💡 应用密钥,创建应用时分配的 secretId
2294
+ - code: str = <default> 💡 授权码
2295
+ - grant_type: "authorization_code" | "refresh_token" = <default> 💡 身份类型
2296
+ - redirect_uri: str = <default> 💡 应用注册的回调地址,``grant_type`` 为 "authorization_code" 时必携带
2297
+ - refresh_token: str = <default> 💡 刷新 token,单次请求有效
1874
2298
  """
1875
- api = complete_url("/api/v1/file/name", base_url)
1876
- if not isinstance(payload, dict):
1877
- fid: int | str
1878
- if isinstance(payload, str):
1879
- fid, name = payload.split("|", 1)
2299
+ request_kwargs.setdefault("parse", default_parse)
2300
+ payload = dict_to_lower(payload)
2301
+ if not payload.get("grant_type"):
2302
+ if payload.get("refresh_token"):
2303
+ payload["grant_type"] = "refresh_token"
1880
2304
  else:
1881
- fid, name = payload
1882
- payload = {"fileId": fid, "fileName": name}
1883
- return self.request(api, "PUT", json=payload, async_=async_, **request_kwargs)
2305
+ payload["grant_type"] = "authorization_code"
2306
+ if request is None:
2307
+ if headers := request_kwargs.get("headers"):
2308
+ headers = dict(headers, platform="open_platform")
2309
+ else:
2310
+ headers = {"platform": "open_platform"}
2311
+ request_kwargs["headers"] = headers
2312
+ request = get_default_request()
2313
+ request_kwargs["async_"] = async_
2314
+ return request(
2315
+ url=complete_url("/api/v1/oauth2/access_token", base_url),
2316
+ method="POST",
2317
+ params=payload,
2318
+ **request_kwargs,
2319
+ )
1884
2320
 
1885
2321
  @overload
1886
- def fs_trash(
1887
- self,
1888
- payload: dict | int | str | Iterable[int | str],
2322
+ @staticmethod
2323
+ def login_oauth_verify(
2324
+ payload: dict,
1889
2325
  /,
1890
2326
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2327
+ request: None | Callable = None,
1891
2328
  *,
1892
2329
  async_: Literal[False] = False,
1893
2330
  **request_kwargs,
1894
2331
  ) -> dict:
1895
2332
  ...
1896
2333
  @overload
1897
- def fs_trash(
1898
- self,
1899
- payload: dict | int | str | Iterable[int | str],
2334
+ @staticmethod
2335
+ def login_oauth_verify(
2336
+ payload: dict,
1900
2337
  /,
1901
2338
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2339
+ request: None | Callable = None,
1902
2340
  *,
1903
2341
  async_: Literal[True],
1904
2342
  **request_kwargs,
1905
2343
  ) -> Coroutine[Any, Any, dict]:
1906
2344
  ...
1907
- def fs_trash(
1908
- self,
1909
- payload: dict | int | str | Iterable[int | str],
2345
+ @staticmethod
2346
+ def login_oauth_verify(
2347
+ payload: dict,
1910
2348
  /,
1911
2349
  base_url: str | Callable[[], str] = DEFAULT_OPEN_BASE_URL,
2350
+ request: None | Callable = None,
1912
2351
  *,
1913
2352
  async_: Literal[False, True] = False,
1914
2353
  **request_kwargs,
1915
2354
  ) -> dict | Coroutine[Any, Any, dict]:
1916
- """删除文件至回收站
2355
+ """检查 ``appId`` 对应的 ``redirectUri`` 是否可用
1917
2356
 
1918
- POST https://open-api.123pan.com/api/v1/file/trash
2357
+ POST https://open-api.123pan.com/api/v1/oauth2/app/verify
1919
2358
 
1920
2359
  .. admonition:: Reference
1921
- /API列表/文件管理/删除/删除文件至回收站
1922
2360
 
1923
- https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/en07662k2kki4bo6
2361
+ /接入指南/第三方挂载应用接入/授权地址
2362
+
2363
+ https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/gr7ggimkcysm18ap
1924
2364
 
1925
2365
  :payload:
1926
- - fileIDs: list[int] 💡 文件 id 列表,最多 100 个
2366
+ - appId: str 💡 应用标识,创建应用时分配的 appId
2367
+ - redirectUri: str 💡 回调链接
2368
+ - scope: str = "user:base,file:all:read,file:all:write" 💡 权限
1927
2369
  """
1928
- api = complete_url("/api/v1/file/trash", base_url)
1929
- if not isinstance(payload, dict):
1930
- if isinstance(payload, (int, str)):
1931
- payload = [payload]
1932
- elif not isinstance(payload, (tuple, list)):
1933
- payload = list(payload)
1934
- payload = {"fileIDs": payload}
1935
- return self.request(api, "POST", json=payload, async_=async_, **request_kwargs)
2370
+ request_kwargs.setdefault("parse", default_parse)
2371
+ payload = dict_to_lower_merge(payload, scope="user:base,file:all:read,file:all:write")
2372
+ if request is None:
2373
+ request = get_default_request()
2374
+ request_kwargs["async_"] = async_
2375
+ return request(
2376
+ url=complete_url("/api/v1/oauth2/app/verify", base_url),
2377
+ method="POST",
2378
+ json=payload,
2379
+ **request_kwargs,
2380
+ )
1936
2381
 
1937
2382
  ########## Offline Download API ##########
1938
2383
 
@@ -1972,6 +2417,7 @@ class P123OpenClient:
1972
2417
  POST https://open-api.123pan.com/api/v1/offline/download
1973
2418
 
1974
2419
  .. admonition:: Reference
2420
+
1975
2421
  /API列表/离线下载/创建离线下载任务
1976
2422
 
1977
2423
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/he47hsq2o1xvgado
@@ -2033,6 +2479,7 @@ class P123OpenClient:
2033
2479
  GET https://open-api.123pan.com/api/v1/offline/download/process
2034
2480
 
2035
2481
  .. admonition:: Reference
2482
+
2036
2483
  /API列表/离线下载/获取离线下载进度
2037
2484
 
2038
2485
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/sclficr3t655pii5
@@ -2086,11 +2533,14 @@ class P123OpenClient:
2086
2533
  POST https://open-api.123pan.com/api/v1/oss/source/copy
2087
2534
 
2088
2535
  .. note::
2089
- 说明:图床复制任务创建(可创建的任务数:3,fileIDs 长度限制:100,当前一个任务处理完后将会继续处理下个任务)
2090
- 该接口将会复制云盘里的文件或目录对应的图片到对应图床目录,每次任务包含的图片总数限制 1000 张,图片格式:png, gif, jpeg, tiff, webp,jpg,tif,svg,bmp,图片大小限制:100M,文件夹层级限制:15层
2091
- 如果图床目录下存在相同 etag、size 的图片将会视为同一张图片,将覆盖原图片
2536
+ 图床复制任务创建(可创建的任务数:3,fileIDs 长度限制:100,当前一个任务处理完后将会继续处理下个任务)。
2537
+
2538
+ 该接口将会复制云盘里的文件或目录对应的图片到对应图床目录,每次任务包含的图片总数限制 1000 张,图片格式:png, gif, jpeg, tiff, webp,jpg,tif,svg,bmp,图片大小限制:100M,文件夹层级限制:15层。
2539
+
2540
+ 如果图床目录下存在相同 etag、size 的图片将会视为同一张图片,将覆盖原图片
2092
2541
 
2093
2542
  .. admonition:: Reference
2543
+
2094
2544
  /API列表/图床/复制云盘图片/创建复制任务
2095
2545
 
2096
2546
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/trahy3lmds4o0i3r
@@ -2151,6 +2601,7 @@ class P123OpenClient:
2151
2601
  GET https://open-api.123pan.com/api/v1/oss/source/copy/process
2152
2602
 
2153
2603
  .. admonition:: Reference
2604
+
2154
2605
  /API列表/图床/复制云盘图片/获取复制任务详情
2155
2606
 
2156
2607
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/rissl4ewklaui4th
@@ -2199,6 +2650,7 @@ class P123OpenClient:
2199
2650
  GET https://open-api.123pan.com/api/v1/oss/source/copy/fail
2200
2651
 
2201
2652
  .. admonition:: Reference
2653
+
2202
2654
  /API列表/图床/复制云盘图片/获取复制失败文件列表
2203
2655
 
2204
2656
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tlug9od3xlw2w23v
@@ -2253,6 +2705,7 @@ class P123OpenClient:
2253
2705
  彻底删除文件前,文件必须要在回收站中,否则无法删除
2254
2706
 
2255
2707
  .. admonition:: Reference
2708
+
2256
2709
  /API列表/图床/删除图片
2257
2710
 
2258
2711
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ef8yluqdzm2yttdn
@@ -2305,6 +2758,7 @@ class P123OpenClient:
2305
2758
  GET https://open-api.123pan.com/api/v1/oss/file/detail
2306
2759
 
2307
2760
  .. admonition:: Reference
2761
+
2308
2762
  /API列表/图床/获取图片信息/获取图片详情
2309
2763
 
2310
2764
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/rgf2ndfaxc2gugp8
@@ -2357,6 +2811,7 @@ class P123OpenClient:
2357
2811
  其它则代表下一页开始的文件 id,携带到请求参数中,可查询下一页
2358
2812
 
2359
2813
  .. admonition:: Reference
2814
+
2360
2815
  /API列表/图床/获取图片信息/获取图片列表
2361
2816
 
2362
2817
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/zayr72q8xd7gg4f8
@@ -2414,6 +2869,7 @@ class P123OpenClient:
2414
2869
  POST https://open-api.123pan.com/upload/v1/oss/file/mkdir
2415
2870
 
2416
2871
  .. admonition:: Reference
2872
+
2417
2873
  /API列表/图床/上传图片/创建目录
2418
2874
 
2419
2875
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tpqqm04ocqwvonrk
@@ -2468,6 +2924,7 @@ class P123OpenClient:
2468
2924
  POST https://open-api.123pan.com/api/v1/oss/file/move
2469
2925
 
2470
2926
  .. admonition:: Reference
2927
+
2471
2928
  /API列表/图床/移动图片
2472
2929
 
2473
2930
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/eqeargimuvycddna
@@ -2522,6 +2979,7 @@ class P123OpenClient:
2522
2979
  POST https://open-api.123pan.com/api/v1/oss/offline/download
2523
2980
 
2524
2981
  .. admonition:: Reference
2982
+
2525
2983
  /API列表/图床/图床离线迁移/创建离线迁移任务
2526
2984
 
2527
2985
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ctigc3a08lqzsfnq
@@ -2585,6 +3043,7 @@ class P123OpenClient:
2585
3043
  GET https://open-api.123pan.com/api/v1/oss/offline/download/process
2586
3044
 
2587
3045
  .. admonition:: Reference
3046
+
2588
3047
  /API列表/图床/图床离线迁移/获取离线迁移任务
2589
3048
 
2590
3049
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/svo92desugbyhrgq
@@ -2633,11 +3092,12 @@ class P123OpenClient:
2633
3092
  POST https://open-api.123pan.com/upload/v1/oss/file/create
2634
3093
 
2635
3094
  .. note::
2636
- - 文件名要小于 256 个字符且不能包含以下字符:"\\/:*?|><
3095
+ - 文件名要小于 256 个字符且不能包含以下字符:``"\\/:*?|><``
2637
3096
  - 文件名不能全部是空格
2638
3097
  - 不会重名
2639
3098
 
2640
3099
  .. admonition:: Reference
3100
+
2641
3101
  /API列表/图床/上传图片/创建文件
2642
3102
 
2643
3103
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xwfka5kt6vtmgs8r
@@ -2656,10 +3116,10 @@ class P123OpenClient:
2656
3116
  .. code:: python
2657
3117
 
2658
3118
  {
2659
- "fileID": str, # 上传后的文件 id。当已有相同 `size``etag` 的文件时,会发生秒传
2660
- "preuploadID": str, # 预上传 id。当 `reuse` 为 "true" 时,该字段不存在
3119
+ "fileID": str, # 上传后的文件 id。当已有相同 ``size````etag`` 的文件时,会发生秒传
3120
+ "preuploadID": str, # 预上传 id。当 ``reuse`` 为 "true" 时,该字段不存在
2661
3121
  "reuse": bool, # 是否秒传,返回 "true" 时表示文件已上传成功
2662
- "sliceSize": int, # 分片大小,必须按此大小生成文件分片再上传。当 `reuse` 为 "true" 时,该字段不存在
3122
+ "sliceSize": int, # 分片大小,必须按此大小生成文件分片再上传。当 ``reuse`` 为 "true" 时,该字段不存在
2663
3123
  }
2664
3124
  """
2665
3125
  api = complete_url("/upload/v1/oss/file/create", base_url)
@@ -2707,6 +3167,7 @@ class P123OpenClient:
2707
3167
  有多个分片时,轮流分别根据序号获取下载链接,然后 PUT 方法上传分片。由于上传链接会过期,所以没必要提前获取一大批
2708
3168
 
2709
3169
  .. admonition:: Reference
3170
+
2710
3171
  /API列表/图床/上传图片/获取上传地址&上传分片
2711
3172
 
2712
3173
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/pyfo3a39q6ac0ocd
@@ -2800,6 +3261,7 @@ class P123OpenClient:
2800
3261
  POST https://open-api.123pan.com/upload/v1/oss/file/upload_complete
2801
3262
 
2802
3263
  .. admonition:: Reference
3264
+
2803
3265
  /API列表/图床/上传图片/上传完毕
2804
3266
 
2805
3267
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/yhgo0kt3nkngi8r2
@@ -2859,6 +3321,7 @@ class P123OpenClient:
2859
3321
  POST https://open-api.123pan.com/upload/v1/oss/file/upload_async_result
2860
3322
 
2861
3323
  .. admonition:: Reference
3324
+
2862
3325
  /API列表/图床/上传图片/异步轮询获取上传结果
2863
3326
 
2864
3327
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/lbdq2cbyzfzayipu
@@ -2939,27 +3402,28 @@ class P123OpenClient:
2939
3402
  """上传文件
2940
3403
 
2941
3404
  .. note::
2942
- 如果文件名中包含字符 "\\/:*?|><,则转换为对应的全角字符
3405
+ 如果文件名中包含字符 ``"\\/:*?|><``,则转换为对应的全角字符
2943
3406
 
2944
3407
  .. admonition:: Reference
3408
+
2945
3409
  /API列表/图床/上传图片/💡上传流程说明
2946
3410
 
2947
3411
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/di0url3qn13tk28t
2948
3412
 
2949
3413
  :param file: 待上传的文件
2950
3414
 
2951
- - 如果为 `collections.abc.Buffer`,则作为二进制数据上传
2952
- - 如果为 `filewrap.SupportsRead`,则作为可读的二进制文件上传
2953
- - 如果为 `str``os.PathLike`,则视为路径,打开后作为文件上传
2954
- - 如果为 `yarl.URL``http_request.SupportsGeturl` (`pip install python-http_request`),则视为超链接,打开后作为文件上传
2955
- - 如果为 `collections.abc.Iterable[collections.abc.Buffer]``collections.abc.AsyncIterable[collections.abc.Buffer]`,则迭代以获取二进制数据,逐步上传
3415
+ - 如果为 ``collections.abc.Buffer``,则作为二进制数据上传
3416
+ - 如果为 ``filewrap.SupportsRead``,则作为可读的二进制文件上传
3417
+ - 如果为 ``str````os.PathLike``,则视为路径,打开后作为文件上传
3418
+ - 如果为 ``yarl.URL````http_request.SupportsGeturl`` (``pip install python-http_request``),则视为超链接,打开后作为文件上传
3419
+ - 如果为 ``collections.abc.Iterable[collections.abc.Buffer]````collections.abc.AsyncIterable[collections.abc.Buffer]``,则迭代以获取二进制数据,逐步上传
2956
3420
 
2957
3421
  :param file_md5: 文件的 MD5 散列值
2958
3422
  :param file_name: 文件名
2959
3423
  :param file_size: 文件大小
2960
3424
  :param parent_id: 要上传的目标目录,默认为根目录
2961
3425
  :param duplicate: 处理同名:0: 提示/忽略 1: 保留两者 2: 替换
2962
- :param preupload_id: 预上传 id,用于断点续传,提供此参数,则会忽略 `file_md5`、`file_name`、`file_size`、`parent_id``duplicate`
3426
+ :param preupload_id: 预上传 id,用于断点续传,提供此参数,则会忽略 ``file_md5``、``file_name``、``file_size``、``parent_id````duplicate``
2963
3427
  :param slice_size: 分块大小,断点续传时,如果只上传过少于 2 个分块时,会被使用
2964
3428
  :param async_: 是否异步
2965
3429
  :param request_kwargs: 其它请求参数
@@ -3223,6 +3687,7 @@ class P123OpenClient:
3223
3687
  POST https://open-api.123pan.com/api/v1/share/create
3224
3688
 
3225
3689
  .. admonition:: Reference
3690
+
3226
3691
  /API列表/分享管理/创建分享链接
3227
3692
 
3228
3693
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/dwd2ss0qnpab5i5s
@@ -3230,7 +3695,7 @@ class P123OpenClient:
3230
3695
  :payload:
3231
3696
  - fileIDList: str 💡 分享文件 id 列表,最多 100 个,用逗号,分隔连接
3232
3697
  - shareExpire: 0 | 1 | 7 | 30 = 0 💡 分享链接有效期天数,0 为永久
3233
- - shareName: str 💡 分享链接名称,须小于 35 个字符且不能包含特殊字符 "\\/:*?|><
3698
+ - shareName: str 💡 分享链接名称,须小于 35 个字符且不能包含特殊字符 ``"\\/:*?|><``
3234
3699
  - sharePwd: str = "" 💡 设置分享链接提取码
3235
3700
  - trafficLimit: int = <default> 💡 免登陆限制流量,单位:字节
3236
3701
  - trafficLimitSwitch: 1 | 2 = <default> 💡 免登录流量限制开关:1:关闭 2:打开
@@ -3276,6 +3741,7 @@ class P123OpenClient:
3276
3741
  POST https://open-api.123pan.com/api/v1/share/content-payment/create
3277
3742
 
3278
3743
  .. admonition:: Reference
3744
+
3279
3745
  /API列表/分享管理/创建付费分享链接
3280
3746
 
3281
3747
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/qz30c5k2npe8l98r
@@ -3285,7 +3751,7 @@ class P123OpenClient:
3285
3751
  - isReward: 0 | 1 = 0 💡 是否开启打赏
3286
3752
  - payAmount: int = 1 💡 金额,从 1 到 99,单位:元
3287
3753
  - resourceDesc: str = "" 💡 资源描述
3288
- - shareName: str 💡 分享链接名称,须小于 35 个字符且不能包含特殊字符 "\\/:*?|><
3754
+ - shareName: str 💡 分享链接名称,须小于 35 个字符且不能包含特殊字符 ``"\\/:*?|><``
3289
3755
  """
3290
3756
  api = complete_url("/api/v1/share/content-payment/create", base_url)
3291
3757
  payload = dict_to_lower_merge(payload, {"payAmount": 1, "isReward": 0, "resourceDesc": ""})
@@ -3327,6 +3793,7 @@ class P123OpenClient:
3327
3793
  PUT https://open-api.123pan.com/api/v1/share/list/info
3328
3794
 
3329
3795
  .. admonition:: Reference
3796
+
3330
3797
  /API列表/分享管理/修改分享链接
3331
3798
 
3332
3799
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ga6hhca1u8v9yqx0
@@ -3382,6 +3849,7 @@ class P123OpenClient:
3382
3849
  GET https://open-api.123pan.com/api/v1/share/list
3383
3850
 
3384
3851
  .. admonition:: Reference
3852
+
3385
3853
  /API列表/分享管理/获取分享链接列表
3386
3854
 
3387
3855
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ixg0arldi61fe7av
@@ -3433,6 +3901,7 @@ class P123OpenClient:
3433
3901
  POST https://open-api.123pan.com/api/v1/transcode/delete
3434
3902
 
3435
3903
  .. admonition:: Reference
3904
+
3436
3905
  /API列表/视频转码/删除视频/删除转码视频
3437
3906
 
3438
3907
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tg2xgotkgmgpulrp
@@ -3484,6 +3953,7 @@ class P123OpenClient:
3484
3953
  POST https://open-api.123pan.com/api/v1/transcode/file/download
3485
3954
 
3486
3955
  .. admonition:: Reference
3956
+
3487
3957
  /API列表/视频转码/视频文件下载/原文件下载
3488
3958
 
3489
3959
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/mlltlx57sty6g9gf
@@ -3535,6 +4005,7 @@ class P123OpenClient:
3535
4005
  该接口需要轮询去查询结果,建议 10s 一次
3536
4006
 
3537
4007
  .. admonition:: Reference
4008
+
3538
4009
  /API列表/视频转码/视频文件下载/某个视频全部转码文件下载
3539
4010
 
3540
4011
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/yb7hrb0x2gym7xic
@@ -3585,6 +4056,7 @@ class P123OpenClient:
3585
4056
  POST https://open-api.123pan.com/api/v1/transcode/m3u8_ts/download
3586
4057
 
3587
4058
  .. admonition:: Reference
4059
+
3588
4060
  /API列表/视频转码/视频文件下载/单个转码文件下载(m3u8或ts)
3589
4061
 
3590
4062
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/yf97p60yyzb8mzbr
@@ -3631,6 +4103,7 @@ class P123OpenClient:
3631
4103
  POST https://open-api.123pan.com/api/v1/transcode/folder/info
3632
4104
 
3633
4105
  .. admonition:: Reference
4106
+
3634
4107
  /API列表/视频转码/获取视频信息/获取转码空间文件夹信息
3635
4108
 
3636
4109
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/kaalgke88r9y7nlt
@@ -3674,9 +4147,10 @@ class P123OpenClient:
3674
4147
  GET https://open-api.123pan.com/api/v1/video/transcode/list
3675
4148
 
3676
4149
  .. attention::
3677
- 此接口仅限授权 `access_token` 调用
4150
+ 此接口仅限授权 ``access_token`` 调用
3678
4151
 
3679
4152
  .. admonition:: Reference
4153
+
3680
4154
  /API列表/视频转码/获取视频信息/视频转码列表(三方挂载应用授权使用)
3681
4155
 
3682
4156
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tgg6g84gdrmyess5
@@ -3725,6 +4199,7 @@ class P123OpenClient:
3725
4199
  POST https://open-api.123pan.com/api/v1/transcode/video/record
3726
4200
 
3727
4201
  .. admonition:: Reference
4202
+
3728
4203
  /API列表/视频转码/查询转码信息/查询某个视频的转码记录
3729
4204
 
3730
4205
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/ost1m82sa9chh0mc
@@ -3776,6 +4251,7 @@ class P123OpenClient:
3776
4251
  POST https://open-api.123pan.com/api/v1/transcode/video/resolutions
3777
4252
 
3778
4253
  .. admonition:: Reference
4254
+
3779
4255
  /API列表/视频转码/获取视频信息/获取视频文件可转码的分辨率
3780
4256
 
3781
4257
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/apzlsgyoggmqwl36
@@ -3824,6 +4300,7 @@ class P123OpenClient:
3824
4300
  POST https://open-api.123pan.com/api/v1/transcode/video/result
3825
4301
 
3826
4302
  .. admonition:: Reference
4303
+
3827
4304
  /API列表/视频转码/查询转码信息/查询某个视频的转码结果
3828
4305
 
3829
4306
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/iucbqgge0dgfc8sv
@@ -3872,6 +4349,7 @@ class P123OpenClient:
3872
4349
  POST https://open-api.123pan.com/api/v1/transcode/upload/from_cloud_disk
3873
4350
 
3874
4351
  .. admonition:: Reference
4352
+
3875
4353
  /API列表/视频转码/上传视频/云盘上传/从云盘空间上传
3876
4354
 
3877
4355
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/tqy2xatoo4qmdbz7
@@ -3930,6 +4408,7 @@ class P123OpenClient:
3930
4408
  POST https://open-api.123pan.com/api/v1/transcode/video
3931
4409
 
3932
4410
  .. admonition:: Reference
4411
+
3933
4412
  /API列表/视频转码/视频转码/视频转码操作
3934
4413
 
3935
4414
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xy42nv2x8wav9n5l
@@ -3981,19 +4460,20 @@ class P123OpenClient:
3981
4460
  POST https://open-api.123pan.com/upload/v1/file/create
3982
4461
 
3983
4462
  .. note::
3984
- - 文件名要小于 256 个字符且不能包含以下字符:"\\/:*?|><
4463
+ - 文件名要小于 256 个字符且不能包含以下字符:``"\\/:*?|><``
3985
4464
  - 文件名不能全部是空格
3986
4465
  - 开发者上传单文件大小限制 10 GB
3987
4466
  - 不会重名
3988
4467
 
3989
4468
  .. admonition:: Reference
4469
+
3990
4470
  /API列表/文件管理/上传/V1/创建文件
3991
4471
 
3992
4472
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/lrfuu3qe7q1ul8ig
3993
4473
 
3994
4474
  :payload:
3995
4475
  - containDir: "false" | "true" = "false" 💡 上传文件是否包含路径
3996
- - filename: str 💡 文件名,但 `containDir` 为 "true" 时,视为路径
4476
+ - filename: str 💡 文件名,但 ``containDir`` 为 "true" 时,视为路径
3997
4477
  - duplicate: 0 | 1 | 2 = 0 💡 处理同名:0: 跳过/报错 1: 保留/后缀编号 2: 替换/覆盖
3998
4478
  - etag: str 💡 文件 md5
3999
4479
  - parentFileID: int = 0 💡 父目录 id,根目录是 0
@@ -4005,10 +4485,10 @@ class P123OpenClient:
4005
4485
  .. code:: python
4006
4486
 
4007
4487
  {
4008
- "fileID": str, # 上传后的文件 id。当已有相同 `size``etag` 的文件时,会发生秒传
4009
- "preuploadID": str, # 预上传 id。当 `reuse` 为 "true" 时,该字段不存在
4488
+ "fileID": str, # 上传后的文件 id。当已有相同 ``size````etag`` 的文件时,会发生秒传
4489
+ "preuploadID": str, # 预上传 id。当 ``reuse`` 为 "true" 时,该字段不存在
4010
4490
  "reuse": bool, # 是否秒传,返回 "true" 时表示文件已上传成功
4011
- "sliceSize": int, # 分片大小,必须按此大小生成文件分片再上传。当 `reuse` 为 "true" 时,该字段不存在
4491
+ "sliceSize": int, # 分片大小,必须按此大小生成文件分片再上传。当 ``reuse`` 为 "true" 时,该字段不存在
4012
4492
  }
4013
4493
  """
4014
4494
  api = complete_url("/upload/v1/file/create", base_url)
@@ -4059,6 +4539,7 @@ class P123OpenClient:
4059
4539
  有多个分片时,轮流分别根据序号获取下载链接,然后 PUT 方法上传分片。由于上传链接会过期,所以没必要提前获取一大批
4060
4540
 
4061
4541
  .. admonition:: Reference
4542
+
4062
4543
  /API列表/文件管理/上传/V1/获取上传地址&上传分片
4063
4544
 
4064
4545
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/sonz9n085gnz0n3m
@@ -4109,6 +4590,7 @@ class P123OpenClient:
4109
4590
  此接口用于罗列已经上传的分片信息,以供比对
4110
4591
 
4111
4592
  .. admonition:: Reference
4593
+
4112
4594
  /API列表/文件管理/上传/V1/列举已上传分片(非必需)
4113
4595
 
4114
4596
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/dd28ws4bfn644cny
@@ -4157,6 +4639,7 @@ class P123OpenClient:
4157
4639
  POST https://open-api.123pan.com/upload/v1/file/upload_complete
4158
4640
 
4159
4641
  .. admonition:: Reference
4642
+
4160
4643
  /API列表/文件管理/上传/V1/上传完毕
4161
4644
 
4162
4645
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/hkdmcmvg437rfu6x
@@ -4216,6 +4699,7 @@ class P123OpenClient:
4216
4699
  POST https://open-api.123pan.com/upload/v1/file/upload_async_result
4217
4700
 
4218
4701
  .. admonition:: Reference
4702
+
4219
4703
  /API列表/文件管理/上传/V1/异步轮询获取上传结果
4220
4704
 
4221
4705
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/qgcosr6adkmm51h7
@@ -4297,9 +4781,10 @@ class P123OpenClient:
4297
4781
  """上传文件
4298
4782
 
4299
4783
  .. note::
4300
- 如果文件名中包含字符 "\\/:*?|><,则转换为对应的全角字符
4784
+ 如果文件名中包含字符 ``"\\/:*?|><``,则转换为对应的全角字符
4301
4785
 
4302
4786
  .. admonition:: Reference
4787
+
4303
4788
  /API列表/文件管理/上传/v1/💡上传流程说明
4304
4789
 
4305
4790
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/il16qi0opiel4889
@@ -4310,18 +4795,18 @@ class P123OpenClient:
4310
4795
 
4311
4796
  :param file: 待上传的文件
4312
4797
 
4313
- - 如果为 `collections.abc.Buffer`,则作为二进制数据上传
4314
- - 如果为 `filewrap.SupportsRead`,则作为可读的二进制文件上传
4315
- - 如果为 `str``os.PathLike`,则视为路径,打开后作为文件上传
4316
- - 如果为 `yarl.URL``http_request.SupportsGeturl` (`pip install python-http_request`),则视为超链接,打开后作为文件上传
4317
- - 如果为 `collections.abc.Iterable[collections.abc.Buffer]``collections.abc.AsyncIterable[collections.abc.Buffer]`,则迭代以获取二进制数据,逐步上传
4798
+ - 如果为 ``collections.abc.Buffer``,则作为二进制数据上传
4799
+ - 如果为 ``filewrap.SupportsRead``,则作为可读的二进制文件上传
4800
+ - 如果为 ``str````os.PathLike``,则视为路径,打开后作为文件上传
4801
+ - 如果为 ``yarl.URL````http_request.SupportsGeturl`` (``pip install python-http_request``),则视为超链接,打开后作为文件上传
4802
+ - 如果为 ``collections.abc.Iterable[collections.abc.Buffer]````collections.abc.AsyncIterable[collections.abc.Buffer]``,则迭代以获取二进制数据,逐步上传
4318
4803
 
4319
4804
  :param file_md5: 文件的 MD5 散列值
4320
4805
  :param file_name: 文件名
4321
4806
  :param file_size: 文件大小
4322
4807
  :param parent_id: 要上传的目标目录
4323
4808
  :param duplicate: 处理同名:0: 提示/忽略 1: 保留两者 2: 替换
4324
- :param preupload_id: 预上传 id,用于断点续传,提供此参数,则会忽略 `file_md5`、`file_name`、`file_size`、`parent_id``duplicate`
4809
+ :param preupload_id: 预上传 id,用于断点续传,提供此参数,则会忽略 ``file_md5``、``file_name``、``file_size``、``parent_id````duplicate``
4325
4810
  :param slice_size: 分块大小,断点续传时,如果只上传过少于 2 个分块时,会被使用
4326
4811
  :param async_: 是否异步
4327
4812
  :param request_kwargs: 其它请求参数
@@ -4582,6 +5067,7 @@ class P123OpenClient:
4582
5067
  GET https://open-api.123pan.com/api/v1/user/info
4583
5068
 
4584
5069
  .. admonition:: Reference
5070
+
4585
5071
  /API列表/用户管理/获取用户信息
4586
5072
 
4587
5073
  https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/fa2w0rosunui2v4m
@@ -4606,9 +5092,6 @@ class P123OpenClient:
4606
5092
  ########## API Aliases ##########
4607
5093
 
4608
5094
  login_open = login
4609
- login_access_token_open = login_access_token
4610
- login_auth_open = login_auth
4611
- login_refresh_token_open = login_refresh_token
4612
5095
  dlink_disable_open = dlink_disable
4613
5096
  dlink_enable_open = dlink_enable
4614
5097
  dlink_log_open = dlink_log
@@ -4629,6 +5112,10 @@ class P123OpenClient:
4629
5112
  fs_rename_open = fs_rename
4630
5113
  fs_rename_one_open = fs_rename_one
4631
5114
  fs_trash_open = fs_trash
5115
+ login_token_open = login_token
5116
+ login_oauth_authorize_open = login_oauth_authorize
5117
+ login_oauth_token_open = login_oauth_token
5118
+ login_oauth_verify_open = login_oauth_verify
4632
5119
  offline_download_open = offline_download
4633
5120
  offline_process_open = offline_process
4634
5121
  oss_copy_open = oss_copy
@@ -4674,22 +5161,62 @@ class P123OpenClient:
4674
5161
  class P123Client(P123OpenClient):
4675
5162
  """123 的客户端对象
4676
5163
 
5164
+ .. caution::
5165
+ 优先级为:token > passport+password > refresh_token > client_id+client_secret > 扫码
5166
+
5167
+ 使用 refresh_token(或者说 oauth 登录),只允许访问 open 接口
5168
+
4677
5169
  :param passport: 手机号或邮箱
4678
5170
  :param password: 密码
4679
5171
  :param token: 123 的访问令牌
5172
+ :param client_id: 应用标识,创建应用时分配的 appId
5173
+ :param client_secret: 应用密钥,创建应用时分配的 secretId
5174
+ :param refresh_token: 刷新令牌
4680
5175
  """
5176
+ passport: int | str = ""
5177
+ password: str = ""
5178
+
4681
5179
  def __init__(
4682
5180
  self,
4683
5181
  /,
4684
5182
  passport: int | str | PathLike = "",
4685
5183
  password: str = "",
4686
5184
  token: None | str | PathLike = None,
5185
+ client_id: str = "",
5186
+ client_secret: str = "",
5187
+ refresh_token: str = "",
4687
5188
  ):
4688
- if isinstance(passport, PathLike):
5189
+ if (isinstance(passport, PathLike) or
5190
+ not token and
5191
+ isinstance(passport, str) and
5192
+ len(passport) >= 128
5193
+ ):
4689
5194
  token = passport
5195
+ elif (not refresh_token and
5196
+ isinstance(passport, str) and
5197
+ len(passport) >= 48 and
5198
+ not passport.strip(digits+ascii_uppercase)
5199
+ ):
5200
+ refresh_token = passport
5201
+ elif (not client_id and
5202
+ isinstance(passport, str) and
5203
+ len(passport) >= 32 and
5204
+ not passport.strip(digits+"abcdef")
5205
+ ):
5206
+ client_id = passport
4690
5207
  else:
4691
5208
  self.passport = passport
5209
+ if (not client_secret and
5210
+ isinstance(password, str)
5211
+ and len(password) >= 32 and
5212
+ not password.strip(digits+"abcdef")
5213
+ ):
5214
+ client_secret = password
5215
+ else:
4692
5216
  self.password = password
5217
+ self.client_id = client_id
5218
+ self.client_secret = client_secret
5219
+ self.refresh_token = refresh_token
4693
5220
  if token is None:
4694
5221
  self.login()
4695
5222
  elif isinstance(token, str):
@@ -4702,6 +5229,8 @@ class P123Client(P123OpenClient):
4702
5229
  self._read_token()
4703
5230
  if not self.token:
4704
5231
  self.login()
5232
+ if not self.passport:
5233
+ self.passport = self.token_user_info["username"]
4705
5234
 
4706
5235
  @overload # type: ignore
4707
5236
  def login(
@@ -4709,7 +5238,11 @@ class P123Client(P123OpenClient):
4709
5238
  /,
4710
5239
  passport: int | str = "",
4711
5240
  password: str = "",
5241
+ client_id: str = "",
5242
+ client_secret: str = "",
5243
+ refresh_token: str = "",
4712
5244
  remember: bool = True,
5245
+ platform: int = 0,
4713
5246
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4714
5247
  *,
4715
5248
  async_: Literal[False] = False,
@@ -4722,7 +5255,11 @@ class P123Client(P123OpenClient):
4722
5255
  /,
4723
5256
  passport: int | str = "",
4724
5257
  password: str = "",
5258
+ client_id: str = "",
5259
+ client_secret: str = "",
5260
+ refresh_token: str = "",
4725
5261
  remember: bool = True,
5262
+ platform: int = 0,
4726
5263
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4727
5264
  *,
4728
5265
  async_: Literal[True],
@@ -4734,7 +5271,11 @@ class P123Client(P123OpenClient):
4734
5271
  /,
4735
5272
  passport: int | str = "",
4736
5273
  password: str = "",
5274
+ client_id: str = "",
5275
+ client_secret: str = "",
5276
+ refresh_token: str = "",
4737
5277
  remember: bool = True,
5278
+ platform: int = 0,
4738
5279
  base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4739
5280
  *,
4740
5281
  async_: Literal[False, True] = False,
@@ -4745,6 +5286,7 @@ class P123Client(P123OpenClient):
4745
5286
  :param passport: 账号
4746
5287
  :param password: 密码
4747
5288
  :param remember: 是否记住密码(不用管)
5289
+ :param platform: 用哪个设备平台扫码
4748
5290
  :param base_url: 接口的基地址
4749
5291
  :param async_: 是否异步
4750
5292
  :param request_kwargs: 其它请求参数
@@ -4759,6 +5301,18 @@ class P123Client(P123OpenClient):
4759
5301
  self.password = password
4760
5302
  else:
4761
5303
  password = self.password
5304
+ if client_id:
5305
+ self.client_id = client_id
5306
+ else:
5307
+ client_id = self.client_id
5308
+ if client_secret:
5309
+ self.client_secret = client_secret
5310
+ else:
5311
+ client_secret = self.client_secret
5312
+ if refresh_token:
5313
+ self.refresh_token = refresh_token
5314
+ else:
5315
+ refresh_token = self.refresh_token
4762
5316
  def gen_step():
4763
5317
  if passport and password:
4764
5318
  resp = yield self.login_passport(
@@ -4770,8 +5324,17 @@ class P123Client(P123OpenClient):
4770
5324
  check_response(resp)
4771
5325
  self.token = resp["data"]["token"]
4772
5326
  return resp
5327
+ elif client_id and client_secret or refresh_token:
5328
+ return self.login_open(
5329
+ client_id,
5330
+ client_secret,
5331
+ refresh_token,
5332
+ async_=async_,
5333
+ **request_kwargs,
5334
+ )
4773
5335
  else:
4774
5336
  resp = yield self.login_with_qrcode(
5337
+ platform=platform,
4775
5338
  base_url=base_url,
4776
5339
  async_=async_,
4777
5340
  **request_kwargs,
@@ -4785,6 +5348,7 @@ class P123Client(P123OpenClient):
4785
5348
  self,
4786
5349
  /,
4787
5350
  replace: bool | Self = False,
5351
+ platform: int = 0,
4788
5352
  base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4789
5353
  *,
4790
5354
  async_: Literal[False] = False,
@@ -4796,6 +5360,7 @@ class P123Client(P123OpenClient):
4796
5360
  self,
4797
5361
  /,
4798
5362
  replace: bool | Self = False,
5363
+ platform: int = 0,
4799
5364
  base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4800
5365
  *,
4801
5366
  async_: Literal[True],
@@ -4806,6 +5371,7 @@ class P123Client(P123OpenClient):
4806
5371
  self,
4807
5372
  /,
4808
5373
  replace: bool | Self = False,
5374
+ platform: int = 0,
4809
5375
  base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4810
5376
  *,
4811
5377
  async_: Literal[False, True] = False,
@@ -4816,9 +5382,10 @@ class P123Client(P123OpenClient):
4816
5382
  :param replace: 替换某个 client 对象的 token
4817
5383
 
4818
5384
  - 如果为 P123Client, 则更新到此对象
4819
- - 如果为 True,则更新到 `self`
4820
- - 如果为 False,否则返回新的 `P123Client` 对象
5385
+ - 如果为 True,则更新到 `self``
5386
+ - 如果为 False,否则返回新的 ``P123Client`` 对象
4821
5387
 
5388
+ :param platform: 用哪个设备平台扫码
4822
5389
  :param base_url: 接口的基地址
4823
5390
  :param async_: 是否异步
4824
5391
  :param request_kwargs: 其它请求参数
@@ -4827,13 +5394,13 @@ class P123Client(P123OpenClient):
4827
5394
  """
4828
5395
  def gen_step():
4829
5396
  resp = yield self.login_qrcode_auto(
5397
+ platform=platform,
4830
5398
  base_url=base_url,
4831
5399
  async_=async_,
4832
5400
  **request_kwargs,
4833
5401
  )
4834
- check_response(resp)
4835
5402
  if resp["code"] != 200:
4836
- raise P123LoginError(EAUTH, resp)
5403
+ raise P123LoginError(errno.EAUTH, resp)
4837
5404
  token = resp["data"]["token"]
4838
5405
  if replace is False:
4839
5406
  return type(self)(passport=self.passport, password=self.password, token=token)
@@ -4849,6 +5416,7 @@ class P123Client(P123OpenClient):
4849
5416
  def login_qrcode_auto(
4850
5417
  self,
4851
5418
  /,
5419
+ platform: int = 0,
4852
5420
  base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4853
5421
  *,
4854
5422
  async_: Literal[False] = False,
@@ -4859,6 +5427,7 @@ class P123Client(P123OpenClient):
4859
5427
  def login_qrcode_auto(
4860
5428
  self,
4861
5429
  /,
5430
+ platform: int = 0,
4862
5431
  base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4863
5432
  *,
4864
5433
  async_: Literal[True],
@@ -4868,13 +5437,18 @@ class P123Client(P123OpenClient):
4868
5437
  def login_qrcode_auto(
4869
5438
  self,
4870
5439
  /,
5440
+ platform: int = 0,
4871
5441
  base_url: str | Callable[[], str] = DEFAULT_LOGIN_BASE_URL,
4872
5442
  *,
4873
5443
  async_: Literal[False, True] = False,
4874
5444
  **request_kwargs,
4875
5445
  ) -> dict | Coroutine[Any, Any, dict]:
4876
- """执行一次自动扫码,但并不因此更新 `self.token`
5446
+ """执行一次自动扫码,但并不因此更新 ``self.token``
4877
5447
 
5448
+ .. caution::
5449
+ 非会员目前只支持同时在线 3 台登录设备
5450
+
5451
+ :param platform: 用哪个设备平台扫码
4878
5452
  :param base_url: 接口的基地址
4879
5453
  :param async_: 是否异步
4880
5454
  :param request_kwargs: 其它请求参数
@@ -4889,6 +5463,14 @@ class P123Client(P123OpenClient):
4889
5463
  )
4890
5464
  check_response(resp)
4891
5465
  uniID = resp["data"]["uniID"]
5466
+ if platform:
5467
+ resp = yield self.login_qrcode_scan(
5468
+ {"uniID": uniID, "scanPlatform": platform},
5469
+ base_url=base_url,
5470
+ async_=async_,
5471
+ **request_kwargs,
5472
+ )
5473
+ check_response(resp)
4892
5474
  resp = yield self.login_qrcode_confirm(
4893
5475
  uniID,
4894
5476
  base_url=base_url,
@@ -4902,7 +5484,9 @@ class P123Client(P123OpenClient):
4902
5484
  async_=async_,
4903
5485
  **request_kwargs,
4904
5486
  )
4905
- return resp
5487
+ check_response(resp)
5488
+ if resp["code"] == 200 or resp["data"]["loginStatus"] not in (0, 1, 3):
5489
+ return resp
4906
5490
  return run_gen_step(gen_step, async_)
4907
5491
 
4908
5492
  @overload
@@ -4974,14 +5558,14 @@ class P123Client(P123OpenClient):
4974
5558
  print("\r\033[1K[loginStatus=1] qrcode: scanned", end="")
4975
5559
  case 2:
4976
5560
  print("\r\033[1K[loginStatus=2] qrcode: cancelled", end="")
4977
- raise P123LoginError(EAUTH, f"qrcode: cancelled with {resp!r}")
5561
+ raise P123LoginError(errno.EAUTH, f"qrcode: cancelled with {resp!r}")
4978
5562
  case 3:
4979
5563
  print("\r\033[1K[loginStatus=3] qrcode: login", end="")
4980
5564
  case 4:
4981
5565
  print("\r\033[1K[loginStatus=4] qrcode: expired", end="")
4982
- raise P123LoginError(EAUTH, f"qrcode: expired with {resp!r}")
5566
+ raise P123LoginError(errno.EAUTH, f"qrcode: expired with {resp!r}")
4983
5567
  case _:
4984
- raise P123LoginError(EAUTH, f"qrcode: aborted with {resp!r}")
5568
+ raise P123LoginError(errno.EAUTH, f"qrcode: aborted with {resp!r}")
4985
5569
  return run_gen_step(gen_step, async_)
4986
5570
 
4987
5571
  ########## App API ##########
@@ -5395,7 +5979,7 @@ class P123Client(P123OpenClient):
5395
5979
  "Etag": "...", # 必填,文件的 MD5
5396
5980
  "FileID": 0, # 可以随便填
5397
5981
  "FileName": "a", # 随便填一个名字
5398
- "S3KeyFlag": str # 必填,格式为 f"{UID}-0",UID 就是上传此文件的用户的 UID,如果此文件是由你上传的,则可从 `P123Client.user_info` 的响应中获取
5982
+ "S3KeyFlag": str # 必填,格式为 f"{UID}-0",UID 就是上传此文件的用户的 UID,如果此文件是由你上传的,则可从 ``P123Client.user_info`` 的响应中获取
5399
5983
  "Size": 0, # 可以随便填,填了可能搜索更准确
5400
5984
  }
5401
5985
 
@@ -5425,10 +6009,10 @@ class P123Client(P123OpenClient):
5425
6009
  resp["payload"] = payload
5426
6010
  check_response(resp)
5427
6011
  if not (info_list := resp["data"]["infoList"]):
5428
- raise FileNotFoundError(ENOENT, resp)
6012
+ raise FileNotFoundError(errno.ENOENT, resp)
5429
6013
  payload = cast(dict, info_list[0])
5430
6014
  if payload["Type"]:
5431
- raise IsADirectoryError(EISDIR, resp)
6015
+ raise IsADirectoryError(errno.EISDIR, resp)
5432
6016
  payload = dict_to_lower_merge(
5433
6017
  payload, {"driveId": 0, "Type": 0, "FileID": 0})
5434
6018
  if "filename" not in payload:
@@ -5479,7 +6063,7 @@ class P123Client(P123OpenClient):
5479
6063
  POST https://www.123pan.com/api/file/batch_download_info
5480
6064
 
5481
6065
  .. warning::
5482
- 会把一些文件或目录以 zip 包的形式下载,但非会员有流量限制,所以还是推荐用 `P123Client.download_info` 逐个获取下载链接并下载
6066
+ 会把一些文件或目录以 zip 包的形式下载,但非会员有流量限制,所以还是推荐用 ``P123Client.download_info`` 逐个获取下载链接并下载
5483
6067
 
5484
6068
  :payload:
5485
6069
  - fileIdList: list[FileID]
@@ -5534,15 +6118,15 @@ class P123Client(P123OpenClient):
5534
6118
  """获取下载链接
5535
6119
 
5536
6120
  .. note::
5537
- `payload` 支持多种格式的输入,按下面的规则按顺序进行判断:
6121
+ ``payload`` 支持多种格式的输入,按下面的规则按顺序进行判断:
5538
6122
 
5539
- 1. 如果是 `int``str`,则视为文件 id,必须在你的网盘中存在此文件
5540
- 2. 如果是 `dict`(不区分大小写),有 "S3KeyFlag", "Etag" 和 "Size" 的值,则直接获取链接,文件不必在你网盘中
5541
- 3. 如果是 `dict`(不区分大小写),有 "Etag" 和 "Size" 的值,则会先秒传(临时文件路径为 /.tempfile)再获取链接,文件不必在你网盘中
5542
- 4. 如果是 `dict`(不区分大小写),有 "FileID",则会先获取信息,再获取链接,必须在你的网盘中存在此文件
6123
+ 1. 如果是 ``int````str``,则视为文件 id,必须在你的网盘中存在此文件
6124
+ 2. 如果是 ``dict`` (不区分大小写),有 "S3KeyFlag", "Etag" 和 "Size" 的值,则直接获取链接,文件不必在你网盘中
6125
+ 3. 如果是 ``dict`` (不区分大小写),有 "Etag" 和 "Size" 的值,则会先秒传(临时文件路径为 /.tempfile)再获取链接,文件不必在你网盘中
6126
+ 4. 如果是 ``dict`` (不区分大小写),有 "FileID",则会先获取信息,再获取链接,必须在你的网盘中存在此文件
5543
6127
  5. 否则会报错 ValueError
5544
6128
 
5545
- :params payload: 文件 id 或者文件信息,文件信息必须包含的信息如下:
6129
+ :param payload: 文件 id 或者文件信息,文件信息必须包含的信息如下:
5546
6130
 
5547
6131
  - FileID: int | str 💡 下载链接
5548
6132
  - S3KeyFlag: str 💡 s3 存储名
@@ -5550,8 +6134,8 @@ class P123Client(P123OpenClient):
5550
6134
  - Size: int 💡 文件大小
5551
6135
  - FileName: str 💡 默认用 Etag(即 MD5)作为文件名,可以省略
5552
6136
 
5553
- :params async_: 是否异步
5554
- :params request_kwargs: 其它请求参数
6137
+ :param async_: 是否异步
6138
+ :param request_kwargs: 其它请求参数
5555
6139
 
5556
6140
  :return: 下载链接
5557
6141
  """
@@ -5564,10 +6148,10 @@ class P123Client(P123OpenClient):
5564
6148
  resp = yield self.fs_info(fileid, async_=async_, **request_kwargs)
5565
6149
  check_response(resp)
5566
6150
  if not (info_list := resp["data"]["infoList"]):
5567
- raise P123OSError(ENOENT, resp)
6151
+ raise P123OSError(errno.ENOENT, resp)
5568
6152
  info = info_list[0]
5569
6153
  if info["Type"]:
5570
- raise IsADirectoryError(EISDIR, resp)
6154
+ raise IsADirectoryError(errno.EISDIR, resp)
5571
6155
  payload = dict_to_lower_merge(payload, info)
5572
6156
  else:
5573
6157
  raise ValueError("`Size` and `Etag` must be provided")
@@ -5585,7 +6169,7 @@ class P123Client(P123OpenClient):
5585
6169
  )
5586
6170
  check_response(resp)
5587
6171
  if not resp["data"]["Reuse"]:
5588
- raise P123OSError(ENOENT, resp)
6172
+ raise P123OSError(errno.ENOENT, resp)
5589
6173
  payload["s3keyflag"] = resp["data"]["Info"]["S3KeyFlag"]
5590
6174
  resp = yield self.download_info(
5591
6175
  payload,
@@ -5684,7 +6268,7 @@ class P123Client(P123OpenClient):
5684
6268
  POST https://www.123pan.com/api/restful/goapi/v1/file/copy/async
5685
6269
 
5686
6270
  :payload:
5687
- - fileList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
6271
+ - fileList: list[File] 💡 信息可以取自 ``P123Client.fs_info`` 接口
5688
6272
 
5689
6273
  .. code:: python
5690
6274
 
@@ -5708,7 +6292,7 @@ class P123Client(P123OpenClient):
5708
6292
  check_response(resp)
5709
6293
  info_list = resp["data"]["infoList"]
5710
6294
  if not info_list:
5711
- raise FileNotFoundError(ENOENT, resp)
6295
+ raise FileNotFoundError(errno.ENOENT, resp)
5712
6296
  payload = {"fileList": info_list}
5713
6297
  payload = dict_to_lower_merge(payload, targetFileId=parent_id)
5714
6298
  return self.request(
@@ -6006,7 +6590,7 @@ class P123Client(P123OpenClient):
6006
6590
  - "syncFileList": 同步空间
6007
6591
 
6008
6592
  - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6009
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6593
+ - SearchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
6010
6594
  - OnlyLookAbnormalFile: int = <default>
6011
6595
  """
6012
6596
  if isinstance(payload, (int, str)):
@@ -6117,9 +6701,9 @@ class P123Client(P123OpenClient):
6117
6701
  - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6118
6702
 
6119
6703
  .. note::
6120
- 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6704
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 ``SearchData``)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6121
6705
 
6122
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6706
+ - SearchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
6123
6707
  - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
6124
6708
  """
6125
6709
  if not isinstance(payload, dict):
@@ -6222,9 +6806,9 @@ class P123Client(P123OpenClient):
6222
6806
  - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6223
6807
 
6224
6808
  .. note::
6225
- 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6809
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 ``SearchData``)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6226
6810
 
6227
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
6811
+ - SearchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
6228
6812
  - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
6229
6813
  - RequestSource: int = <default> 💡 浏览器中,在同步空间中为 1
6230
6814
  """
@@ -6605,9 +7189,9 @@ class P123Client(P123OpenClient):
6605
7189
  - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
6606
7190
 
6607
7191
  .. note::
6608
- 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
7192
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 ``SearchData``)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
6609
7193
 
6610
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
7194
+ - SearchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
6611
7195
  - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
6612
7196
  """
6613
7197
  if not isinstance(payload, dict):
@@ -6727,7 +7311,7 @@ class P123Client(P123OpenClient):
6727
7311
  POST https://www.123pan.com/api/file/trash
6728
7312
 
6729
7313
  :payload:
6730
- - fileTrashInfoList: list[File] 💡 信息可以取自 `P123Client.fs_info` 接口
7314
+ - fileTrashInfoList: list[File] 💡 信息可以取自 ``P123Client.fs_info`` 接口
6731
7315
 
6732
7316
  .. code:: python
6733
7317
 
@@ -7261,11 +7845,11 @@ class P123Client(P123OpenClient):
7261
7845
 
7262
7846
  :payload:
7263
7847
  - uniID: str 💡 二维码 id
7264
- - scanPlatform: int = 4 💡 扫码的平台代码,微信是 4
7848
+ - scanPlatform: int = 0 💡 扫码的平台代码,部分已知:4:微信 7:android
7265
7849
  """
7266
7850
  if not isinstance(payload, dict):
7267
7851
  payload = {"uniID": payload}
7268
- payload.setdefault("scanPlatform", 4)
7852
+ payload.setdefault("scanPlatform", 0)
7269
7853
  request_kwargs.setdefault("parse", default_parse)
7270
7854
  if request is None:
7271
7855
  request = get_default_request()
@@ -7462,7 +8046,7 @@ class P123Client(P123OpenClient):
7462
8046
  POST https://www.123pan.com/api/offline_download/task/resolve
7463
8047
 
7464
8048
  :payload:
7465
- - urls: str = <default> 💡 下载链接,多个用 "\n" 隔开(用于新建链接下载任务)
8049
+ - urls: str = <default> 💡 下载链接,多个用 "\\n" 隔开(用于新建链接下载任务)
7466
8050
  - info_hash: str = <default> 💡 种子文件的 info_hash(用于新建BT任务)
7467
8051
  """
7468
8052
  if isinstance(payload, str):
@@ -7621,7 +8205,7 @@ class P123Client(P123OpenClient):
7621
8205
  POST https://www.123pan.com/api/share/delete
7622
8206
 
7623
8207
  :payload:
7624
- - shareInfoList: list[ShareID] 💡 信息可以取自 `P123Client.fs_info` 接口
8208
+ - shareInfoList: list[ShareID] 💡 信息可以取自 ``P123Client.fs_info`` 接口
7625
8209
 
7626
8210
  .. code:: python
7627
8211
 
@@ -7821,7 +8405,7 @@ class P123Client(P123OpenClient):
7821
8405
 
7822
8406
  如果文件在 100MB 以内,下载时是不需要登录的;如果超过 100 MB,但分享者设置的免登录流量包未告罄,下载时也不需要登录
7823
8407
 
7824
- 你也可以使用 `P123Client.download_info` 来获取下载链接,则不需要提供 "ShareKey" 和 "SharePwd"
8408
+ 你也可以使用 ``P123Client.download_info`` 来获取下载链接,则不需要提供 "ShareKey" 和 "SharePwd"
7825
8409
 
7826
8410
  :payload:
7827
8411
  - ShareKey: str 💡 分享码
@@ -8164,7 +8748,7 @@ class P123Client(P123OpenClient):
8164
8748
  - Page: int = <default> 💡 第几页,从 1 开始,可以是 0
8165
8749
  - event: str = "shareListFile"
8166
8750
  - operateType: int | str = <default>
8167
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
8751
+ - SearchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
8168
8752
  """
8169
8753
  if isinstance(payload, int):
8170
8754
  payload = {"Page": payload}
@@ -8234,7 +8818,7 @@ class P123Client(P123OpenClient):
8234
8818
  - Page: int = <default> 💡 第几页,从 1 开始,可以是 0
8235
8819
  - event: str = "shareListFile"
8236
8820
  - operateType: int | str = <default>
8237
- - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
8821
+ - SearchData: str = <default> 💡 搜索关键字(将无视 ``parentFileId`` 参数)
8238
8822
  """
8239
8823
  if isinstance(payload, int):
8240
8824
  payload = {"Page": payload}
@@ -8627,7 +9211,7 @@ class P123Client(P123OpenClient):
8627
9211
  - parentFileId: int | str = 0 💡 父目录 id
8628
9212
  - size: int = 0 💡 文件大小,单位:字节
8629
9213
  - type: 0 | 1 = 1 💡 类型,如果是目录则是 1,如果是文件则是 0
8630
- - NotReuse: bool = False 💡 不要重用(仅在 `type=1` 时有效,如果为 False,当有重名时,立即返回,此时 `duplicate` 字段无效)
9214
+ - NotReuse: bool = False 💡 不要重用(仅在 `type=1` 时有效,如果为 False,当有重名时,立即返回,此时 ``duplicate`` 字段无效)
8631
9215
  - ...
8632
9216
  """
8633
9217
  if isinstance(payload, str):
@@ -8706,15 +9290,15 @@ class P123Client(P123OpenClient):
8706
9290
  """上传文件
8707
9291
 
8708
9292
  .. note::
8709
- 如果文件名中包含字符 "\\/:*?|><,则转换为对应的全角字符
9293
+ 如果文件名中包含字符 ``"\\/:*?|><``,则转换为对应的全角字符
8710
9294
 
8711
9295
  :param file: 待上传的文件
8712
9296
 
8713
- - 如果为 `collections.abc.Buffer`,则作为二进制数据上传
8714
- - 如果为 `filewrap.SupportsRead`,则作为可读的二进制文件上传
8715
- - 如果为 `str``os.PathLike`,则视为路径,打开后作为文件上传
8716
- - 如果为 `yarl.URL``http_request.SupportsGeturl` (`pip install python-http_request`),则视为超链接,打开后作为文件上传
8717
- - 如果为 `collections.abc.Iterable[collections.abc.Buffer]``collections.abc.AsyncIterable[collections.abc.Buffer]`,则迭代以获取二进制数据,逐步上传
9297
+ - 如果为 ``collections.abc.Buffer``,则作为二进制数据上传
9298
+ - 如果为 ``filewrap.SupportsRead``,则作为可读的二进制文件上传
9299
+ - 如果为 ``str````os.PathLike``,则视为路径,打开后作为文件上传
9300
+ - 如果为 ``yarl.URL````http_request.SupportsGeturl`` (``pip install python-http_request``),则视为超链接,打开后作为文件上传
9301
+ - 如果为 ``collections.abc.Iterable[collections.abc.Buffer]````collections.abc.AsyncIterable[collections.abc.Buffer]``,则迭代以获取二进制数据,逐步上传
8718
9302
 
8719
9303
  :param file_md5: 文件的 MD5 散列值
8720
9304
  :param file_name: 文件名
@@ -8978,11 +9562,11 @@ class P123Client(P123OpenClient):
8978
9562
 
8979
9563
  :param file: 待上传的文件
8980
9564
 
8981
- - 如果为 `collections.abc.Buffer`,则作为二进制数据上传
8982
- - 如果为 `filewrap.SupportsRead`,则作为可读的二进制文件上传
8983
- - 如果为 `str``os.PathLike`,则视为路径,打开后作为文件上传
8984
- - 如果为 `yarl.URL``http_request.SupportsGeturl` (`pip install python-http_request`),则视为超链接,打开后作为文件上传
8985
- - 如果为 `collections.abc.Iterable[collections.abc.Buffer]``collections.abc.AsyncIterable[collections.abc.Buffer]`,则迭代以获取二进制数据,逐步上传
9565
+ - 如果为 ``collections.abc.Buffer``,则作为二进制数据上传
9566
+ - 如果为 ``filewrap.SupportsRead``,则作为可读的二进制文件上传
9567
+ - 如果为 ``str````os.PathLike``,则视为路径,打开后作为文件上传
9568
+ - 如果为 ``yarl.URL````http_request.SupportsGeturl`` (``pip install python-http_request``),则视为超链接,打开后作为文件上传
9569
+ - 如果为 ``collections.abc.Iterable[collections.abc.Buffer]````collections.abc.AsyncIterable[collections.abc.Buffer]``,则迭代以获取二进制数据,逐步上传
8986
9570
 
8987
9571
  :param file_md5: 文件的 MD5 散列值
8988
9572
  :param file_name: 文件名
@@ -9320,6 +9904,8 @@ with temp_globals():
9320
9904
  CLIENT_API_METHODS_MAP[api] = [name]
9321
9905
 
9322
9906
 
9907
+ # TODO: 只有在使用密码登录的情况下,即使 token 失效,也可以重新登录(在有账号和密码的情况下,遇到 401 报错,会自动重新登录(需要加锁))
9908
+ # TODO: 新的上传方法需要封装:https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xogi45g7okqk7svr
9323
9909
  # TODO: 对于某些工具的接口封装,例如 重复文件清理
9324
9910
  # TODO: 同步空间
9325
9911
  # TODO: 直链空间