p115client 0.0.5.12.3__py3-none-any.whl → 0.0.5.13__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.
p115client/client.py CHANGED
@@ -59,6 +59,7 @@ from orjson import dumps, loads
59
59
  from p115cipher.fast import (
60
60
  rsa_encode, rsa_decode, ecdh_encode_token, ecdh_aes_encode, ecdh_aes_decode, make_upload_payload,
61
61
  )
62
+ from p115pickcode import id_to_pickcode
62
63
  from property import locked_cacheproperty
63
64
  from re import compile as re_compile
64
65
  from startfile import startfile, startfile_async # type: ignore
@@ -316,7 +317,7 @@ def cookies_equal(cookies1: None | str, cookies2: None | str, /) -> bool:
316
317
  return True
317
318
  cks1 = cookies_str_to_dict(cookies1)
318
319
  cks2 = cookies_str_to_dict(cookies2)
319
- return cks1.get("UID", nan) == cks2.get("UID", nan) and cks1.get("SEID", nan) == cks2.get("SEID", nan)
320
+ return all(cks1.get(key, nan) == cks2.get(key, nan) for key in ("UID", "SEID"))
320
321
 
321
322
 
322
323
  def convert_digest(digest, /):
@@ -666,26 +667,20 @@ def normalize_attr_web[D: dict[str, Any]](
666
667
  if dict_cls is None:
667
668
  dict_cls = cast(type[D], dict)
668
669
  attr: dict[str, Any] = dict_cls()
669
- is_directory = attr["is_dir"] = "fid" not in info
670
- if not simple:
671
- attr["is_directory"] = is_directory
672
- if is_directory:
673
- attr["id"] = int(info["cid"]) # cid => category_id
674
- attr["parent_id"] = int(info["pid"]) # pid => parent_id
670
+ is_dir = attr["is_dir"] = "fid" not in info
671
+ if is_dir:
672
+ attr["id"] = int(info["cid"]) # category_id
673
+ attr["parent_id"] = int(info["pid"]) # parent_id
675
674
  else:
676
- attr["id"] = int(info["fid"]) # fid => file_id
677
- attr["parent_id"] = int(info["cid"])
678
- attr["name"] = info["n"]
675
+ attr["id"] = int(info["fid"]) # file_id
676
+ attr["parent_id"] = int(info["cid"]) # category_id
677
+ attr["name"] = info.get("n") or info["file_name"]
679
678
  attr["sha1"] = info.get("sha") or ""
680
679
  attr["size"] = int(info.get("s") or 0)
681
680
  if "pc" in info:
682
681
  attr["pickcode"] = info["pc"]
683
- if not simple:
684
- attr["pick_code"] = attr["pickcode"]
685
- if "pt" in info:
686
- attr["pick_time"] = int(info["pt"] or 0)
687
- if "e" in info:
688
- attr["pick_expire"] = int(info["e"] or 0)
682
+ else:
683
+ attr["pickcode"] = id_to_pickcode(attr["id"], is_dir=is_dir)
689
684
  if simple:
690
685
  if "c" in info:
691
686
  attr["is_collect"] = int(info["c"])
@@ -694,7 +689,8 @@ def normalize_attr_web[D: dict[str, Any]](
694
689
  if "te" in info:
695
690
  attr["mtime"] = int(info["te"])
696
691
  else:
697
- attr["ico"] = info.get("ico", "folder" if is_directory else "")
692
+ attr["pick_code"] = attr["pickcode"]
693
+ attr["ico"] = info.get("ico", "folder" if is_dir else "")
698
694
  if "te" in info:
699
695
  attr["mtime"] = attr["user_utime"] = int(info["te"])
700
696
  if "tp" in info:
@@ -704,39 +700,37 @@ def normalize_attr_web[D: dict[str, Any]](
704
700
  if "tu" in info:
705
701
  attr["utime"] = int(info["tu"])
706
702
  if t := info.get("t"):
707
- if isinstance(t, (int, float)):
708
- attr["time"] = t
709
- elif t.isdecimal():
710
- attr["time"] = int(t)
703
+ attr["time"] = try_parse_int(t)
711
704
  if "fdes" in info:
712
705
  val = info["fdes"]
713
706
  if isinstance(val, str):
714
707
  attr["desc"] = val
715
- attr["has_desc"] = bool(val)
708
+ attr["has_desc"] = 1 if val else 0
716
709
  for key, name in (
717
710
  ("aid", "area_id"),
718
711
  ("audio_play_long", "audio_play_long"),
719
- ("c", "violated"),
720
712
  ("c", "is_collect"),
721
713
  ("cc", "cover"),
714
+ ("cc", "category_cover"),
722
715
  ("class", "class"),
723
716
  ("current_time", "current_time"),
724
- #("d", "has_desc"),
717
+ ("d", "has_desc"),
725
718
  ("dp", "dir_path"),
719
+ ("e", "pick_expire"),
726
720
  ("fl", "labels"),
727
- ("hdf", "hidden"),
728
721
  ("hdf", "is_private"),
729
722
  ("is_top", "is_top"),
730
723
  ("ispl", "show_play_long"),
731
724
  ("issct", "is_shortcut"),
732
725
  ("iv", "is_video"),
733
726
  ("last_time", "last_time"),
734
- ("m", "star"),
735
727
  ("m", "is_mark"),
728
+ ("m", "star"),
736
729
  ("ns", "name_show"),
737
- #("p", "has_pass"),
730
+ ("p", "has_pass"),
738
731
  ("play_long", "play_long"),
739
732
  ("played_end", "played_end"),
733
+ ("pt", "pick_time"),
740
734
  ("score", "score"),
741
735
  ("sh", "is_share"),
742
736
  ("sta", "status"),
@@ -748,6 +742,8 @@ def normalize_attr_web[D: dict[str, Any]](
748
742
  if vdi := info.get("vdi"):
749
743
  attr["defination"] = vdi
750
744
  match vdi:
745
+ case 1:
746
+ attr["defination_str"] = "video-sd"
751
747
  case 2:
752
748
  attr["defination_str"] = "video-hd"
753
749
  case 3:
@@ -760,7 +756,7 @@ def normalize_attr_web[D: dict[str, Any]](
760
756
  attr["defination_str"] = "video-origin"
761
757
  case _:
762
758
  attr["defination_str"] = "video-sd"
763
- if is_directory:
759
+ if is_dir:
764
760
  attr["type"] = 0
765
761
  elif info.get("iv") or "vdi" in info:
766
762
  attr["type"] = 4
@@ -814,18 +810,16 @@ def normalize_attr_app[D: dict[str, Any]](
814
810
  if dict_cls is None:
815
811
  dict_cls = cast(type[D], dict)
816
812
  attr: dict[str, Any] = dict_cls()
817
- is_directory = attr["is_dir"] = info["fc"] == "0" # fc => file_category
818
- if not simple:
819
- attr["is_directory"] = is_directory
820
- attr["id"] = int(info["fid"]) # fid => file_id
821
- attr["parent_id"] = int(info["pid"]) # pid => parent_id
813
+ is_dir = attr["is_dir"] = info["fc"] == "0" # file_category
814
+ attr["id"] = int(info["fid"]) # file_id
815
+ attr["parent_id"] = int(info["pid"]) # parent_id
822
816
  attr["name"] = info["fn"]
823
817
  sha1 = attr["sha1"] = info.get("sha1") or ""
824
818
  attr["size"] = int(info.get("fs") or 0)
825
819
  if "pc" in info:
826
820
  attr["pickcode"] = info["pc"]
827
- if not simple:
828
- attr["pick_code"] = attr["pickcode"]
821
+ else:
822
+ attr["pickcode"] = id_to_pickcode(attr["id"], is_dir=is_dir)
829
823
  if simple:
830
824
  if "ic" in info:
831
825
  attr["is_collect"] = int(info["ic"])
@@ -834,6 +828,7 @@ def normalize_attr_app[D: dict[str, Any]](
834
828
  if "upt" in info:
835
829
  attr["mtime"] = int(info["upt"])
836
830
  else:
831
+ attr["pick_code"] = attr["pickcode"]
837
832
  attr["ico"] = info.get("ico", "folder" if attr["is_dir"] else "")
838
833
  if "thumb" in info:
839
834
  thumb = info["thumb"]
@@ -854,21 +849,19 @@ def normalize_attr_app[D: dict[str, Any]](
854
849
  ("def", "defination"), # 视频清晰度:1:标清 2:高清 3:超清 4:1080P 5:4k 100:原画
855
850
  ("def2", "defination2"), # 视频清晰度:1:标清 2:高清 3:超清 4:1080P 5:4k 100:原画
856
851
  ("fatr", "audio_play_long"), # 音频长度
857
- ("fco", "cover"), # 文件夹封面
858
- ("fco", "folder_cover"), # 文件夹封面
852
+ ("fco", "cover"), # 目录封面
853
+ ("fco", "folder_cover"), # 目录封面
859
854
  ("fdesc", "desc"), # 文件备注
860
855
  ("fl", "labels"), # 文件标签,得到 1 个字典列表
861
856
  ("flabel", "fflabel"), # 文件标签(一般为空)
862
857
  ("fta", "status"), # 文件状态:0/2:未上传完成,1:已上传完成
863
858
  ("ftype", "file_type"), # 文件类型代码
864
- ("ic", "violated"), # 是否违规
865
859
  ("ic", "is_collect"), # 是否违规
866
860
  ("is_top", "is_top"), # 是否置顶
867
- ("ism", "star"), # 是否星标
868
861
  ("ism", "is_mark"), # 是否星标
869
- ("isp", "hidden"), # 是否加密隐藏(隐藏模式中显示)
862
+ ("ism", "star"), # 是否星标(别名)
870
863
  ("isp", "is_private"), # 是否加密隐藏(隐藏模式中显示)
871
- ("ispl", "show_play_long"), # 是否统计文件夹下视频时长
864
+ ("ispl", "show_play_long"), # 是否统计目录下视频时长
872
865
  ("iss", "is_share"), # 是否共享
873
866
  ("issct", "is_shortcut"), # 是否在快捷入口
874
867
  ("isv", "is_video"), # 是否为视频
@@ -884,7 +877,7 @@ def normalize_attr_app[D: dict[str, Any]](
884
877
  ):
885
878
  if key in info:
886
879
  attr[name] = try_parse_int(info[key])
887
- if is_directory:
880
+ if is_dir:
888
881
  attr["type"] = 0
889
882
  elif (thumb := info.get("thumb")) and thumb.startswith("?"):
890
883
  attr["type"] = 2
@@ -942,15 +935,15 @@ def normalize_attr_app2[D: dict[str, Any]](
942
935
  attr: dict[str, Any] = dict_cls()
943
936
  if "file_id" in info and "parent_id" in info:
944
937
  if "file_category" in info:
945
- is_directory = not int(info["file_category"])
938
+ is_dir = not int(info["file_category"])
946
939
  else:
947
- is_directory = bool(info.get("sha1") or info.get("file_sha1"))
940
+ is_dir = bool(info.get("sha1") or info.get("file_sha1"))
948
941
  attr["id"] = int(info["file_id"])
949
942
  attr["parent_id"] = int(info["parent_id"])
950
943
  attr["name"] = info["file_name"]
951
944
  else:
952
- is_directory = "file_id" not in info
953
- if is_directory:
945
+ is_dir = "file_id" not in info
946
+ if is_dir:
954
947
  attr["id"] = int(info["file_id"])
955
948
  attr["parent_id"] = int(info["category_id"])
956
949
  attr["name"] = info["file_name"]
@@ -958,15 +951,13 @@ def normalize_attr_app2[D: dict[str, Any]](
958
951
  attr["id"] = int(info["category_id"])
959
952
  attr["parent_id"] = int(info["parent_id"])
960
953
  attr["name"] = info["category_name"]
961
- attr["is_dir"] = is_directory
962
- if not simple:
963
- attr["is_directory"] = is_directory
954
+ attr["is_dir"] = is_dir
964
955
  attr["sha1"] = info.get("sha1") or info.get("file_sha1") or ""
965
956
  attr["size"] = int(info.get("file_size") or 0)
966
957
  if "pick_code" in info:
967
958
  attr["pickcode"] = info["pick_code"]
968
- if not simple:
969
- attr["pick_code"] = attr["pickcode"]
959
+ else:
960
+ attr["pickcode"] = id_to_pickcode(attr["id"], is_dir=is_dir)
970
961
  if simple:
971
962
  if "is_collect" in info:
972
963
  attr["is_collect"] = int(info["is_collect"])
@@ -975,7 +966,8 @@ def normalize_attr_app2[D: dict[str, Any]](
975
966
  if "user_ptime" in info:
976
967
  attr["mtime"] = int(info["user_ptime"])
977
968
  else:
978
- if is_directory:
969
+ attr["pick_code"] = attr["pickcode"]
970
+ if is_dir:
979
971
  if "thumb_url" in info:
980
972
  attr["thumb"] = info["thumb_url"]
981
973
  if "file_description" in info:
@@ -1004,13 +996,6 @@ def normalize_attr_app2[D: dict[str, Any]](
1004
996
  attr["ico"] = info.get("ico", "folder" if attr["is_dir"] else "")
1005
997
  if "fl" in info:
1006
998
  attr["labels"] = info["fl"]
1007
- for key, name in (
1008
- ("is_collect", "violated"),
1009
- ("is_mark", "star"),
1010
- ("is_private", "hidden"),
1011
- ):
1012
- if key in info:
1013
- attr[name] = int(info[key] or 0)
1014
999
  for name in (
1015
1000
  "area_id",
1016
1001
  "can_delete",
@@ -1047,7 +1032,9 @@ def normalize_attr_app2[D: dict[str, Any]](
1047
1032
  ):
1048
1033
  if name in info:
1049
1034
  attr[name] = try_parse_int(info[name])
1050
- if is_directory:
1035
+ if "is_mark" in attr:
1036
+ attr["star"] = attr["is_mark"]
1037
+ if is_dir:
1051
1038
  attr["type"] = 0
1052
1039
  elif "thumb_url" in info:
1053
1040
  attr["type"] = 2
@@ -1454,7 +1441,7 @@ class ClientRequestMixin:
1454
1441
  headers = IgnoreCaseDict(self.headers)
1455
1442
  headers.update(request_kwargs.get("headers") or {})
1456
1443
  if m := CRE_API_match(url):
1457
- headers["host"] = m.expand(r"\1.api.115.com")
1444
+ headers.setdefault("host", m.expand(r"\1.api.115.com"))
1458
1445
  request_kwargs["headers"] = headers
1459
1446
  if ecdh_encrypt:
1460
1447
  url = make_url(url, _default_k_ec)
@@ -3259,7 +3246,7 @@ class P115OpenClient(ClientRequestMixin):
3259
3246
  name=info["file_name"],
3260
3247
  size=int(info["file_size"]),
3261
3248
  sha1=info["sha1"],
3262
- is_directory=not url,
3249
+ is_dir=not url,
3263
3250
  headers=resp["headers"],
3264
3251
  )
3265
3252
  raise P115FileNotFoundError(
@@ -3534,7 +3521,7 @@ class P115OpenClient(ClientRequestMixin):
3534
3521
  - source: str = <default>
3535
3522
  - sys_dir: int | str = <default> 💡 系统通用目录
3536
3523
  - star: 0 | 1 = <default> 💡 是否星标文件
3537
- - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示文件夹:1:展示 0:不展示
3524
+ - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
3538
3525
  - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
3539
3526
  - type: int = <default> 💡 文件类型
3540
3527
 
@@ -3784,6 +3771,13 @@ class P115OpenClient(ClientRequestMixin):
3784
3771
 
3785
3772
  GET https://proapi.115.com/open/ufile/search
3786
3773
 
3774
+ .. attention::
3775
+ 最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
3776
+
3777
+ 不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
3778
+
3779
+ 它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
3780
+
3787
3781
  .. hint::
3788
3782
  相当于 `P115Client.fs_search_app2`
3789
3783
 
@@ -4250,7 +4244,7 @@ class P115OpenClient(ClientRequestMixin):
4250
4244
  - save_path: str 💡 保存到 `wp_path_id` 对应目录下的相对路径
4251
4245
  - torrent_sha1: str 💡 种子文件的 sha1
4252
4246
  - wanted: str 💡 选择文件进行下载(是数字索引,从 0 开始计数,用 "," 分隔)
4253
- - wp_path_id: int | str = <default> 💡 保存目标文件夹 id
4247
+ - wp_path_id: int | str = <default> 💡 保存目标目录 id
4254
4248
  """
4255
4249
  api = complete_proapi("/open/offline/add_task_bt ", base_url)
4256
4250
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -4775,8 +4769,8 @@ class P115OpenClient(ClientRequestMixin):
4775
4769
  - topupload: int = 0 💡 上传调度文件类型调度标记
4776
4770
 
4777
4771
  - 0: 单文件上传任务标识 1 条单独的文件上传记录
4778
- - 1: 文件夹任务调度的第 1 个子文件上传请求标识 1 次文件夹上传记录
4779
- - 2: 文件夹任务调度的其余后续子文件不作记作单独上传的上传记录
4772
+ - 1: 目录任务调度的第 1 个子文件上传请求标识 1 次目录上传记录
4773
+ - 2: 目录任务调度的其余后续子文件不作记作单独上传的上传记录
4780
4774
  - -1: 没有该参数
4781
4775
 
4782
4776
  - sign_key: str = "" 💡 二次验证时读取文件的范围
@@ -6708,7 +6702,7 @@ class P115Client(P115OpenClient):
6708
6702
  if is_open_api:
6709
6703
  headers["cookie"] = ""
6710
6704
  if m := CRE_API_match(url):
6711
- headers["host"] = m.expand(r"\1.api.115.com")
6705
+ headers.setdefault("host", m.expand(r"\1.api.115.com"))
6712
6706
  if ecdh_encrypt:
6713
6707
  url = make_url(url, _default_k_ec)
6714
6708
  if data:
@@ -7943,7 +7937,7 @@ class P115Client(P115OpenClient):
7943
7937
  pickcode=pickcode,
7944
7938
  name=resp["file_name"],
7945
7939
  size=int(resp["file_size"]),
7946
- is_directory=not resp["state"],
7940
+ is_dir=not resp["state"],
7947
7941
  headers=resp["headers"],
7948
7942
  )
7949
7943
  else:
@@ -7962,7 +7956,7 @@ class P115Client(P115OpenClient):
7962
7956
  url,
7963
7957
  pickcode=pickcode,
7964
7958
  name=unquote(urlsplit(url).path.rsplit("/", 1)[-1]),
7965
- is_directory=False,
7959
+ is_dir=False,
7966
7960
  headers=resp["headers"],
7967
7961
  )
7968
7962
  for fid, info in resp["data"].items():
@@ -7979,7 +7973,7 @@ class P115Client(P115OpenClient):
7979
7973
  name=info["file_name"],
7980
7974
  size=int(info["file_size"]),
7981
7975
  sha1=info["sha1"],
7982
- is_directory=not url,
7976
+ is_dir=not url,
7983
7977
  headers=resp["headers"],
7984
7978
  )
7985
7979
  raise P115FileNotFoundError(
@@ -8302,6 +8296,7 @@ class P115Client(P115OpenClient):
8302
8296
  :payload:
8303
8297
  - pick_code: str
8304
8298
  - full_name: str
8299
+ - dl: int = <default>
8305
8300
  """
8306
8301
  api = complete_proapi("/2.0/ufile/extract_down_file", base_url, app)
8307
8302
  headers = request_kwargs.get("headers")
@@ -8520,8 +8515,9 @@ class P115Client(P115OpenClient):
8520
8515
  GET https://webapi.115.com/files/extract_folders
8521
8516
 
8522
8517
  :payload:
8523
- - pick_code: str
8524
- - full_dir_name: str
8518
+ - pick_code: str 💡 压缩包文件的提取码
8519
+ - full_dir_name: str 💡 多个用逗号 "," 隔开
8520
+ - full_file_name: str = <default> 💡 多个用逗号 "," 隔开
8525
8521
  """
8526
8522
  api = complete_webapi("/files/extract_folders", base_url=base_url)
8527
8523
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@@ -8562,9 +8558,9 @@ class P115Client(P115OpenClient):
8562
8558
  POST https://webapi.115.com/files/extract_folders
8563
8559
 
8564
8560
  :payload:
8565
- - pick_code: str
8566
- - full_dir_name: str 💡 多个用逗号 "," 隔开
8567
- - full_file_name: str 💡 多个用逗号 "," 隔开
8561
+ - pick_code: str 💡 压缩包文件的提取码
8562
+ - full_dir_name: str 💡 多个用逗号 "," 隔开
8563
+ - full_file_name: str = <default> 💡 多个用逗号 "," 隔开
8568
8564
  """
8569
8565
  api = complete_webapi("/files/extract_folders", base_url=base_url)
8570
8566
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -9924,64 +9920,6 @@ class P115Client(P115OpenClient):
9924
9920
  api = complete_webapi("/files/edit", base_url=base_url)
9925
9921
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
9926
9922
 
9927
- @overload
9928
- def fs_edit_app(
9929
- self,
9930
- payload: int | str | tuple[int | str] | list | dict,
9931
- /,
9932
- app: str = "android",
9933
- base_url: bool | str | Callable[[], str] = False,
9934
- *,
9935
- async_: Literal[False] = False,
9936
- **request_kwargs,
9937
- ) -> dict:
9938
- ...
9939
- @overload
9940
- def fs_edit_app(
9941
- self,
9942
- payload: int | str | tuple[int | str] | list | dict,
9943
- /,
9944
- app: str = "android",
9945
- base_url: bool | str | Callable[[], str] = False,
9946
- *,
9947
- async_: Literal[True],
9948
- **request_kwargs,
9949
- ) -> Coroutine[Any, Any, dict]:
9950
- ...
9951
- def fs_edit_app(
9952
- self,
9953
- payload: int | str | tuple[int | str] | list | dict,
9954
- /,
9955
- app: str = "android",
9956
- base_url: bool | str | Callable[[], str] = False,
9957
- *,
9958
- async_: Literal[False, True] = False,
9959
- **request_kwargs,
9960
- ) -> dict | Coroutine[Any, Any, dict]:
9961
- """设置文件或目录(备注、标签等)
9962
-
9963
- POST https://proapi.115.com/android/files/update
9964
-
9965
- :payload:
9966
- - file_id: int | str
9967
- - file_id[]: int | str
9968
- - ...
9969
- - file_id[0]: int | str
9970
- - file_id[1]: int | str
9971
- - ...
9972
- - file_desc: str = <default> 💡 可以用 html
9973
- - file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
9974
- - fid_cover: int | str = <default> 💡 封面图片的文件 id,多个用逗号 "," 隔开,如果要删除,值设为 0 即可
9975
- - show_play_long: 0 | 1 = <default> 💡 文件名称显示时长
9976
- - ...
9977
- """
9978
- api = complete_proapi("/files/update", base_url, app)
9979
- if isinstance(payload, (int, str)):
9980
- payload = {"file_id": payload}
9981
- elif isinstance(payload, tuple):
9982
- payload = {f"file_id[i]": p for i, p in enumerate(payload)}
9983
- return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
9984
-
9985
9923
  @overload
9986
9924
  def _fs_edit_set(
9987
9925
  self,
@@ -10485,7 +10423,7 @@ class P115Client(P115OpenClient):
10485
10423
  - source: str = <default>
10486
10424
  - sys_dir: int | str = <default>
10487
10425
  - star: 0 | 1 = <default> 💡 是否星标文件
10488
- - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示文件夹:1:展示 0:不展示
10426
+ - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
10489
10427
  - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
10490
10428
  - suffix_type: int = <default>
10491
10429
  - type: int = <default> 💡 文件类型
@@ -10627,7 +10565,7 @@ class P115Client(P115OpenClient):
10627
10565
  - source: str = <default>
10628
10566
  - sys_dir: int | str = <default> 💡 系统通用目录
10629
10567
  - star: 0 | 1 = <default> 💡 是否星标文件
10630
- - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示文件夹:1:展示 0:不展示
10568
+ - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
10631
10569
  - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
10632
10570
  - type: int = <default> 💡 文件类型
10633
10571
 
@@ -10757,7 +10695,7 @@ class P115Client(P115OpenClient):
10757
10695
  - source: str = <default>
10758
10696
  - sys_dir: int | str = <default>
10759
10697
  - star: 0 | 1 = <default> 💡 是否星标文件
10760
- - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示文件夹:1:展示 0:不展示
10698
+ - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
10761
10699
  - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
10762
10700
  - type: int = <default> 💡 文件类型
10763
10701
 
@@ -10877,7 +10815,7 @@ class P115Client(P115OpenClient):
10877
10815
  - source: str = <default>
10878
10816
  - sys_dir: int | str = <default>
10879
10817
  - star: 0 | 1 = <default> 💡 是否星标文件
10880
- - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示文件夹:1:展示 0:不展示
10818
+ - stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
10881
10819
  - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
10882
10820
  - type: int = <default> 💡 文件类型
10883
10821
 
@@ -11125,11 +11063,11 @@ class P115Client(P115OpenClient):
11125
11063
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
11126
11064
 
11127
11065
  @overload
11128
- def fs_top_set(
11066
+ def fs_files_update_app(
11129
11067
  self,
11130
- payload: int | str | Iterable[int | str] | dict,
11068
+ payload: int | str | tuple[int | str] | list | dict,
11131
11069
  /,
11132
- top: bool = True,
11070
+ app: str = "android",
11133
11071
  base_url: bool | str | Callable[[], str] = False,
11134
11072
  *,
11135
11073
  async_: Literal[False] = False,
@@ -11137,42 +11075,50 @@ class P115Client(P115OpenClient):
11137
11075
  ) -> dict:
11138
11076
  ...
11139
11077
  @overload
11140
- def fs_top_set(
11078
+ def fs_files_update_app(
11141
11079
  self,
11142
- payload: int | str | Iterable[int | str] | dict,
11080
+ payload: int | str | tuple[int | str] | list | dict,
11143
11081
  /,
11144
- top: bool = True,
11082
+ app: str = "android",
11145
11083
  base_url: bool | str | Callable[[], str] = False,
11146
11084
  *,
11147
11085
  async_: Literal[True],
11148
11086
  **request_kwargs,
11149
11087
  ) -> Coroutine[Any, Any, dict]:
11150
11088
  ...
11151
- def fs_top_set(
11089
+ def fs_files_update_app(
11152
11090
  self,
11153
- payload: int | str | Iterable[int | str] | dict,
11091
+ payload: int | str | tuple[int | str] | list | dict,
11154
11092
  /,
11155
- top: bool = True,
11093
+ app: str = "android",
11156
11094
  base_url: bool | str | Callable[[], str] = False,
11157
11095
  *,
11158
11096
  async_: Literal[False, True] = False,
11159
11097
  **request_kwargs,
11160
11098
  ) -> dict | Coroutine[Any, Any, dict]:
11161
- """文件或目录置顶
11099
+ """设置(若干个)文件或目录(名字、备注、标签等)
11162
11100
 
11163
- POST https://webapi.115.com/files/top
11101
+ POST https://proapi.115.com/android/files/update
11164
11102
 
11165
11103
  :payload:
11166
- - file_id: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
11167
- - top: 0 | 1 = 1
11104
+ - file_id: int | str
11105
+ - file_id[]: int | str
11106
+ - ...
11107
+ - file_id[0]: int | str
11108
+ - file_id[1]: int | str
11109
+ - ...
11110
+ - file_desc: str = <default> 💡 可以用 html
11111
+ - file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
11112
+ - file_name: str = <default> 💡 文件或目录名
11113
+ - fid_cover: int | str = <default> 💡 封面图片的文件 id,多个用逗号 "," 隔开,如果要删除,值设为 0 即可
11114
+ - show_play_long: 0 | 1 = <default> 💡 文件名称显示时长
11115
+ - ...
11168
11116
  """
11169
- api = complete_webapi("/files/top", base_url=base_url)
11117
+ api = complete_proapi("/files/update", base_url, app)
11170
11118
  if isinstance(payload, (int, str)):
11171
- payload = {"file_id": payload, "top": int(top)}
11172
- elif not isinstance(payload, dict):
11173
- payload = {"file_id": ",".join(map(str, payload)), "top": int(top)}
11174
- else:
11175
- payload = {"top": int(top), **payload}
11119
+ payload = {"file_id": payload}
11120
+ elif isinstance(payload, tuple):
11121
+ payload = {f"file_id[i]": p for i, p in enumerate(payload)}
11176
11122
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
11177
11123
 
11178
11124
  @overload
@@ -11262,6 +11208,61 @@ class P115Client(P115OpenClient):
11262
11208
  payload = {f"show_play_long[{id}]": is_set for id in ids}
11263
11209
  return self.fs_batch_edit(payload, async_=async_, **request_kwargs)
11264
11210
 
11211
+ @overload
11212
+ def fs_folder_update_app(
11213
+ self,
11214
+ payload: dict,
11215
+ /,
11216
+ app: str = "android",
11217
+ base_url: bool | str | Callable[[], str] = False,
11218
+ *,
11219
+ async_: Literal[False] = False,
11220
+ **request_kwargs,
11221
+ ) -> dict:
11222
+ ...
11223
+ @overload
11224
+ def fs_folder_update_app(
11225
+ self,
11226
+ payload: dict,
11227
+ /,
11228
+ app: str = "android",
11229
+ base_url: bool | str | Callable[[], str] = False,
11230
+ *,
11231
+ async_: Literal[True],
11232
+ **request_kwargs,
11233
+ ) -> Coroutine[Any, Any, dict]:
11234
+ ...
11235
+ def fs_folder_update_app(
11236
+ self,
11237
+ payload: dict,
11238
+ /,
11239
+ app: str = "android",
11240
+ base_url: bool | str | Callable[[], str] = False,
11241
+ *,
11242
+ async_: Literal[False, True] = False,
11243
+ **request_kwargs,
11244
+ ) -> dict | Coroutine[Any, Any, dict]:
11245
+ """设置文件或目录,或者创建目录
11246
+
11247
+ POST https://proapi.115.com/android/folder/update
11248
+
11249
+ .. note::
11250
+ 如果提供了 `cid` 和 `name`,则表示对 `cid` 对应的文件或目录进行改名,否则创建目录
11251
+
11252
+ :payload:
11253
+ - name: str 💡 名字
11254
+ - pid: int = 0 💡 在此目录 id 下创建目录
11255
+ - aid: int = 1 💡 area_id
11256
+ - cid: int = <default> 💡 文件或目录的 id,优先级高于 `pid`
11257
+ - user_id: int = <default> 💡 不用管
11258
+ - ...
11259
+ """
11260
+ api = complete_proapi("/folder/update", base_url, app)
11261
+ payload = dict(payload, user_id=self.user_id)
11262
+ payload.setdefault("aid", 1)
11263
+ payload.setdefault("pid", 0)
11264
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
11265
+
11265
11266
  @overload
11266
11267
  def fs_hide(
11267
11268
  self,
@@ -11615,7 +11616,7 @@ class P115Client(P115OpenClient):
11615
11616
  - 播放视频: 3
11616
11617
  - 上传: 4
11617
11618
  - ??: 5
11618
- - ??: 6
11619
+ - ??: 6(似乎是一些在离线、转存等过程中有重名的目录)
11619
11620
  - 接收: 7
11620
11621
  - 移动: 8
11621
11622
 
@@ -11770,7 +11771,7 @@ class P115Client(P115OpenClient):
11770
11771
  - 播放视频: 3
11771
11772
  - 上传: 4
11772
11773
  - ??: 5
11773
- - ??: 6
11774
+ - ??: 6(似乎是一些在离线、转存等过程中有重名的目录)
11774
11775
  - 接收: 7
11775
11776
  - 移动: 8
11776
11777
 
@@ -11830,7 +11831,7 @@ class P115Client(P115OpenClient):
11830
11831
  - 播放视频: 3
11831
11832
  - 上传: 4
11832
11833
  - ??: 5
11833
- - ??: 6
11834
+ - ??: 6(似乎是一些在离线、转存等过程中有重名的目录)
11834
11835
  - 接收: 7
11835
11836
  - 移动: 8
11836
11837
  """
@@ -11891,7 +11892,7 @@ class P115Client(P115OpenClient):
11891
11892
  - 播放视频: 3
11892
11893
  - 上传: 4
11893
11894
  - ??: 5
11894
- - ??: 6
11895
+ - ??: 6(似乎是一些在离线、转存等过程中有重名的目录)
11895
11896
  - 接收: 7
11896
11897
  - 移动: 8
11897
11898
  """
@@ -12763,7 +12764,7 @@ class P115Client(P115OpenClient):
12763
12764
  async_: Literal[False, True] = False,
12764
12765
  **request_kwargs,
12765
12766
  ) -> dict | Coroutine[Any, Any, dict]:
12766
- """为文件或目录设置标签,此接口是对 `fs_edit_app` 的封装
12767
+ """为文件或目录设置标签,此接口是对 `fs_files_update_app` 的封装
12767
12768
 
12768
12769
  .. attention::
12769
12770
  这个接口会把标签列表进行替换,而不是追加
@@ -12991,8 +12992,9 @@ class P115Client(P115OpenClient):
12991
12992
  @overload
12992
12993
  def fs_mkdir_app(
12993
12994
  self,
12994
- payload: dict,
12995
+ payload: dict | str,
12995
12996
  /,
12997
+ pid: int = 0,
12996
12998
  app: str = "android",
12997
12999
  base_url: bool | str | Callable[[], str] = False,
12998
13000
  *,
@@ -13003,8 +13005,9 @@ class P115Client(P115OpenClient):
13003
13005
  @overload
13004
13006
  def fs_mkdir_app(
13005
13007
  self,
13006
- payload: dict,
13008
+ payload: dict | str,
13007
13009
  /,
13010
+ pid: int = 0,
13008
13011
  app: str = "android",
13009
13012
  base_url: bool | str | Callable[[], str] = False,
13010
13013
  *,
@@ -13014,26 +13017,32 @@ class P115Client(P115OpenClient):
13014
13017
  ...
13015
13018
  def fs_mkdir_app(
13016
13019
  self,
13017
- payload: dict,
13020
+ payload: dict | str,
13018
13021
  /,
13022
+ pid: int = 0,
13019
13023
  app: str = "android",
13020
13024
  base_url: bool | str | Callable[[], str] = False,
13021
13025
  *,
13022
13026
  async_: Literal[False, True] = False,
13023
13027
  **request_kwargs,
13024
13028
  ) -> dict | Coroutine[Any, Any, dict]:
13025
- """新建目录
13026
-
13027
- .. todo::
13028
- 待破解
13029
-
13030
- - name: str 💡 目录名
13029
+ """新建目录,此接口是对 `fs_folder_update_app` 的封装
13031
13030
 
13032
- POST https://proapi.115.com/android/1.0/folder/update
13031
+ :payload:
13032
+ - name: str 💡 名字
13033
+ - pid: int = 0 💡 上级目录的 id
13033
13034
  """
13034
- api = complete_proapi("/folder/update", base_url, app)
13035
- payload = dict(payload, user_id=self.user_id)
13036
- return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
13035
+ if isinstance(payload, str):
13036
+ payload = {"pid": pid, "name": payload}
13037
+ else:
13038
+ payload = {"pid": pid, **payload}
13039
+ return self.fs_folder_update_app(
13040
+ payload,
13041
+ app=app,
13042
+ base_url=base_url,
13043
+ async_=async_,
13044
+ **request_kwargs,
13045
+ )
13037
13046
 
13038
13047
  @overload
13039
13048
  def fs_move(
@@ -14612,7 +14621,11 @@ class P115Client(P115OpenClient):
14612
14621
  GET https://webapi.115.com/files/search
14613
14622
 
14614
14623
  .. attention::
14615
- 最多只能取回前 10,000 条数据,也就是 limit + offset <= 10_000
14624
+ 最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
14625
+
14626
+ 不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
14627
+
14628
+ 它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
14616
14629
 
14617
14630
  这个接口实际上不支持在查询中直接设置排序,只能由 `P115Client.fs_order_set` 设置
14618
14631
 
@@ -14709,12 +14722,16 @@ class P115Client(P115OpenClient):
14709
14722
  async_: Literal[False, True] = False,
14710
14723
  **request_kwargs,
14711
14724
  ) -> dict | Coroutine[Any, Any, dict]:
14712
- """搜索文件或目录(提示:好像最多只能罗列前 10,000 条数据,也就是 limit + offset <= 10_000)
14725
+ """搜索文件或目录
14713
14726
 
14714
14727
  GET https://proapi.115.com/android/files/search
14715
14728
 
14716
14729
  .. attention::
14717
- 最多只能取回前 10,000 条数据,也就是 limit + offset <= 10_000
14730
+ 最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
14731
+
14732
+ 不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
14733
+
14734
+ 它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
14718
14735
 
14719
14736
  :payload:
14720
14737
  - aid: int | str = 1 💡 area_id。1:正常文件 7:回收站文件 12:瞬间文件 120:彻底删除文件、简历附件
@@ -14804,12 +14821,16 @@ class P115Client(P115OpenClient):
14804
14821
  async_: Literal[False, True] = False,
14805
14822
  **request_kwargs,
14806
14823
  ) -> dict | Coroutine[Any, Any, dict]:
14807
- """搜索文件或目录(提示:好像最多只能罗列前 10,000 条数据,也就是 limit + offset <= 10_000)
14824
+ """搜索文件或目录
14808
14825
 
14809
14826
  GET https://proapi.115.com/android/2.0/ufile/search
14810
14827
 
14811
14828
  .. attention::
14812
- 最多只能取回前 10,000 条数据,也就是 limit + offset <= 10_000
14829
+ 最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
14830
+
14831
+ 不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
14832
+
14833
+ 它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
14813
14834
 
14814
14835
  :payload:
14815
14836
  - aid: int | str = 1 💡 area_id。1:正常文件 7:回收站文件 12:瞬间文件 120:彻底删除文件、简历附件
@@ -15398,6 +15419,57 @@ class P115Client(P115OpenClient):
15398
15419
  payload = {"sys_dir": payload}
15399
15420
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
15400
15421
 
15422
+ @overload
15423
+ def fs_top_set(
15424
+ self,
15425
+ payload: int | str | Iterable[int | str] | dict,
15426
+ /,
15427
+ top: bool = True,
15428
+ base_url: bool | str | Callable[[], str] = False,
15429
+ *,
15430
+ async_: Literal[False] = False,
15431
+ **request_kwargs,
15432
+ ) -> dict:
15433
+ ...
15434
+ @overload
15435
+ def fs_top_set(
15436
+ self,
15437
+ payload: int | str | Iterable[int | str] | dict,
15438
+ /,
15439
+ top: bool = True,
15440
+ base_url: bool | str | Callable[[], str] = False,
15441
+ *,
15442
+ async_: Literal[True],
15443
+ **request_kwargs,
15444
+ ) -> Coroutine[Any, Any, dict]:
15445
+ ...
15446
+ def fs_top_set(
15447
+ self,
15448
+ payload: int | str | Iterable[int | str] | dict,
15449
+ /,
15450
+ top: bool = True,
15451
+ base_url: bool | str | Callable[[], str] = False,
15452
+ *,
15453
+ async_: Literal[False, True] = False,
15454
+ **request_kwargs,
15455
+ ) -> dict | Coroutine[Any, Any, dict]:
15456
+ """文件或目录置顶
15457
+
15458
+ POST https://webapi.115.com/files/top
15459
+
15460
+ :payload:
15461
+ - file_id: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
15462
+ - top: 0 | 1 = 1
15463
+ """
15464
+ api = complete_webapi("/files/top", base_url=base_url)
15465
+ if isinstance(payload, (int, str)):
15466
+ payload = {"file_id": payload, "top": int(top)}
15467
+ elif not isinstance(payload, dict):
15468
+ payload = {"file_id": ",".join(map(str, payload)), "top": int(top)}
15469
+ else:
15470
+ payload = {"top": int(top), **payload}
15471
+ return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
15472
+
15401
15473
  @overload
15402
15474
  def fs_video(
15403
15475
  self,
@@ -19347,11 +19419,11 @@ class P115Client(P115OpenClient):
19347
19419
  ) -> dict | Coroutine[Any, Any, dict]:
19348
19420
  """获取分享链接的某个目录中可下载的文件的列表(只含文件,不含目录,任意深度,简略信息)
19349
19421
 
19422
+ GET https://webapi.115.com/share/downlist
19423
+
19350
19424
  .. attention::
19351
19425
  cid 不能为 0
19352
19426
 
19353
- GET https://webapi.115.com/share/downlist
19354
-
19355
19427
  :payload:
19356
19428
  - share_code: str
19357
19429
  - receive_code: str
@@ -19396,11 +19468,11 @@ class P115Client(P115OpenClient):
19396
19468
  ) -> dict | Coroutine[Any, Any, dict]:
19397
19469
  """获取分享链接的某个目录中可下载的文件的列表(只含文件,不含目录,任意深度,简略信息)
19398
19470
 
19471
+ GET https://proapi.115.com/app/share/downlist
19472
+
19399
19473
  .. attention::
19400
19474
  cid 不能为 0
19401
19475
 
19402
- GET https://proapi.115.com/app/share/downlist
19403
-
19404
19476
  :payload:
19405
19477
  - share_code: str
19406
19478
  - receive_code: str
@@ -19502,7 +19574,7 @@ class P115Client(P115OpenClient):
19502
19574
  name=info["fn"],
19503
19575
  size=int(info["fs"]),
19504
19576
  sha1=info.get("sha1", ""),
19505
- is_directory=not url,
19577
+ is_dir=not url,
19506
19578
  )
19507
19579
  if async_:
19508
19580
  async def async_request() -> P115URL:
@@ -19834,7 +19906,7 @@ class P115Client(P115OpenClient):
19834
19906
  - share_code: str
19835
19907
  - receive_code: str
19836
19908
  - file_id: int | str 💡 有多个时,用逗号 "," 分隔
19837
- - cid: int | str = <default> 💡 这是你网盘的目录 cid
19909
+ - cid: int | str = <default> 💡 这是你网盘的目录 cid,如果不指定则用默认
19838
19910
  - is_check: 0 | 1 = <default>
19839
19911
  """
19840
19912
  api = complete_webapi("/share/receive", base_url=base_url)
@@ -19882,7 +19954,7 @@ class P115Client(P115OpenClient):
19882
19954
  - share_code: str
19883
19955
  - receive_code: str
19884
19956
  - file_id: int | str 💡 有多个时,用逗号 "," 分隔
19885
- - cid: int | str = <default> 💡 这是你网盘的目录 cid
19957
+ - cid: int | str = <default> 💡 这是你网盘的目录 cid,如果不指定则用默认
19886
19958
  - is_check: 0 | 1 = <default>
19887
19959
  """
19888
19960
  api = complete_proapi("/2.0/share/receive", base_url, app)
@@ -20039,7 +20111,7 @@ class P115Client(P115OpenClient):
20039
20111
  GET https://webapi.115.com/share/search
20040
20112
 
20041
20113
  .. attention::
20042
- 最多只能取回前 10,000 条数据,也就是 limit + offset <= 10_000
20114
+ 最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
20043
20115
 
20044
20116
  :payload:
20045
20117
  - share_code: str 💡 分享码
@@ -20254,7 +20326,7 @@ class P115Client(P115OpenClient):
20254
20326
  name=info["fn"],
20255
20327
  size=int(info["fs"]),
20256
20328
  sha1=info.get("sha1", ""),
20257
- is_directory=not url,
20329
+ is_dir=not url,
20258
20330
  )
20259
20331
  if async_:
20260
20332
  async def async_request() -> P115URL: