p123client 0.0.6.4__py3-none-any.whl → 0.0.6.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
p123client/client.py CHANGED
@@ -40,6 +40,9 @@ from .exception import P123OSError, P123BrokenUpload
40
40
 
41
41
 
42
42
  # 默认使用的域名
43
+ # "https://www.123pan.com"
44
+ # "https://www.123pan.com/a"
45
+ # "https://www.123pan.com/b"
43
46
  DEFAULT_BASE_URL = "https://www.123pan.com/b"
44
47
  DEFAULT_LOGIN_BASE_URL = "https://login.123pan.com"
45
48
  DEFAULT_OPEN_BASE_URL = "https://open-api.123pan.com"
@@ -371,7 +374,7 @@ class P123OpenClient:
371
374
  check_response(resp)
372
375
  self.token = resp["data"]["accessToken"]
373
376
  return resp
374
- return run_gen_step(gen_step, async_=async_)
377
+ return run_gen_step(gen_step, async_)
375
378
 
376
379
  @overload
377
380
  @staticmethod
@@ -1212,6 +1215,8 @@ class P123OpenClient:
1212
1215
  })
1213
1216
  return self.request(api, params=payload, async_=async_, **request_kwargs)
1214
1217
 
1218
+ fs_list_v2 = fs_list
1219
+
1215
1220
  @overload
1216
1221
  def fs_list_v1(
1217
1222
  self,
@@ -1258,7 +1263,7 @@ class P123OpenClient:
1258
1263
  :payload:
1259
1264
  - limit: int = 100 💡 分页大小,最多 100
1260
1265
  - orderBy: str = "file_id" 💡 排序依据
1261
-
1266
+
1262
1267
  - "file_id": 文件 id
1263
1268
  - "file_name": 文件名
1264
1269
  - "size": 文件大小
@@ -2857,7 +2862,7 @@ class P123OpenClient:
2857
2862
  "duplicate": duplicate,
2858
2863
  "slice_size": slice_size,
2859
2864
  }) from e
2860
- return run_gen_step(gen_step, async_=async_)
2865
+ return run_gen_step(gen_step, async_)
2861
2866
 
2862
2867
  @overload
2863
2868
  def share_create(
@@ -4213,7 +4218,7 @@ class P123OpenClient:
4213
4218
  "duplicate": duplicate,
4214
4219
  "slice_size": slice_size,
4215
4220
  }) from e
4216
- return run_gen_step(gen_step, async_=async_)
4221
+ return run_gen_step(gen_step, async_)
4217
4222
 
4218
4223
  @overload
4219
4224
  def user_info(
@@ -4285,6 +4290,7 @@ class P123OpenClient:
4285
4290
  fs_detail_open = fs_detail
4286
4291
  fs_info_open = fs_info
4287
4292
  fs_list_open = fs_list
4293
+ fs_list_v2_open = fs_list_v2
4288
4294
  fs_list_v1_open = fs_list_v1
4289
4295
  fs_mkdir_open = fs_mkdir
4290
4296
  fs_move_open = fs_move
@@ -4415,7 +4421,7 @@ class P123Client(P123OpenClient):
4415
4421
  check_response(resp)
4416
4422
  self.token = resp["data"]["token"]
4417
4423
  return resp
4418
- return run_gen_step(gen_step, async_=async_)
4424
+ return run_gen_step(gen_step, async_)
4419
4425
 
4420
4426
  @overload
4421
4427
  @staticmethod
@@ -4456,6 +4462,45 @@ class P123Client(P123OpenClient):
4456
4462
  request_kwargs["async_"] = async_
4457
4463
  return request(**request_kwargs)
4458
4464
 
4465
+ @overload
4466
+ @staticmethod
4467
+ def app_server_time(
4468
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4469
+ request: None | Callable = None,
4470
+ *,
4471
+ async_: Literal[False] = False,
4472
+ **request_kwargs,
4473
+ ) -> dict:
4474
+ ...
4475
+ @overload
4476
+ @staticmethod
4477
+ def app_server_time(
4478
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4479
+ request: None | Callable = None,
4480
+ *,
4481
+ async_: Literal[True],
4482
+ **request_kwargs,
4483
+ ) -> Coroutine[Any, Any, dict]:
4484
+ ...
4485
+ @staticmethod
4486
+ def app_server_time(
4487
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
4488
+ request: None | Callable = None,
4489
+ *,
4490
+ async_: Literal[False, True] = False,
4491
+ **request_kwargs,
4492
+ ) -> dict | Coroutine[Any, Any, dict]:
4493
+ """获取 123 网盘的服务器时间戳
4494
+
4495
+ GET https://www.123pan.com/api/get/server/time
4496
+ """
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)
4503
+
4459
4504
  @overload
4460
4505
  def download_info(
4461
4506
  self,
@@ -4531,8 +4576,7 @@ class P123Client(P123OpenClient):
4531
4576
  )
4532
4577
  resp["payload"] = payload
4533
4578
  check_response(resp)
4534
- info_list = resp["data"]["infoList"]
4535
- if not info_list:
4579
+ if not (info_list := resp["data"]["infoList"]):
4536
4580
  raise FileNotFoundError(ENOENT, resp)
4537
4581
  payload = cast(dict, info_list[0])
4538
4582
  if payload["Type"]:
@@ -4549,7 +4593,7 @@ class P123Client(P123OpenClient):
4549
4593
  async_=async_,
4550
4594
  **request_kwargs,
4551
4595
  )
4552
- return run_gen_step(gen_step, async_=async_)
4596
+ return run_gen_step(gen_step, async_)
4553
4597
 
4554
4598
  @overload
4555
4599
  def download_info_batch(
@@ -4641,12 +4685,22 @@ class P123Client(P123OpenClient):
4641
4685
  ) -> str | Coroutine[Any, Any, str]:
4642
4686
  """获取下载链接
4643
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
+
4644
4697
  :params payload: 文件 id 或者文件信息,文件信息必须包含的信息如下:
4645
4698
 
4646
- - S3KeyFlag: str 💡 存储桶名
4647
- - Etag: str 💡 文件的 MD5 散列值
4648
- - Size: int 💡 文件大小
4649
- - FileName: str = <default> 💡 默认用 Etag(即 MD5)作为文件名
4699
+ - FileID: int | str 💡 下载链接
4700
+ - S3KeyFlag: str 💡 s3 存储名
4701
+ - Etag: str 💡 文件的 MD5 散列值
4702
+ - Size: int 💡 文件大小
4703
+ - FileName: str 💡 默认用 Etag(即 MD5)作为文件名,可以省略
4650
4704
 
4651
4705
  :params async_: 是否异步
4652
4706
  :params request_kwargs: 其它请求参数
@@ -4654,7 +4708,37 @@ class P123Client(P123OpenClient):
4654
4708
  :return: 下载链接
4655
4709
  """
4656
4710
  def gen_step():
4711
+ nonlocal payload
4657
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)
4718
+ if not (info_list := resp["data"]["infoList"]):
4719
+ raise P123OSError(ENOENT, resp)
4720
+ info = info_list[0]
4721
+ if info["Type"]:
4722
+ raise IsADirectoryError(EISDIR, resp)
4723
+ payload = dict_to_lower_merge(payload, info)
4724
+ else:
4725
+ raise ValueError("`Size` and `Etag` must be provided")
4726
+ if "s3keyflag" not in payload:
4727
+ resp = yield self.upload_request(
4728
+ {
4729
+ "filename": ".tempfile",
4730
+ "duplicate": 2,
4731
+ "etag": payload["etag"],
4732
+ "size": payload["size"],
4733
+ "type": 0,
4734
+ },
4735
+ async_=async_,
4736
+ **request_kwargs,
4737
+ )
4738
+ check_response(resp)
4739
+ if not resp["data"]["Reuse"]:
4740
+ raise P123OSError(ENOENT, resp)
4741
+ payload["s3keyflag"] = resp["data"]["Info"]["S3KeyFlag"]
4658
4742
  resp = yield self.download_info(
4659
4743
  payload,
4660
4744
  async_=async_,
@@ -4670,7 +4754,7 @@ class P123Client(P123OpenClient):
4670
4754
  )
4671
4755
  check_response(resp)
4672
4756
  return resp["data"]["downloadUrl"]
4673
- return run_gen_step(gen_step, async_=async_)
4757
+ return run_gen_step(gen_step, async_)
4674
4758
 
4675
4759
  @overload
4676
4760
  def fs_copy(
@@ -4746,7 +4830,7 @@ class P123Client(P123OpenClient):
4746
4830
  async_=async_,
4747
4831
  **request_kwargs,
4748
4832
  )
4749
- return run_gen_step(gen_step, async_=async_)
4833
+ return run_gen_step(gen_step, async_)
4750
4834
 
4751
4835
  @overload
4752
4836
  def fs_detail(
@@ -5013,7 +5097,7 @@ class P123Client(P123OpenClient):
5013
5097
  - next: int = 0 💡 下一批拉取开始的 id
5014
5098
  - orderBy: str = "file_id" 💡 排序依据
5015
5099
 
5016
- - "file_id": 文件 id
5100
+ - "file_id": 文件 id,也可以写作 "fileId"
5017
5101
  - "file_name": 文件名
5018
5102
  - "size": 文件大小
5019
5103
  - "create_at": 创建时间
@@ -5109,7 +5193,7 @@ class P123Client(P123OpenClient):
5109
5193
  - next: int = 0 💡 下一批拉取开始的 id
5110
5194
  - orderBy: str = "file_id" 💡 排序依据
5111
5195
 
5112
- - "file_id": 文件 id
5196
+ - "file_id": 文件 id,也可以写作 "fileId"
5113
5197
  - "file_name": 文件名
5114
5198
  - "size": 文件大小
5115
5199
  - "create_at": 创建时间
@@ -5118,7 +5202,7 @@ class P123Client(P123OpenClient):
5118
5202
  - ...
5119
5203
 
5120
5204
  - orderDirection: "asc" | "desc" = "asc" 💡 排序顺序
5121
- - Page: int = <default> 💡 第几页,从 1 开始,可以是 0
5205
+ - Page: int = 1 💡 第几页,从 1 开始
5122
5206
  - parentFileId: int | str = 0 💡 父目录 id
5123
5207
  - trashed: "false" | "true" = <default> 💡 是否查看回收站的文件
5124
5208
  - inDirectSpace: "false" | "true" = "false"
@@ -5129,8 +5213,13 @@ class P123Client(P123OpenClient):
5129
5213
  - "syncFileList": 同步空间
5130
5214
 
5131
5215
  - operateType: int | str = <default> 💡 操作类型,如果在同步空间,则需要指定为 "SyncSpacePage"
5216
+
5217
+ .. note::
5218
+ 这个值似乎不影响结果,所以可以忽略。我在浏览器中,看到罗列根目录为 1,搜索(指定 `SearchData`)为 2,同步空间的根目录为 3,罗列其它目录大多为 4,偶尔为 8,也可能是其它值
5219
+
5132
5220
  - SearchData: str = <default> 💡 搜索关键字(将无视 `parentFileId` 参数)
5133
- - OnlyLookAbnormalFile: int = <default>
5221
+ - OnlyLookAbnormalFile: int = 0 💡 大概可传入 0 或 1
5222
+ - RequestSource: int = <default> 💡 浏览器中,在同步空间中为 1
5134
5223
  """
5135
5224
  if isinstance(payload, (int, str)):
5136
5225
  payload = {"parentFileId": payload}
@@ -5143,6 +5232,8 @@ class P123Client(P123OpenClient):
5143
5232
  "parentFileId": 0,
5144
5233
  "inDirectSpace": "false",
5145
5234
  "event": event,
5235
+ "OnlyLookAbnormalFile": 0,
5236
+ "Page": 1,
5146
5237
  })
5147
5238
  if not payload.get("trashed"):
5148
5239
  match payload["event"]:
@@ -5280,6 +5371,50 @@ class P123Client(P123OpenClient):
5280
5371
  **request_kwargs,
5281
5372
  )
5282
5373
 
5374
+ @overload
5375
+ def fs_fresh(
5376
+ self,
5377
+ payload: dict = {},
5378
+ /,
5379
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5380
+ *,
5381
+ async_: Literal[False] = False,
5382
+ **request_kwargs,
5383
+ ) -> dict:
5384
+ ...
5385
+ @overload
5386
+ def fs_fresh(
5387
+ self,
5388
+ payload: dict = {},
5389
+ /,
5390
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5391
+ *,
5392
+ async_: Literal[True],
5393
+ **request_kwargs,
5394
+ ) -> Coroutine[Any, Any, dict]:
5395
+ ...
5396
+ def fs_fresh(
5397
+ self,
5398
+ payload: dict = {},
5399
+ /,
5400
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5401
+ *,
5402
+ async_: Literal[False, True] = False,
5403
+ **request_kwargs,
5404
+ ) -> dict | Coroutine[Any, Any, dict]:
5405
+ """刷新列表和直链缓存
5406
+
5407
+ POST https://www.123pan.com/api/restful/goapi/v1/cdnLink/cache/refresh
5408
+ """
5409
+ return self.request(
5410
+ "restful/goapi/v1/cdnLink/cache/refresh",
5411
+ "POST",
5412
+ json=payload,
5413
+ base_url=base_url,
5414
+ async_=async_,
5415
+ **request_kwargs,
5416
+ )
5417
+
5283
5418
  @overload # type: ignore
5284
5419
  def fs_rename(
5285
5420
  self,
@@ -5336,6 +5471,56 @@ class P123Client(P123OpenClient):
5336
5471
  **request_kwargs,
5337
5472
  )
5338
5473
 
5474
+ @overload
5475
+ def fs_sync_log(
5476
+ self,
5477
+ payload: dict | int = 1,
5478
+ /,
5479
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5480
+ *,
5481
+ async_: Literal[False] = False,
5482
+ **request_kwargs,
5483
+ ) -> dict:
5484
+ ...
5485
+ @overload
5486
+ def fs_sync_log(
5487
+ self,
5488
+ payload: dict | int = 1,
5489
+ /,
5490
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5491
+ *,
5492
+ async_: Literal[True],
5493
+ **request_kwargs,
5494
+ ) -> Coroutine[Any, Any, dict]:
5495
+ ...
5496
+ def fs_sync_log(
5497
+ self,
5498
+ payload: dict | int = 1,
5499
+ /,
5500
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
5501
+ *,
5502
+ async_: Literal[False, True] = False,
5503
+ **request_kwargs,
5504
+ ) -> dict | Coroutine[Any, Any, dict]:
5505
+ """获取同步空间的操作记录
5506
+
5507
+ GET https://www.123pan.com/api/restful/goapi/v1/sync-disk/file/log
5508
+
5509
+ :payload:
5510
+ - page: int = 1 💡 第几页
5511
+ - pageSize: int = 100 💡 每页大小
5512
+ - searchData: str = <default> 💡 搜索关键字
5513
+ """
5514
+ if not isinstance(payload, dict):
5515
+ payload = {"page": payload, "pageSize": 100}
5516
+ return self.request(
5517
+ "restful/goapi/v1/sync-disk/file/log",
5518
+ params=payload,
5519
+ base_url=base_url,
5520
+ async_=async_,
5521
+ **request_kwargs,
5522
+ )
5523
+
5339
5524
  @overload # type: ignore
5340
5525
  def fs_trash(
5341
5526
  self,
@@ -5391,6 +5576,8 @@ class P123Client(P123OpenClient):
5391
5576
  - "recycleRestore": 移出回收站
5392
5577
 
5393
5578
  - operation: bool = <default>
5579
+ - operatePlace: int = <default>
5580
+ - RequestSource: int = <default>
5394
5581
  """
5395
5582
  if isinstance(payload, (int, str)):
5396
5583
  payload = {"fileTrashInfoList": [{"FileId": payload}]}
@@ -7004,8 +7191,10 @@ class P123Client(P123OpenClient):
7004
7191
  if async_:
7005
7192
  async def request():
7006
7193
  chunks = bio_chunk_async_iter(file, chunksize=slice_size) # type: ignore
7007
- upload_data["partNumberStart"] = 1
7194
+ slice_no = 1
7008
7195
  async for chunk in chunks:
7196
+ upload_data["partNumberStart"] = slice_no
7197
+ upload_data["partNumberEnd"] = slice_no + 1
7009
7198
  resp = await self.upload_prepare(
7010
7199
  upload_data,
7011
7200
  base_url=base_url,
@@ -7014,17 +7203,18 @@ class P123Client(P123OpenClient):
7014
7203
  )
7015
7204
  check_response(resp)
7016
7205
  await self.request(
7017
- resp["data"]["presignedUrls"]["1"],
7206
+ resp["data"]["presignedUrls"][str(slice_no)],
7018
7207
  data=chunk,
7019
7208
  async_=True,
7020
7209
  **upload_request_kwargs,
7021
7210
  )
7022
- upload_data["partNumberStart"] += 1
7211
+ slice_no += 1
7023
7212
  yield request()
7024
7213
  else:
7025
7214
  chunks = bio_chunk_iter(file, chunksize=slice_size) # type: ignore
7026
7215
  for slice_no, chunk in enumerate(chunks, 1):
7027
7216
  upload_data["partNumberStart"] = slice_no
7217
+ upload_data["partNumberEnd"] = slice_no + 1
7028
7218
  resp = self.upload_prepare(
7029
7219
  upload_data,
7030
7220
  base_url=base_url,
@@ -7032,7 +7222,7 @@ class P123Client(P123OpenClient):
7032
7222
  )
7033
7223
  check_response(resp)
7034
7224
  self.request(
7035
- resp["data"]["presignedUrls"]["1"],
7225
+ resp["data"]["presignedUrls"][str(slice_no)],
7036
7226
  data=chunk,
7037
7227
  **upload_request_kwargs,
7038
7228
  )
@@ -7057,7 +7247,7 @@ class P123Client(P123OpenClient):
7057
7247
  async_=async_,
7058
7248
  **request_kwargs,
7059
7249
  )
7060
- return run_gen_step(gen_step, async_=async_)
7250
+ return run_gen_step(gen_step, async_)
7061
7251
 
7062
7252
  @overload
7063
7253
  def upload_file_fast(
@@ -7218,7 +7408,56 @@ class P123Client(P123OpenClient):
7218
7408
  async_=async_,
7219
7409
  **request_kwargs,
7220
7410
  )
7221
- return run_gen_step(gen_step, async_=async_)
7411
+ return run_gen_step(gen_step, async_)
7412
+
7413
+ @overload
7414
+ def user_device_list(
7415
+ self,
7416
+ payload: dict | str = "deviceManagement",
7417
+ /,
7418
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7419
+ *,
7420
+ async_: Literal[False] = False,
7421
+ **request_kwargs,
7422
+ ) -> dict:
7423
+ ...
7424
+ @overload
7425
+ def user_device_list(
7426
+ self,
7427
+ payload: dict | str = "deviceManagement",
7428
+ /,
7429
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7430
+ *,
7431
+ async_: Literal[True],
7432
+ **request_kwargs,
7433
+ ) -> Coroutine[Any, Any, dict]:
7434
+ ...
7435
+ def user_device_list(
7436
+ self,
7437
+ payload: dict | str = "deviceManagement",
7438
+ /,
7439
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7440
+ *,
7441
+ async_: Literal[False, True] = False,
7442
+ **request_kwargs,
7443
+ ) -> dict | Coroutine[Any, Any, dict]:
7444
+ """用户设备列表
7445
+
7446
+ GET https://www.123pan.com/api/user/device_list
7447
+
7448
+ :payload:
7449
+ - event: str = "deviceManagement" 💡 事件类型,"deviceManagement" 为管理登录设备列表
7450
+ - operateType: int = <default>
7451
+ """
7452
+ if not isinstance(payload, dict):
7453
+ payload = {"event": payload}
7454
+ return self.request(
7455
+ "user/device_list",
7456
+ params=payload,
7457
+ base_url=base_url,
7458
+ async_=async_,
7459
+ **request_kwargs,
7460
+ )
7222
7461
 
7223
7462
  @overload
7224
7463
  def user_info(
@@ -7312,4 +7551,57 @@ class P123Client(P123OpenClient):
7312
7551
  request_kwargs["async_"] = async_
7313
7552
  return request(url=api, method="POST", json=payload, **request_kwargs)
7314
7553
 
7315
- # TODO: 添加扫码登录接口,以及通过扫码登录的方法
7554
+ @overload
7555
+ def user_use_history(
7556
+ self,
7557
+ payload: dict | str = "loginRecord",
7558
+ /,
7559
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7560
+ *,
7561
+ async_: Literal[False] = False,
7562
+ **request_kwargs,
7563
+ ) -> dict:
7564
+ ...
7565
+ @overload
7566
+ def user_use_history(
7567
+ self,
7568
+ payload: dict | str = "loginRecord",
7569
+ /,
7570
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7571
+ *,
7572
+ async_: Literal[True],
7573
+ **request_kwargs,
7574
+ ) -> Coroutine[Any, Any, dict]:
7575
+ ...
7576
+ def user_use_history(
7577
+ self,
7578
+ payload: dict | str = "loginRecord",
7579
+ /,
7580
+ base_url: str | Callable[[], str] = DEFAULT_BASE_URL,
7581
+ *,
7582
+ async_: Literal[False, True] = False,
7583
+ **request_kwargs,
7584
+ ) -> dict | Coroutine[Any, Any, dict]:
7585
+ """用户使用记录
7586
+
7587
+ GET https://www.123pan.com/api/user/use_history
7588
+
7589
+ :payload:
7590
+ - event: str = "loginRecord" 💡 事件类型,"loginRecord" 为登录记录
7591
+ """
7592
+ if not isinstance(payload, dict):
7593
+ payload = {"event": payload}
7594
+ return self.request(
7595
+ "user/use_history",
7596
+ params=payload,
7597
+ base_url=base_url,
7598
+ async_=async_,
7599
+ **request_kwargs,
7600
+ )
7601
+
7602
+ # TODO: 添加扫码登录接口,以及通过扫码登录的方法,特别是用已登录的设备扫描一个新的 token 出来
7603
+ # TODO: 添加 同步空间 和 直链空间 的操作接口
7604
+ # TODO: 添加 图床 的操作接口
7605
+ # TODO: 添加 视频转码 的操作接口
7606
+ # TODO: 对于某些工具的接口封装,例如 重复文件清理
7607
+ # TODO: 开放接口有更新了,从此开始 https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced/xogi45g7okqk7svr
@@ -6,14 +6,14 @@ __all__ = ["make_uri", "upload_uri", "get_downurl", "iterdir", "share_iterdir"]
6
6
 
7
7
  from asyncio import sleep as async_sleep
8
8
  from collections import deque
9
- from collections.abc import AsyncIterator, Callable, Coroutine, Iterator
9
+ from collections.abc import AsyncIterator, Callable, Coroutine, Iterable, Iterator, Mapping
10
10
  from errno import EISDIR, ENOENT
11
11
  from functools import partial
12
12
  from itertools import count
13
13
  from time import sleep, time
14
14
  from typing import Literal
15
15
  from typing import overload, Any, Literal
16
- from urllib.parse import unquote
16
+ from urllib.parse import unquote, urlsplit
17
17
 
18
18
  from encode_uri import encode_uri_component_loose
19
19
  from iterutils import run_gen_step, run_gen_step_iter, Yield
@@ -73,7 +73,7 @@ def make_uri(
73
73
  size = info["Size"]
74
74
  s3_key_flag = info["S3KeyFlag"]
75
75
  return f"123://{name}|{size}|{md5}?{s3_key_flag}"
76
- return run_gen_step(gen_step, async_=async_)
76
+ return run_gen_step(gen_step, async_)
77
77
 
78
78
 
79
79
  @overload
@@ -194,19 +194,16 @@ def get_downurl(
194
194
  size = int(size_s)
195
195
  if s3_key_flag:
196
196
  payload = {
197
- "S3KeyFlag": s3_key_flag,
198
197
  "FileName": name,
199
198
  "Etag": md5,
200
199
  "Size": size,
200
+ "S3KeyFlag": s3_key_flag,
201
201
  }
202
202
  else:
203
- resp = yield client.fs_mkdir("我的秒传", async_=async_, **request_kwargs)
204
- check_response(resp)
205
203
  resp = yield client.upload_file_fast(
204
+ file_name=".tempfile",
206
205
  file_md5=md5,
207
- file_name=f"{md5}-{size}",
208
206
  file_size=size,
209
- parent_id=resp["data"]["Info"]["FileId"],
210
207
  duplicate=2,
211
208
  async_=async_,
212
209
  **request_kwargs,
@@ -216,9 +213,12 @@ def get_downurl(
216
213
  resp = yield client.download_info(payload, async_=async_, **request_kwargs)
217
214
  check_response(resp)
218
215
  return resp["data"]["DownloadUrl"]
219
- return run_gen_step(gen_step, async_=async_)
216
+ return run_gen_step(gen_step, async_)
220
217
 
221
218
 
219
+ # TODO: _iterdir 支持广度优先遍历
220
+ # TODO: 失败时,报错信息支持返回已经成功和未成功的列表,并且形式上也要利于断点重试
221
+ # TODO: 支持传入其它自定义的查询参数
222
222
  @overload
223
223
  def _iterdir(
224
224
  fs_files: Callable,
@@ -227,7 +227,9 @@ def _iterdir(
227
227
  min_depth: int = 1,
228
228
  max_depth: int = 1,
229
229
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
230
- interval: int | float = 0,
230
+ cooldown: int | float = 0,
231
+ base_url: None | str | Callable[[], str] = None,
232
+ extra_data: None | Mapping | Iterable[tuple[str, Any]] = None,
231
233
  *,
232
234
  async_: Literal[False] = False,
233
235
  **request_kwargs,
@@ -241,7 +243,9 @@ def _iterdir(
241
243
  min_depth: int = 1,
242
244
  max_depth: int = 1,
243
245
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
244
- interval: int | float = 0,
246
+ cooldown: int | float = 0,
247
+ base_url: None | str | Callable[[], str] = None,
248
+ extra_data: None | Mapping | Iterable[tuple[str, Any]] = None,
245
249
  *,
246
250
  async_: Literal[True],
247
251
  **request_kwargs,
@@ -254,7 +258,9 @@ def _iterdir(
254
258
  min_depth: int = 1,
255
259
  max_depth: int = 1,
256
260
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
257
- interval: int | float = 0,
261
+ cooldown: int | float = 0,
262
+ base_url: None | str | Callable[[], str] = None,
263
+ extra_data: None | Mapping | Iterable[tuple[str, Any]] = None,
258
264
  *,
259
265
  async_: Literal[False, True] = False,
260
266
  **request_kwargs,
@@ -273,7 +279,9 @@ def _iterdir(
273
279
  - 如果返回值是 False,则跳过此节点(但依然会继续处理位于此节点之下的节点)
274
280
  - 如果返回值是 True,则输出此节点
275
281
 
276
- :param interval: 两次调用之间,休息的时间
282
+ :param cooldown: 两次调用之间,冷却的时间(用两次调用开始时的时间差,而不是一次完成到下一次开始的时间差)
283
+ :param base_url: 基地址,如果为空,则用默认
284
+ :param extra_data: 附加数据
277
285
  :param async_: 是否异步
278
286
  :param request_kwargs: 其它请求参数
279
287
 
@@ -281,6 +289,8 @@ def _iterdir(
281
289
  """
282
290
  default_payload = payload
283
291
  page_size = int(payload.setdefault("limit", 100))
292
+ if base_url:
293
+ request_kwargs["base_url"] = base_url
284
294
  def gen_step():
285
295
  nonlocal parent_id
286
296
  dq: deque[tuple[int, int, str]] = deque()
@@ -293,13 +303,13 @@ def _iterdir(
293
303
  payload = {**default_payload, "parentFileId": parent_id}
294
304
  for i in count(1):
295
305
  payload["Page"] = i
296
- if last_ts and interval > 0 and (remains := last_ts + interval - time()) > 0:
306
+ if last_ts and cooldown > 0 and (remains := last_ts + cooldown - time()) > 0:
297
307
  if async_:
298
308
  yield async_sleep(remains)
299
309
  else:
300
310
  sleep(remains)
301
311
  resp = yield fs_files(payload, async_=async_, **request_kwargs)
302
- if interval > 0:
312
+ if cooldown > 0:
303
313
  last_ts = time()
304
314
  check_response(resp)
305
315
  info_list = resp["data"]["InfoList"]
@@ -323,6 +333,8 @@ def _iterdir(
323
333
  continue
324
334
  elif pred:
325
335
  if depth >= min_depth:
336
+ if extra_data:
337
+ info = dict(extra_data, **info)
326
338
  yield Yield(info)
327
339
  if pred is 1:
328
340
  continue
@@ -336,7 +348,7 @@ def _iterdir(
336
348
  break
337
349
  if next_id := resp["data"]["Next"]:
338
350
  payload["next"] = next_id
339
- return run_gen_step_iter(gen_step, async_=async_)
351
+ return run_gen_step_iter(gen_step, async_)
340
352
 
341
353
 
342
354
  @overload
@@ -346,7 +358,8 @@ def iterdir(
346
358
  min_depth: int = 1,
347
359
  max_depth: int = 1,
348
360
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
349
- interval: int | float = 0,
361
+ cooldown: int | float = 0,
362
+ base_url: None | str | Callable[[], str] = None,
350
363
  use_list_new: bool = False,
351
364
  *,
352
365
  async_: Literal[False] = False,
@@ -360,7 +373,8 @@ def iterdir(
360
373
  min_depth: int = 1,
361
374
  max_depth: int = 1,
362
375
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
363
- interval: int | float = 0,
376
+ cooldown: int | float = 0,
377
+ base_url: None | str | Callable[[], str] = None,
364
378
  use_list_new: bool = False,
365
379
  *,
366
380
  async_: Literal[True],
@@ -373,7 +387,8 @@ def iterdir(
373
387
  min_depth: int = 1,
374
388
  max_depth: int = 1,
375
389
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
376
- interval: int | float = 0,
390
+ cooldown: int | float = 0,
391
+ base_url: None | str | Callable[[], str] = None,
377
392
  use_list_new: bool = False,
378
393
  *,
379
394
  async_: Literal[False, True] = False,
@@ -392,7 +407,8 @@ def iterdir(
392
407
  - 如果返回值是 False,则跳过此节点(但依然会继续处理位于此节点之下的节点)
393
408
  - 如果返回值是 True,则输出此节点
394
409
 
395
- :param interval: 两次调用之间,休息的时间
410
+ :param cooldown: 两次调用之间,冷却的时间(用两次调用开始时的时间差,而不是一次完成到下一次开始的时间差)
411
+ :param base_url: 基地址,如果为空,则用默认
396
412
  :param use_list_new: 使用 `P123Client.fs_list_new` 而不是 `P123Client.fs_list`
397
413
  :param async_: 是否异步
398
414
  :param request_kwargs: 其它请求参数
@@ -405,7 +421,8 @@ def iterdir(
405
421
  min_depth=min_depth,
406
422
  max_depth=max_depth,
407
423
  predicate=predicate,
408
- interval=interval,
424
+ cooldown=cooldown,
425
+ base_url=base_url,
409
426
  async_=async_,
410
427
  **request_kwargs,
411
428
  )
@@ -419,7 +436,8 @@ def share_iterdir(
419
436
  min_depth: int = 1,
420
437
  max_depth: int = 1,
421
438
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
422
- interval: int | float = 0,
439
+ cooldown: int | float = 0,
440
+ base_url: None | str | Callable[[], str] = None,
423
441
  *,
424
442
  async_: Literal[False] = False,
425
443
  **request_kwargs,
@@ -433,7 +451,8 @@ def share_iterdir(
433
451
  min_depth: int = 1,
434
452
  max_depth: int = 1,
435
453
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
436
- interval: int | float = 0,
454
+ cooldown: int | float = 0,
455
+ base_url: None | str | Callable[[], str] = None,
437
456
  *,
438
457
  async_: Literal[True],
439
458
  **request_kwargs,
@@ -446,15 +465,24 @@ def share_iterdir(
446
465
  min_depth: int = 1,
447
466
  max_depth: int = 1,
448
467
  predicate: None | Callable[[dict], Literal[None, 0, 1, False, True]] = None,
449
- interval: int | float = 0,
468
+ cooldown: int | float = 0,
469
+ base_url: None | str | Callable[[], str] = None,
450
470
  *,
451
471
  async_: Literal[False, True] = False,
452
472
  **request_kwargs,
453
473
  ) -> Iterator[dict] | AsyncIterator[dict]:
454
474
  """遍历分享的文件列表
455
475
 
456
- :param share_key: 分享码,在分享链接中的位置形如 "https://www.123pan.com/s/{share_key}"
457
- :param share_pwd: 密码,如果没有就不传
476
+ :param share_key: 分享码或者分享链接(可以携带提取码)
477
+
478
+ .. note::
479
+ 在分享链接中的位置形如 f"https://www.123pan.com/s/{share_key}"
480
+
481
+ 如果携带提取码,要写成 f"https://www.123pan.com/s/{share_key}?提取码:{share_pwd}"
482
+
483
+ 上面的基地址不必是 "https://www.123pan.com"
484
+
485
+ :param share_pwd: 提取码(4个文字),可以为空
458
486
  :param parent_id: 父目录 id,默认是根目录
459
487
  :param min_depth: 最小深度,小于此深度的不会输出
460
488
  :param max_depth: 最大深度,大于此深度的不会输出,如果小于 0 则无限
@@ -465,20 +493,33 @@ def share_iterdir(
465
493
  - 如果返回值是 False,则跳过此节点(但依然会继续处理位于此节点之下的节点)
466
494
  - 如果返回值是 True,则输出此节点
467
495
 
468
- :param interval: 两次调用之间,休息的时间
496
+ :param cooldown: 两次调用之间,冷却的时间(用两次调用开始时的时间差,而不是一次完成到下一次开始的时间差)
497
+ :param base_url: 基地址,如果为空,则用默认(如果 `share_key` 是分享链接,则用它的 origin)
469
498
  :param async_: 是否异步
470
499
  :param request_kwargs: 其它请求参数
471
500
 
472
501
  :return: 迭代器,产生文件或目录的信息
473
502
  """
503
+ if share_key.startswith(("http://", "https://")):
504
+ urlp = urlsplit(share_key)
505
+ if not base_url:
506
+ base_url = f"{urlp.scheme}://{urlp.netloc}"
507
+ share_key = urlp.path.rsplit("/", 1)[-1]
508
+ if not share_pwd:
509
+ share_pwd = urlp.query.rpartition(":")[-1]
510
+ if len(share_pwd) != 4:
511
+ share_pwd = ""
512
+ payload = {"ShareKey": share_key, "SharePwd": share_pwd}
474
513
  return _iterdir(
475
514
  P123Client.share_fs_list,
476
- {"ShareKey": share_key, "SharePwd": share_pwd},
515
+ payload,
477
516
  parent_id=parent_id,
478
517
  min_depth=min_depth,
479
518
  max_depth=max_depth,
480
519
  predicate=predicate,
481
- interval=interval,
520
+ cooldown=cooldown,
521
+ base_url=base_url,
522
+ extra_data=payload,
482
523
  async_=async_,
483
524
  **request_kwargs,
484
525
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: p123client
3
- Version: 0.0.6.4
3
+ Version: 0.0.6.9
4
4
  Summary: Python 123 webdisk client.
5
5
  Home-page: https://github.com/ChenyangGao/p123client
6
6
  License: MIT
@@ -31,7 +31,7 @@ Requires-Dist: python-filewrap (>=0.2.6.1)
31
31
  Requires-Dist: python-hashtools (>=0.0.3.3)
32
32
  Requires-Dist: python-http_request (>=0.0.7)
33
33
  Requires-Dist: python-httpfile (>=0.0.5)
34
- Requires-Dist: python-iterutils (>=0.2)
34
+ Requires-Dist: python-iterutils (>=0.2.4.1)
35
35
  Requires-Dist: python-property (>=0.0.3)
36
36
  Requires-Dist: yarl
37
37
  Project-URL: Repository, https://github.com/ChenyangGao/p123client
@@ -1,12 +1,12 @@
1
1
  LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
2
  p123client/__init__.py,sha256=gfUum-q3f_XuXOk2HpArDAIxAlscZm8Fau1kiNkNFpg,214
3
- p123client/client.py,sha256=Ha09EnH9tC2CfacGH7DaO93BUVB6wRDiSym32In2WFw,248614
3
+ p123client/client.py,sha256=sjv9kzsiVl8hFMsaaAMGjCpsbewo3LaXqbYUpzhh5bI,258804
4
4
  p123client/const.py,sha256=T17OzPQrnIG6w_Hzjc8TF_fFMKa-hQMSn1gff8pVcBc,56
5
5
  p123client/exception.py,sha256=020xGo8WQmGCJz1UzNg9oFzpEvToQcgTye0s6lkFASQ,1540
6
6
  p123client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- p123client/tool/__init__.py,sha256=2k_tcc67O9QG4wzESIdnAwqNHybCGlrsnxo_uBqBhEI,16673
7
+ p123client/tool/__init__.py,sha256=rZIgiXMNN21acq6OdT3ywQdPQRSmxDrifzHmhj-5E8E,18860
8
8
  p123client/type.py,sha256=T17OzPQrnIG6w_Hzjc8TF_fFMKa-hQMSn1gff8pVcBc,56
9
- p123client-0.0.6.4.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
10
- p123client-0.0.6.4.dist-info/METADATA,sha256=7l04-PIItAf9zNy88qMzDiis2KeBX8HNucLCtvsO3ik,8855
11
- p123client-0.0.6.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
12
- p123client-0.0.6.4.dist-info/RECORD,,
9
+ p123client-0.0.6.9.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
10
+ p123client-0.0.6.9.dist-info/METADATA,sha256=BGbHQeCskMwE3Lxh985P00v3W4BDi49-b-5-YRKJEHc,8859
11
+ p123client-0.0.6.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
12
+ p123client-0.0.6.9.dist-info/RECORD,,