p115client 0.0.5.11.8.2__tar.gz → 0.0.5.11.9__tar.gz

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.
Files changed (26) hide show
  1. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/PKG-INFO +1 -1
  2. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/_upload.py +52 -27
  3. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/client.py +60 -26
  4. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/__init__.py +1 -0
  5. p115client-0.0.5.11.9/p115client/tool/auth.py +62 -0
  6. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/upload.py +406 -4
  7. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/util.py +23 -1
  8. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/pyproject.toml +1 -1
  9. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/LICENSE +0 -0
  10. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/__init__.py +0 -0
  11. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/const.py +0 -0
  12. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/exception.py +0 -0
  13. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/py.typed +0 -0
  14. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/attr.py +0 -0
  15. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/download.py +0 -0
  16. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/edit.py +0 -0
  17. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/export_dir.py +0 -0
  18. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/fs_files.py +0 -0
  19. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/history.py +0 -0
  20. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/iterdir.py +0 -0
  21. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/life.py +0 -0
  22. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/pool.py +0 -0
  23. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/request.py +0 -0
  24. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/tool/xys.py +0 -0
  25. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/p115client/type.py +0 -0
  26. {p115client-0.0.5.11.8.2 → p115client-0.0.5.11.9}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: p115client
3
- Version: 0.0.5.11.8.2
3
+ Version: 0.0.5.11.9
4
4
  Summary: Python 115 webdisk client.
5
5
  Home-page: https://github.com/ChenyangGao/p115client
6
6
  License: MIT
@@ -3,19 +3,19 @@
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
  __all__ = [
6
- "make_dataiter", "oss_upload_sign", "oss_upload_request", "oss_multipart_upload_init",
7
- "oss_multipart_upload_complete", "oss_multipart_upload_cancel", "oss_multipart_upload_part",
8
- "oss_multipart_upload_part_iter", "oss_multipart_part_iter", "oss_upload", "oss_multipart_upload",
6
+ "make_dataiter", "oss_upload_sign", "oss_multipart_upload_url", "oss_upload_request",
7
+ "oss_multipart_upload_init", "oss_multipart_upload_complete", "oss_multipart_upload_cancel",
8
+ "oss_multipart_upload_part", "oss_multipart_upload_part_iter", "oss_multipart_part_iter",
9
+ "oss_upload", "oss_multipart_upload",
9
10
  ]
10
11
 
11
12
  from base64 import b64encode
12
13
  from collections.abc import (
13
14
  AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Buffer, Callable,
14
- Coroutine, Generator, ItemsView, Iterable, Iterator, Mapping, Sequence, Sized,
15
+ Coroutine, Generator, ItemsView, Iterable, Iterator, Mapping, Sequence,
15
16
  )
16
17
  from datetime import datetime
17
18
  from email.utils import formatdate
18
- from functools import partial
19
19
  from hmac import digest as hmac_digest
20
20
  from inspect import iscoroutinefunction
21
21
  from itertools import count
@@ -135,8 +135,17 @@ def oss_upload_sign(
135
135
  method: str = "PUT",
136
136
  params: None | str | Mapping | Sequence[tuple[Any, Any]] = None,
137
137
  headers: None | Mapping[str, str] | Iterable[tuple[str, str]] = None,
138
- ) -> tuple[dict, str]:
138
+ ) -> tuple[str, dict]:
139
139
  """计算认证信息,返回带认证信息的请求头
140
+
141
+ :param bucket: 存储桶
142
+ :param object: 对象 id
143
+ :param token: 令牌
144
+ :param method: HTTP 请求方法
145
+ :param params: 其它查询参数
146
+ :param headers: 默认的请求头
147
+
148
+ :return: 构造后的 查询参数 和 请求头 的 2 元组
140
149
  """
141
150
  # subresource_keys = (
142
151
  # "accessPoint", "accessPointPolicy", "acl", "append", "asyncFetch", "bucketArchiveDirectRead",
@@ -188,7 +197,42 @@ def oss_upload_sign(
188
197
  /{bucket}/{object}{params}""".encode("utf-8")
189
198
  signature = to_base64(hmac_digest(bytes(token["AccessKeySecret"], "utf-8"), signature_data, "sha1"))
190
199
  headers["authorization"] = "OSS {0}:{1}".format(token["AccessKeyId"], signature)
191
- return headers, params
200
+ return params, headers
201
+
202
+
203
+ def oss_multipart_upload_url(
204
+ bucket: str,
205
+ object: str,
206
+ token: dict,
207
+ upload_id: int | str,
208
+ part_number: int = 1,
209
+ domain: str = "oss-cn-shenzhen.aliyuncs.com",
210
+ ) -> tuple[str, dict]:
211
+ """构建分块上传的链接和请求头
212
+
213
+ :param bucket: 存储桶
214
+ :param object: 对象 id
215
+ :param token: 令牌
216
+ :param upload_id: 上传 id
217
+ :param domain: 所用的 阿里云网址 或 包含存储桶和对象的完整网址
218
+
219
+ :return: 构造后的 上传链接 和 请求头 的 2 元组
220
+ """
221
+ params, headers = oss_upload_sign(
222
+ bucket,
223
+ object,
224
+ token,
225
+ method="PUT",
226
+ params={"partNumber": part_number, "uploadId": upload_id},
227
+ headers={"x-oss-security-token": token["SecurityToken"]},
228
+ )
229
+ if domain.startswith(("http://", "https://")):
230
+ url = domain.rstrip("/")
231
+ elif domain:
232
+ url = f"http://{bucket}.{domain}/{object}"
233
+ else:
234
+ url = ""
235
+ return url+params, headers
192
236
 
193
237
 
194
238
  def oss_upload_request[T](
@@ -206,7 +250,7 @@ def oss_upload_request[T](
206
250
  ) -> T:
207
251
  """请求阿里云 OSS 的公用函数
208
252
  """
209
- headers, params = oss_upload_sign(
253
+ params, headers = oss_upload_sign(
210
254
  bucket,
211
255
  object,
212
256
  token,
@@ -929,22 +973,3 @@ def oss_multipart_upload(
929
973
  yield close_reporthook()
930
974
  return run_gen_step(gen_step, may_call=False, async_=async_)
931
975
 
932
-
933
- # class MultipartUploader:
934
- # def __init__
935
- # def __del__
936
- # async def __aiter__
937
- # def __iter__
938
- # async def __aenter__
939
- # async def __aexit__
940
- # def __enter__
941
- # def __exit__
942
- # # 0. 应该设计 1 个类,支持同步和异步,实例化不会进行初始化(为了对异步进行适配)
943
- # # 1. 可以作为上下文管理器或者迭代器使用
944
- # # 2. 上下文管理器也返回迭代器(迭代器迭代时,如果未打开文件或者没有上传信息,则会初始化以获取)
945
- # # 3. 中途可以暂停或取消
946
- # # 4. seekable: path, url (支持 range request), file reader (seekable)
947
- # # 5. 支持进度条
948
- # # 6. 设计一个工具函数,放到 p115client.tool.upload 模块中
949
- # ...
950
-
@@ -3274,7 +3274,6 @@ class P115OpenClient(ClientRequestMixin):
3274
3274
  api = complete_proapi("/open/ufile/downurl", base_url)
3275
3275
  if isinstance(payload, str):
3276
3276
  payload = {"pick_code": payload}
3277
- request_headers = request_kwargs.get("headers")
3278
3277
  headers = request_kwargs.get("headers")
3279
3278
  if headers:
3280
3279
  if isinstance(headers, Mapping):
@@ -3462,7 +3461,7 @@ class P115OpenClient(ClientRequestMixin):
3462
3461
  - fields: str = <default>
3463
3462
  - for: str = <default> 💡 文件格式,例如 "doc"
3464
3463
  - format: str = "json" 💡 返回格式,默认即可
3465
- - hide_data: str = <default>
3464
+ - hide_data: str = <default> 💡 是否返回文件数据
3466
3465
  - is_q: 0 | 1 = <default>
3467
3466
  - is_share: 0 | 1 = <default>
3468
3467
  - min_size: int = 0 💡 最小的文件大小
@@ -3771,8 +3770,8 @@ class P115OpenClient(ClientRequestMixin):
3771
3770
  - search_value: str = "." 💡 搜索文本,可以是 sha1
3772
3771
  - show_dir: 0 | 1 = 1 💡 是否显示目录
3773
3772
  - source: str = <default>
3774
- - star: 0 | 1 = <default>
3775
- - suffix: str = <default>
3773
+ - star: 0 | 1 = <default> 💡 是否星标文件
3774
+ - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
3776
3775
  - type: int = <default> 💡 文件类型
3777
3776
 
3778
3777
  - 0: 全部(仅当前目录)
@@ -6173,6 +6172,8 @@ class P115Client(P115OpenClient):
6173
6172
 
6174
6173
  但一个设备的新登录者,并不总是意味着把较早的登录者下线,一般需要触发某个检查机制后,才会把同一设备下除最近一次登录外的所有 cookies 失效
6175
6174
 
6175
+ 所以你可以用一个设备的 cookies 专门用于扫码登录,获取另一个设备的 cookies 执行网盘操作,第 2 个 cookies 失效了,则用第 1 个 cookies 扫码,如此可避免单个 cookies 失效后,不能自动获取新的
6176
+
6176
6177
  :param app: 要登录的 app,如果为 None,则用当前登录设备,如果无当前登录设备,则报错
6177
6178
  :param replace: 替换某个 client 对象的 cookie
6178
6179
 
@@ -6860,6 +6861,7 @@ class P115Client(P115OpenClient):
6860
6861
  self,
6861
6862
  payload: dict,
6862
6863
  /,
6864
+ app: str = "web",
6863
6865
  base_url: bool | str | Callable[[], str] = False,
6864
6866
  *,
6865
6867
  async_: Literal[False] = False,
@@ -6871,6 +6873,7 @@ class P115Client(P115OpenClient):
6871
6873
  self,
6872
6874
  payload: dict,
6873
6875
  /,
6876
+ app: str = "web",
6874
6877
  base_url: bool | str | Callable[[], str] = False,
6875
6878
  *,
6876
6879
  async_: Literal[True],
@@ -6881,6 +6884,7 @@ class P115Client(P115OpenClient):
6881
6884
  self,
6882
6885
  payload: dict,
6883
6886
  /,
6887
+ app: str = "web",
6884
6888
  base_url: bool | str | Callable[[], str] = False,
6885
6889
  *,
6886
6890
  async_: Literal[False, True] = False,
@@ -6895,7 +6899,7 @@ class P115Client(P115OpenClient):
6895
6899
  - aid: int | str 💡 助愿的 id
6896
6900
  - to_cid: int = <default> 💡 助愿中的分享链接转存到你的网盘中目录的 id
6897
6901
  """
6898
- api = complete_api("/api/1.0/web/1.0/act2024xys/adopt", "act", base_url=base_url)
6902
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/adopt", "act", base_url=base_url)
6899
6903
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
6900
6904
 
6901
6905
  @overload
@@ -6903,6 +6907,7 @@ class P115Client(P115OpenClient):
6903
6907
  self,
6904
6908
  payload: dict,
6905
6909
  /,
6910
+ app: str = "web",
6906
6911
  base_url: bool | str | Callable[[], str] = False,
6907
6912
  *,
6908
6913
  async_: Literal[False] = False,
@@ -6914,6 +6919,7 @@ class P115Client(P115OpenClient):
6914
6919
  self,
6915
6920
  payload: dict,
6916
6921
  /,
6922
+ app: str = "web",
6917
6923
  base_url: bool | str | Callable[[], str] = False,
6918
6924
  *,
6919
6925
  async_: Literal[True],
@@ -6924,6 +6930,7 @@ class P115Client(P115OpenClient):
6924
6930
  self,
6925
6931
  payload: dict,
6926
6932
  /,
6933
+ app: str = "web",
6927
6934
  base_url: bool | str | Callable[[], str] = False,
6928
6935
  *,
6929
6936
  async_: Literal[False, True] = False,
@@ -6939,7 +6946,7 @@ class P115Client(P115OpenClient):
6939
6946
  - images: int | str = <default> 💡 图片文件在你的网盘的 id,多个用逗号 "," 隔开
6940
6947
  - file_ids: int | str = <default> 💡 文件在你的网盘的 id,多个用逗号 "," 隔开
6941
6948
  """
6942
- api = complete_api("/api/1.0/web/1.0/act2024xys/aid_desire", "act", base_url=base_url)
6949
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/aid_desire", "act", base_url=base_url)
6943
6950
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
6944
6951
 
6945
6952
  @overload
@@ -6947,6 +6954,7 @@ class P115Client(P115OpenClient):
6947
6954
  self,
6948
6955
  payload: int | str | dict,
6949
6956
  /,
6957
+ app: str = "web",
6950
6958
  base_url: bool | str | Callable[[], str] = False,
6951
6959
  *,
6952
6960
  async_: Literal[False] = False,
@@ -6958,6 +6966,7 @@ class P115Client(P115OpenClient):
6958
6966
  self,
6959
6967
  payload: int | str | dict,
6960
6968
  /,
6969
+ app: str = "web",
6961
6970
  base_url: bool | str | Callable[[], str] = False,
6962
6971
  *,
6963
6972
  async_: Literal[True],
@@ -6968,6 +6977,7 @@ class P115Client(P115OpenClient):
6968
6977
  self,
6969
6978
  payload: int | str | dict,
6970
6979
  /,
6980
+ app: str = "web",
6971
6981
  base_url: bool | str | Callable[[], str] = False,
6972
6982
  *,
6973
6983
  async_: Literal[False, True] = False,
@@ -6980,7 +6990,7 @@ class P115Client(P115OpenClient):
6980
6990
  :payload:
6981
6991
  - ids: int | str 💡 助愿的 id,多个用逗号 "," 隔开
6982
6992
  """
6983
- api = complete_api("/api/1.0/web/1.0/act2024xys/del_aid_desire", "act", base_url=base_url)
6993
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/del_aid_desire", "act", base_url=base_url)
6984
6994
  if isinstance(payload, (int, str)):
6985
6995
  payload = {"ids": payload}
6986
6996
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -6990,6 +7000,7 @@ class P115Client(P115OpenClient):
6990
7000
  self,
6991
7001
  payload: str | dict,
6992
7002
  /,
7003
+ app: str = "web",
6993
7004
  base_url: bool | str | Callable[[], str] = False,
6994
7005
  *,
6995
7006
  async_: Literal[False] = False,
@@ -7001,6 +7012,7 @@ class P115Client(P115OpenClient):
7001
7012
  self,
7002
7013
  payload: str | dict,
7003
7014
  /,
7015
+ app: str = "web",
7004
7016
  base_url: bool | str | Callable[[], str] = False,
7005
7017
  *,
7006
7018
  async_: Literal[True],
@@ -7011,6 +7023,7 @@ class P115Client(P115OpenClient):
7011
7023
  self,
7012
7024
  payload: str | dict,
7013
7025
  /,
7026
+ app: str = "web",
7014
7027
  base_url: bool | str | Callable[[], str] = False,
7015
7028
  *,
7016
7029
  async_: Literal[False, True] = False,
@@ -7027,7 +7040,7 @@ class P115Client(P115OpenClient):
7027
7040
  - limit: int = 10 💡 分页大小
7028
7041
  - sort: int | str = <default> 💡 排序
7029
7042
  """
7030
- api = complete_api("/api/1.0/web/1.0/act2024xys/desire_aid_list", "act", base_url=base_url)
7043
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/desire_aid_list", "act", base_url=base_url)
7031
7044
  if isinstance(payload, str):
7032
7045
  payload = {"start": 0, "page": 1, "limit": 10, "id": payload}
7033
7046
  else:
@@ -7038,6 +7051,7 @@ class P115Client(P115OpenClient):
7038
7051
  def act_xys_get_act_info(
7039
7052
  self,
7040
7053
  /,
7054
+ app: str = "web",
7041
7055
  base_url: bool | str | Callable[[], str] = False,
7042
7056
  *,
7043
7057
  async_: Literal[False] = False,
@@ -7048,6 +7062,7 @@ class P115Client(P115OpenClient):
7048
7062
  def act_xys_get_act_info(
7049
7063
  self,
7050
7064
  /,
7065
+ app: str = "web",
7051
7066
  base_url: bool | str | Callable[[], str] = False,
7052
7067
  *,
7053
7068
  async_: Literal[True],
@@ -7057,6 +7072,7 @@ class P115Client(P115OpenClient):
7057
7072
  def act_xys_get_act_info(
7058
7073
  self,
7059
7074
  /,
7075
+ app: str = "web",
7060
7076
  base_url: bool | str | Callable[[], str] = False,
7061
7077
  *,
7062
7078
  async_: Literal[False, True] = False,
@@ -7066,7 +7082,7 @@ class P115Client(P115OpenClient):
7066
7082
 
7067
7083
  GET https://act.115.com/api/1.0/web/1.0/act2024xys/get_act_info
7068
7084
  """
7069
- api = complete_api("/api/1.0/web/1.0/act2024xys/get_act_info", "act", base_url=base_url)
7085
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/get_act_info", "act", base_url=base_url)
7070
7086
  return self.request(url=api, async_=async_, **request_kwargs)
7071
7087
 
7072
7088
  @overload
@@ -7074,6 +7090,7 @@ class P115Client(P115OpenClient):
7074
7090
  self,
7075
7091
  payload: str | dict,
7076
7092
  /,
7093
+ app: str = "web",
7077
7094
  base_url: bool | str | Callable[[], str] = False,
7078
7095
  *,
7079
7096
  async_: Literal[False] = False,
@@ -7085,6 +7102,7 @@ class P115Client(P115OpenClient):
7085
7102
  self,
7086
7103
  payload: str | dict,
7087
7104
  /,
7105
+ app: str = "web",
7088
7106
  base_url: bool | str | Callable[[], str] = False,
7089
7107
  *,
7090
7108
  async_: Literal[True],
@@ -7095,6 +7113,7 @@ class P115Client(P115OpenClient):
7095
7113
  self,
7096
7114
  payload: str | dict,
7097
7115
  /,
7116
+ app: str = "web",
7098
7117
  base_url: bool | str | Callable[[], str] = False,
7099
7118
  *,
7100
7119
  async_: Literal[False, True] = False,
@@ -7107,7 +7126,7 @@ class P115Client(P115OpenClient):
7107
7126
  :payload:
7108
7127
  - id: str 💡 许愿的 id
7109
7128
  """
7110
- api = complete_api("/api/1.0/web/1.0/act2024xys/get_desire_info", "act", base_url=base_url)
7129
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/get_desire_info", "act", base_url=base_url)
7111
7130
  if isinstance(payload, str):
7112
7131
  payload = {"id": payload}
7113
7132
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@@ -7116,6 +7135,7 @@ class P115Client(P115OpenClient):
7116
7135
  def act_xys_home_list(
7117
7136
  self,
7118
7137
  /,
7138
+ app: str = "web",
7119
7139
  base_url: bool | str | Callable[[], str] = False,
7120
7140
  *,
7121
7141
  async_: Literal[False] = False,
@@ -7126,6 +7146,7 @@ class P115Client(P115OpenClient):
7126
7146
  def act_xys_home_list(
7127
7147
  self,
7128
7148
  /,
7149
+ app: str = "web",
7129
7150
  base_url: bool | str | Callable[[], str] = False,
7130
7151
  *,
7131
7152
  async_: Literal[True],
@@ -7135,6 +7156,7 @@ class P115Client(P115OpenClient):
7135
7156
  def act_xys_home_list(
7136
7157
  self,
7137
7158
  /,
7159
+ app: str = "web",
7138
7160
  base_url: bool | str | Callable[[], str] = False,
7139
7161
  *,
7140
7162
  async_: Literal[False, True] = False,
@@ -7144,7 +7166,7 @@ class P115Client(P115OpenClient):
7144
7166
 
7145
7167
  GET https://act.115.com/api/1.0/web/1.0/act2024xys/home_list
7146
7168
  """
7147
- api = complete_api("/api/1.0/web/1.0/act2024xys/home_list", "act", base_url=base_url)
7169
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/home_list", "act", base_url=base_url)
7148
7170
  return self.request(url=api, async_=async_, **request_kwargs)
7149
7171
 
7150
7172
  @overload
@@ -7152,6 +7174,7 @@ class P115Client(P115OpenClient):
7152
7174
  self,
7153
7175
  payload: int | str | dict = 0,
7154
7176
  /,
7177
+ app: str = "web",
7155
7178
  base_url: bool | str | Callable[[], str] = False,
7156
7179
  *,
7157
7180
  async_: Literal[False] = False,
@@ -7163,6 +7186,7 @@ class P115Client(P115OpenClient):
7163
7186
  self,
7164
7187
  payload: int | str | dict = 0,
7165
7188
  /,
7189
+ app: str = "web",
7166
7190
  base_url: bool | str | Callable[[], str] = False,
7167
7191
  *,
7168
7192
  async_: Literal[True],
@@ -7173,6 +7197,7 @@ class P115Client(P115OpenClient):
7173
7197
  self,
7174
7198
  payload: int | str | dict = 0,
7175
7199
  /,
7200
+ app: str = "web",
7176
7201
  base_url: bool | str | Callable[[], str] = False,
7177
7202
  *,
7178
7203
  async_: Literal[False, True] = False,
@@ -7193,7 +7218,7 @@ class P115Client(P115OpenClient):
7193
7218
  - page: int = 1 💡 第几页
7194
7219
  - limit: int = 10 💡 分页大小
7195
7220
  """
7196
- api = complete_api("/api/1.0/web/1.0/act2024xys/my_aid_desire", "act", base_url=base_url)
7221
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/my_aid_desire", "act", base_url=base_url)
7197
7222
  if isinstance(payload, (int, str)):
7198
7223
  payload = {"start": 0, "page": 1, "limit": 10, "type": payload}
7199
7224
  else:
@@ -7205,6 +7230,7 @@ class P115Client(P115OpenClient):
7205
7230
  self,
7206
7231
  payload: int | str | dict = 0,
7207
7232
  /,
7233
+ app: str = "web",
7208
7234
  base_url: bool | str | Callable[[], str] = False,
7209
7235
  *,
7210
7236
  async_: Literal[False] = False,
@@ -7216,6 +7242,7 @@ class P115Client(P115OpenClient):
7216
7242
  self,
7217
7243
  payload: int | str | dict = 0,
7218
7244
  /,
7245
+ app: str = "web",
7219
7246
  base_url: bool | str | Callable[[], str] = False,
7220
7247
  *,
7221
7248
  async_: Literal[True],
@@ -7226,6 +7253,7 @@ class P115Client(P115OpenClient):
7226
7253
  self,
7227
7254
  payload: int | str | dict = 0,
7228
7255
  /,
7256
+ app: str = "web",
7229
7257
  base_url: bool | str | Callable[[], str] = False,
7230
7258
  *,
7231
7259
  async_: Literal[False, True] = False,
@@ -7246,7 +7274,7 @@ class P115Client(P115OpenClient):
7246
7274
  - page: int = 1 💡 第几页
7247
7275
  - limit: int = 10 💡 分页大小
7248
7276
  """
7249
- api = complete_api("/api/1.0/web/1.0/act2024xys/my_desire", "act", base_url=base_url)
7277
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/my_desire", "act", base_url=base_url)
7250
7278
  if isinstance(payload, (int, str)):
7251
7279
  payload = {"start": 0, "page": 1, "limit": 10, "type": payload}
7252
7280
  else:
@@ -7258,6 +7286,7 @@ class P115Client(P115OpenClient):
7258
7286
  self,
7259
7287
  payload: str | dict,
7260
7288
  /,
7289
+ app: str = "web",
7261
7290
  base_url: bool | str | Callable[[], str] = False,
7262
7291
  *,
7263
7292
  async_: Literal[False] = False,
@@ -7269,6 +7298,7 @@ class P115Client(P115OpenClient):
7269
7298
  self,
7270
7299
  payload: str | dict,
7271
7300
  /,
7301
+ app: str = "web",
7272
7302
  base_url: bool | str | Callable[[], str] = False,
7273
7303
  *,
7274
7304
  async_: Literal[True],
@@ -7279,6 +7309,7 @@ class P115Client(P115OpenClient):
7279
7309
  self,
7280
7310
  payload: str | dict,
7281
7311
  /,
7312
+ app: str = "web",
7282
7313
  base_url: bool | str | Callable[[], str] = False,
7283
7314
  *,
7284
7315
  async_: Literal[False, True] = False,
@@ -7293,7 +7324,7 @@ class P115Client(P115OpenClient):
7293
7324
  - rewardSpace: int = 5 💡 奖励容量,单位是 GB
7294
7325
  - images: int | str = <default> 💡 图片文件在你的网盘的 id,多个用逗号 "," 隔开
7295
7326
  """
7296
- api = complete_api("/api/1.0/web/1.0/act2024xys/wish", "act", base_url=base_url)
7327
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/wish", "act", base_url=base_url)
7297
7328
  if isinstance(payload, str):
7298
7329
  payload = {"rewardSpace": 5, "content": payload}
7299
7330
  else:
@@ -7305,6 +7336,7 @@ class P115Client(P115OpenClient):
7305
7336
  self,
7306
7337
  payload: str | dict,
7307
7338
  /,
7339
+ app: str = "web",
7308
7340
  base_url: bool | str | Callable[[], str] = False,
7309
7341
  *,
7310
7342
  async_: Literal[False] = False,
@@ -7316,6 +7348,7 @@ class P115Client(P115OpenClient):
7316
7348
  self,
7317
7349
  payload: str | dict,
7318
7350
  /,
7351
+ app: str = "web",
7319
7352
  base_url: bool | str | Callable[[], str] = False,
7320
7353
  *,
7321
7354
  async_: Literal[True],
@@ -7326,6 +7359,7 @@ class P115Client(P115OpenClient):
7326
7359
  self,
7327
7360
  payload: str | dict,
7328
7361
  /,
7362
+ app: str = "web",
7329
7363
  base_url: bool | str | Callable[[], str] = False,
7330
7364
  *,
7331
7365
  async_: Literal[False, True] = False,
@@ -7338,7 +7372,7 @@ class P115Client(P115OpenClient):
7338
7372
  :payload:
7339
7373
  - ids: str 💡 许愿的 id,多个用逗号 "," 隔开
7340
7374
  """
7341
- api = complete_api("/api/1.0/web/1.0/act2024xys/del_wish", "act", base_url=base_url)
7375
+ api = complete_api(f"/api/1.0/{app}/1.0/act2024xys/del_wish", "act", base_url=base_url)
7342
7376
  if isinstance(payload, str):
7343
7377
  payload = {"ids": payload}
7344
7378
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -7954,7 +7988,6 @@ class P115Client(P115OpenClient):
7954
7988
  payload = {"pick_code": payload}
7955
7989
  else:
7956
7990
  payload = {"pick_code": payload["pickcode"]}
7957
- request_headers = request_kwargs.get("headers")
7958
7991
  headers = request_kwargs.get("headers")
7959
7992
  if headers:
7960
7993
  if isinstance(headers, Mapping):
@@ -8215,7 +8248,6 @@ class P115Client(P115OpenClient):
8215
8248
  - full_name: str
8216
8249
  """
8217
8250
  api = complete_proapi("/2.0/ufile/extract_down_file", base_url, app)
8218
- request_headers = request_kwargs.get("headers")
8219
8251
  headers = request_kwargs.get("headers")
8220
8252
  if headers:
8221
8253
  if isinstance(headers, Mapping):
@@ -8271,7 +8303,6 @@ class P115Client(P115OpenClient):
8271
8303
  - full_name: str
8272
8304
  """
8273
8305
  api = complete_webapi("/files/extract_down_file", base_url=base_url)
8274
- request_headers = request_kwargs.get("headers")
8275
8306
  headers = request_kwargs.get("headers")
8276
8307
  if headers:
8277
8308
  if isinstance(headers, Mapping):
@@ -10515,7 +10546,7 @@ class P115Client(P115OpenClient):
10515
10546
  - fields: str = <default>
10516
10547
  - for: str = <default> 💡 文件格式,例如 "doc"
10517
10548
  - format: str = "json" 💡 返回格式,默认即可
10518
- - hide_data: str = <default>
10549
+ - hide_data: str = <default> 💡 是否返回文件数据
10519
10550
  - is_q: 0 | 1 = <default>
10520
10551
  - is_share: 0 | 1 = <default>
10521
10552
  - min_size: int = 0 💡 最小的文件大小
@@ -10645,7 +10676,7 @@ class P115Client(P115OpenClient):
10645
10676
  - fields: str = <default>
10646
10677
  - for: str = <default> 💡 文件格式,例如 "doc"
10647
10678
  - format: str = "json" 💡 返回格式,默认即可
10648
- - hide_data: str = <default>
10679
+ - hide_data: str = <default> 💡 是否返回文件数据
10649
10680
  - is_q: 0 | 1 = <default>
10650
10681
  - is_share: 0 | 1 = <default>
10651
10682
  - min_size: int = 0 💡 最小的文件大小
@@ -10774,7 +10805,7 @@ class P115Client(P115OpenClient):
10774
10805
  - fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
10775
10806
  - fields: str = <default>
10776
10807
  - format: str = "json" 💡 返回格式,默认即可
10777
- - hide_data: str = <default>
10808
+ - hide_data: str = <default> 💡 是否返回文件数据
10778
10809
  - is_asc: 0 | 1 = <default>
10779
10810
  - is_q: 0 | 1 = <default>
10780
10811
  - is_share: 0 | 1 = <default>
@@ -11729,7 +11760,7 @@ class P115Client(P115OpenClient):
11729
11760
  :payload:
11730
11761
  - offset: int = 0
11731
11762
  - limit: int = 1150
11732
- - played_end: 0 | 1 = <default>
11763
+ - played_end: 0 | 1 = <default> 💡 是否已经播放完
11733
11764
  - type: int = <default> 💡 类型(??表示还未搞清楚),多个用逗号 "," 隔开
11734
11765
 
11735
11766
  - 全部: 0
@@ -14604,8 +14635,8 @@ class P115Client(P115OpenClient):
14604
14635
  - search_value: str = "." 💡 搜索文本,可以是 sha1
14605
14636
  - show_dir: 0 | 1 = 1 💡 是否显示目录
14606
14637
  - source: str = <default>
14607
- - star: 0 | 1 = <default>
14608
- - suffix: str = <default>
14638
+ - star: 0 | 1 = <default> 💡 是否星标文件
14639
+ - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
14609
14640
  - type: int = <default> 💡 文件类型
14610
14641
 
14611
14642
  - 0: 全部(仅当前目录)
@@ -14701,8 +14732,8 @@ class P115Client(P115OpenClient):
14701
14732
  - search_value: str = "." 💡 搜索文本,可以是 sha1
14702
14733
  - show_dir: 0 | 1 = 1 💡 是否显示目录
14703
14734
  - source: str = <default>
14704
- - star: 0 | 1 = <default>
14705
- - suffix: str = <default>
14735
+ - star: 0 | 1 = <default> 💡 是否星标文件
14736
+ - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
14706
14737
  - type: int = <default> 💡 文件类型
14707
14738
 
14708
14739
  - 0: 全部(仅当前目录)
@@ -21144,6 +21175,9 @@ class P115Client(P115OpenClient):
21144
21175
  )
21145
21176
  return run_gen_step(gen_step, may_call=False, async_=async_)
21146
21177
 
21178
+ # TODO: 分块上传时,允许一定次数的重试
21179
+ # TODO: 不妨单独为分块上传做一个封装
21180
+ # TODO: 减少参数,简化使用方法
21147
21181
  @overload # type: ignore
21148
21182
  def upload_file(
21149
21183
  self,
@@ -4,6 +4,7 @@
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
 
6
6
  from .attr import *
7
+ from .auth import *
7
8
  from .download import *
8
9
  from .edit import *
9
10
  from .export_dir import *
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
+ __all__ = ["deauth_open"]
6
+ __doc__ = "这个模块提供了一些和账号状况有关的函数"
7
+
8
+ from collections.abc import Callable, Coroutine
9
+ from typing import overload, Any, Literal
10
+
11
+ from iterutils import run_gen_step
12
+ from p115client import check_response, P115Client
13
+
14
+
15
+ @overload
16
+ def deauth_open(
17
+ client: str | P115Client,
18
+ predicate: None | Callable = None,
19
+ *,
20
+ async_: Literal[False] = False,
21
+ **request_kwargs,
22
+ ) -> None:
23
+ ...
24
+ @overload
25
+ def deauth_open(
26
+ client: str | P115Client,
27
+ predicate: None | Callable = None,
28
+ *,
29
+ async_: Literal[True],
30
+ **request_kwargs,
31
+ ) -> Coroutine[Any, Any, None]:
32
+ ...
33
+ def deauth_open(
34
+ client: str | P115Client,
35
+ predicate: None | Callable = None,
36
+ *,
37
+ async_: Literal[False, True] = False,
38
+ **request_kwargs,
39
+ ) -> None | Coroutine[Any, Any, None]:
40
+ """批量解绑开放应用
41
+
42
+ :param client: 115 客户端或 cookies
43
+ :param predicate: 筛选条件
44
+ :param async_: 是否异步
45
+ :param request_kwargs: 其它请求参数
46
+ """
47
+ if isinstance(client, str):
48
+ client = P115Client(client, check_for_relogin=True)
49
+ def gen_step():
50
+ resp = yield client.login_open_auth_list(
51
+ async_=async_,
52
+ **request_kwargs,
53
+ )
54
+ check_response(resp)
55
+ for info in filter(predicate, resp["data"]):
56
+ yield client.login_open_deauth(
57
+ info["auth_id"],
58
+ async_=async_,
59
+ **request_kwargs,
60
+ )
61
+ return run_gen_step(gen_step, async_=async_)
62
+
@@ -2,20 +2,47 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __all__ = ["iter_115_to_115", "iter_115_to_115_resume"]
5
+ __all__ = [
6
+ "iter_115_to_115", "iter_115_to_115_resume", "multipart_upload_init",
7
+ "multipart_upload_url", "multipart_upload_complete",
8
+ ]
6
9
  __doc__ = "这个模块提供了一些和上传有关的函数"
7
10
 
8
- from collections.abc import AsyncIterator, Iterator
11
+ import errno
12
+
13
+ from asyncio import to_thread
14
+ from collections.abc import AsyncIterator, Callable, Coroutine, Iterator
9
15
  from itertools import dropwhile
10
- from typing import overload, Any, Literal
16
+ from os import fsdecode, stat, PathLike
17
+ from typing import cast, overload, Any, Literal
18
+ from urllib.parse import unquote, urlsplit
19
+ from uuid import uuid4
11
20
 
12
21
  from asynctools import to_list
13
22
  from concurrenttools import threadpool_map, taskgroup_map, Return
14
- from iterutils import as_gen_step, run_gen_step_iter, with_iter_next, YieldFrom
23
+ from hashtools import file_digest, file_digest_async
24
+ from http_request import SupportsGeturl
25
+ from http_response import get_total_length
26
+ from iterutils import (
27
+ as_gen_step, collect, run_gen_step, run_gen_step_iter,
28
+ with_iter_next, YieldFrom,
29
+ )
30
+ from orjson import loads
15
31
  from p115client import check_response, normalize_attr_simple, P115Client
32
+ from p115client.exception import OperationalError
33
+ from p115client._upload import (
34
+ oss_multipart_part_iter, oss_multipart_upload_init,
35
+ oss_multipart_upload_url, oss_multipart_upload_complete,
36
+ )
37
+ from urlopen import urlopen
38
+ from yarl import URL
16
39
 
17
40
  from .download import iter_download_files
18
41
  from .iterdir import iterdir, iter_files_with_path, unescape_115_charref
42
+ from .util import determine_part_size
43
+
44
+
45
+ ALIYUN_DOMAIN = "oss-cn-shenzhen.aliyuncs.com"
19
46
 
20
47
 
21
48
  @overload
@@ -431,3 +458,378 @@ def iter_115_to_115_resume(
431
458
  ))
432
459
  return run_gen_step_iter(gen_step, may_call=False, async_=async_)
433
460
 
461
+
462
+ @overload
463
+ def multipart_upload_init(
464
+ client: str | P115Client,
465
+ path: str | PathLike | URL | SupportsGeturl,
466
+ pid: int = 0,
467
+ filename: str = "",
468
+ filesize: int = -1,
469
+ filesha1: str = "",
470
+ partsize: int = -1,
471
+ upload_data: None | dict = None,
472
+ domain: str = ALIYUN_DOMAIN,
473
+ *,
474
+ async_: Literal[False] = False,
475
+ **request_kwargs,
476
+ ) -> dict:
477
+ ...
478
+ @overload
479
+ def multipart_upload_init(
480
+ client: str | P115Client,
481
+ path: str | PathLike | URL | SupportsGeturl,
482
+ pid: int = 0,
483
+ filename: str = "",
484
+ filesize: int = -1,
485
+ filesha1: str = "",
486
+ partsize: int = -1,
487
+ upload_data: None | dict = None,
488
+ domain: str = ALIYUN_DOMAIN,
489
+ *,
490
+ async_: Literal[True],
491
+ **request_kwargs,
492
+ ) -> Coroutine[Any, Any, dict]:
493
+ ...
494
+ def multipart_upload_init(
495
+ client: str | P115Client,
496
+ path: str | PathLike | URL | SupportsGeturl,
497
+ pid: int = 0,
498
+ filename: str = "",
499
+ filesize: int = -1,
500
+ filesha1: str = "",
501
+ partsize: int = -1,
502
+ upload_data: None | dict = None,
503
+ domain: str = ALIYUN_DOMAIN,
504
+ *,
505
+ async_: Literal[False, True] = False,
506
+ **request_kwargs,
507
+ ) -> dict | Coroutine[Any, Any, dict]:
508
+ """准备分块上传,获取必要信息
509
+
510
+ :param client: 115 客户端或 cookies
511
+ :param path: 路径 或 链接(仅支持 GET 请求,http(s)协议)
512
+ :param pid: 上传文件到此目录的 id
513
+ :param filename: 文件名,若为空则自动确定
514
+ :param filesize: 文件大小,若为负数则自动计算
515
+ :param filesha1: 文件的 sha1 摘要,若为空则自动计算
516
+ :param partsize: 分块大小,若不为正数则自动确定
517
+ :param upload_data: 上传相关信息,可用于以后的断点续传
518
+ :param domain: 上传到指定的阿里云集群的网址(netloc)
519
+ :param async_: 是否异步
520
+ :param request_kwargs: 其它请求参数
521
+
522
+ :return: 如果秒传成功,则返回响应信息(有 "status" 字段),否则返回上传配置信息(可用于断点续传)
523
+ """
524
+ if isinstance(client, str):
525
+ client = P115Client(client, check_for_relogin=True)
526
+ def gen_step():
527
+ nonlocal upload_data, path, filename, filesha1, filesize, partsize
528
+ if upload_data is None:
529
+ upload_data = {}
530
+ if isinstance(path, str):
531
+ is_path = not path.startswith(("http://", "https://"))
532
+ elif isinstance(path, URL):
533
+ path = str(path)
534
+ is_path = False
535
+ elif isinstance(path, SupportsGeturl):
536
+ path = path.geturl()
537
+ is_path = False
538
+ else:
539
+ path = fsdecode(path)
540
+ is_path = True
541
+ path = cast(str, path)
542
+ if not filename:
543
+ if is_path:
544
+ from os.path import basename
545
+ filename = basename(path)
546
+ else:
547
+ from posixpath import basename
548
+ filename = basename(unquote(urlsplit(path).path))
549
+ if not filename:
550
+ filename = str(uuid4())
551
+ file: Any
552
+ if not filesha1:
553
+ if filesize == 0:
554
+ filesha1 = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
555
+ else:
556
+ if is_path:
557
+ if async_:
558
+ from aiofile import async_open
559
+ async def request():
560
+ async with async_open(path, "rb") as file:
561
+ return await file_digest_async(file, "sha1") # type: ignore
562
+ filesize, filesha1_obj = yield request
563
+ else:
564
+ with open(path, "rb") as file:
565
+ filesize, filesha1_obj = file_digest(file, "sha1")
566
+ else:
567
+ if async_:
568
+ from httpfile import AsyncHttpxFileReader
569
+ async def request():
570
+ file = await AsyncHttpxFileReader.new(path, headers={"user-agent": ""})
571
+ async with file:
572
+ return await file_digest_async(file, "sha1")
573
+ filesize, filesha1_obj = yield request
574
+ else:
575
+ from httpfile import HTTPFileReader
576
+ with HTTPFileReader(path, headers={"user-agent": ""}) as file:
577
+ filesize, filesha1_obj = file_digest(file, "sha1")
578
+ filesha1 = filesha1_obj.hexdigest()
579
+ if filesize < 0:
580
+ if is_path:
581
+ filesize = stat(path).st_size
582
+ else:
583
+ if async_:
584
+ file = yield to_thread(urlopen, path)
585
+ else:
586
+ file = urlopen(path)
587
+ try:
588
+ filesize = get_total_length(file) or 0
589
+ finally:
590
+ file.close()
591
+ if partsize <= 0:
592
+ partsize = determine_part_size(filesize)
593
+ read_range_bytes_or_hash: Callable
594
+ if async_:
595
+ async def read_range_bytes_or_hash(sign_check: str, /) -> bytes:
596
+ file: Any
597
+ if is_path:
598
+ from aiofile import async_open
599
+ start, end = map(int, sign_check.split("-"))
600
+ async with async_open(path, "rb") as file:
601
+ file.seek(start)
602
+ return await file.read(end - start + 1)
603
+ else:
604
+ file = await to_thread(
605
+ urlopen,
606
+ path,
607
+ headers={"Range": "bytes="+sign_check},
608
+ )
609
+ with file:
610
+ return await to_thread(file.read)
611
+ else:
612
+ def read_range_bytes_or_hash(sign_check: str, /) -> bytes:
613
+ if is_path:
614
+ start, end = map(int, sign_check.split("-"))
615
+ with open(path, "rb") as file:
616
+ file.seek(start)
617
+ return file.read(end - start + 1)
618
+ else:
619
+ with urlopen(path, headers={"Range": "bytes="+sign_check}) as file:
620
+ return file.read()
621
+ resp = yield client.upload_file_init(
622
+ filename=filename,
623
+ filesize=filesize,
624
+ filesha1=filesha1,
625
+ read_range_bytes_or_hash=read_range_bytes_or_hash, # type: ignore
626
+ pid=pid,
627
+ async_=async_, # type: ignore
628
+ **request_kwargs,
629
+ )
630
+ status = resp["status"]
631
+ statuscode = resp.get("statuscode", 0)
632
+ if status == 2 and statuscode == 0:
633
+ return resp
634
+ elif status == 1 and statuscode == 0:
635
+ bucket, object, callback = resp["bucket"], resp["object"], resp["callback"]
636
+ else:
637
+ raise OperationalError(errno.EINVAL, resp)
638
+ upload_data["bucket"] = bucket
639
+ upload_data["object"] = object
640
+ upload_data["callback"] = callback
641
+ upload_data["filename"] = filename
642
+ upload_data["filesha1"] = filesha1
643
+ upload_data["filesize"] = filesize
644
+ upload_data["partsize"] = partsize
645
+ upload_data["part_count"] = partsize and -(-filesize // partsize)
646
+ upload_data["pid"] = pid
647
+ else:
648
+ bucket = upload_data["bucket"]
649
+ object = upload_data["object"]
650
+ callback_var = loads(upload_data["callback"]["callback_var"])
651
+ resp = yield client.upload_resume(
652
+ {
653
+ "fileid": object,
654
+ "file_size": upload_data["filesize"],
655
+ "target": callback_var["x:target"],
656
+ "pick_code": callback_var["x:pick_code"],
657
+ },
658
+ async_=async_,
659
+ **request_kwargs,
660
+ )
661
+ check_response(resp)
662
+ url = f"http://{bucket}.{domain}/{object}"
663
+ token = client.upload_token
664
+ if upload_id := upload_data.get("upload_id"):
665
+ parts = yield collect(oss_multipart_part_iter(
666
+ client.request,
667
+ url,
668
+ bucket=bucket,
669
+ object=object,
670
+ upload_id=upload_id,
671
+ token=token,
672
+ async_=async_,
673
+ **request_kwargs,
674
+ ))
675
+ if parts:
676
+ upload_data["part_number_next"] = len(parts) + (int(parts[-1]["Size"]) == upload_data["partsize"])
677
+ else:
678
+ upload_data["part_number_next"] = 1
679
+ else:
680
+ upload_data["upload_id"] = yield oss_multipart_upload_init(
681
+ client.request,
682
+ url,
683
+ bucket=bucket,
684
+ object=object,
685
+ token=token,
686
+ async_=async_,
687
+ **request_kwargs,
688
+ )
689
+ upload_data["part_number_next"] = 1
690
+ return upload_data
691
+ return run_gen_step(gen_step, async_=async_)
692
+
693
+
694
+ def multipart_upload_url(
695
+ client: str | P115Client | dict,
696
+ upload_data: dict,
697
+ part_number: int = 1,
698
+ domain: str = ALIYUN_DOMAIN,
699
+ ) -> tuple[str, dict]:
700
+ """用来获取 上传链接 和 请求头,然后文件需要你自己上传
701
+
702
+ :param client: 115 客户端或 cookies,或者是 token(令牌)
703
+ :param upload_data: 上传相关信息,可用于以后的断点续传
704
+ :param part_number: 需要上传的分块编号,须从 1 开始递增
705
+ :param domain: 上传到指定的阿里云集群的网址(netloc)
706
+
707
+ :return: 上传链接 和 请求头 的 2 元组
708
+ """
709
+ if isinstance(client, dict):
710
+ token = client
711
+ else:
712
+ if isinstance(client, str):
713
+ client = P115Client(client, check_for_relogin=True)
714
+ token = client.upload_token
715
+ return oss_multipart_upload_url(
716
+ bucket=upload_data["bucket"],
717
+ object=upload_data["object"],
718
+ upload_id=upload_data["upload_id"],
719
+ part_number=part_number,
720
+ token=token,
721
+ domain=domain,
722
+ )
723
+
724
+
725
+ @overload
726
+ def multipart_upload_complete(
727
+ client: str | P115Client,
728
+ upload_data: dict,
729
+ domain: str = ALIYUN_DOMAIN,
730
+ *,
731
+ async_: Literal[False] = False,
732
+ **request_kwargs,
733
+ ) -> dict:
734
+ ...
735
+ @overload
736
+ def multipart_upload_complete(
737
+ client: str | P115Client,
738
+ upload_data: dict,
739
+ domain: str = ALIYUN_DOMAIN,
740
+ *,
741
+ async_: Literal[True],
742
+ **request_kwargs,
743
+ ) -> Coroutine[Any, Any, dict]:
744
+ ...
745
+ def multipart_upload_complete(
746
+ client: str | P115Client,
747
+ upload_data: dict,
748
+ domain: str = ALIYUN_DOMAIN,
749
+ *,
750
+ async_: Literal[False, True] = False,
751
+ **request_kwargs,
752
+ ) -> dict | Coroutine[Any, Any, dict]:
753
+ """完成分块上传
754
+
755
+ :param client: 115 客户端或 cookies,或者是 token(令牌)
756
+ :param upload_data: 上传相关信息,可用于以后的断点续传
757
+ :param domain: 上传到指定的阿里云集群的网址(netloc)
758
+ :param async_: 是否异步
759
+ :param request_kwargs: 其它请求参数
760
+
761
+ :return: 接口响应值
762
+
763
+ :example:
764
+ 你可以构建自己的分块上传逻辑,下面是一个例子
765
+
766
+ .. code:: python
767
+ from pathlib import Path
768
+ from p115client import *
769
+ from p115client.tool import *
770
+
771
+ client = P115Client(Path("~/115-cookies.txt").expanduser())
772
+ #client.login_another_open(100195123, replace=True)
773
+
774
+ # TODO: 这里填一个文件的路径
775
+ path = "test.txt"
776
+ upload_data = multipart_upload_init(
777
+ client,
778
+ path,
779
+ pid = 0,
780
+ filename = "",
781
+ upload_data = None,
782
+ )
783
+ if "status" in upload_data:
784
+ resp = upload_data
785
+ else:
786
+ partsize = upload_data["partsize"]
787
+ part_number_next = upload_data["part_number_next"]
788
+ with open(path, "rb") as file:
789
+ if part_number_next > 1:
790
+ file.seek(partsize * (part_number_next - 1))
791
+ for part_number in range(part_number_next, upload_data["part_count"] + 1):
792
+ url, headers = multipart_upload_url(client, upload_data, part_number)
793
+ ## TODO: 你可以自己改写上传的逻辑
794
+ ## NOTE: 使用 urllib3
795
+ # from urllib3 import request
796
+ # request("PUT", url, body=file.read(partsize), headers=headers)
797
+ ## NOTE: 使用 requests
798
+ # from requests import request
799
+ # request("PUT", url, data=file.read(partsize), headers=headers)
800
+ client.request(url=url, method="PUT", data=file.read(partsize), headers=headers, parse=False
801
+ resp = multipart_upload_complete(client, upload_data)
802
+ print(resp)
803
+ """
804
+ bucket = upload_data["bucket"]
805
+ object = upload_data["object"]
806
+ upload_id = upload_data["upload_id"]
807
+ url = f"http://{bucket}.{domain}/{object}"
808
+ if isinstance(client, str):
809
+ client = P115Client(client, check_for_relogin=True)
810
+ token = client.upload_token
811
+ def gen_step():
812
+ parts = yield collect(oss_multipart_part_iter(
813
+ client.request,
814
+ url,
815
+ bucket=bucket,
816
+ object=object,
817
+ upload_id=upload_id,
818
+ token=token,
819
+ async_=async_,
820
+ **request_kwargs,
821
+ ))
822
+ return oss_multipart_upload_complete(
823
+ client.request,
824
+ url,
825
+ bucket=bucket,
826
+ object=object,
827
+ upload_id=upload_id,
828
+ token=token,
829
+ callback=upload_data["callback"],
830
+ parts=parts,
831
+ async_=async_,
832
+ **request_kwargs,
833
+ )
834
+ return run_gen_step(gen_step, async_=async_)
835
+
@@ -4,7 +4,7 @@
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
  __all__ = [
6
6
  "get_status_code", "is_timeouterror", "posix_escape_name", "reduce_image_url_layers",
7
- "share_extract_payload", "unescape_115_charref",
7
+ "share_extract_payload", "unescape_115_charref", "determine_part_size",
8
8
  ]
9
9
  __doc__ = "这个模块提供了一些工具函数"
10
10
 
@@ -105,3 +105,25 @@ def unescape_115_charref(s: str, /) -> str:
105
105
  """
106
106
  return CRE_115_CHARREF_sub(lambda a: chr(int(a[1])), s)
107
107
 
108
+
109
+ def determine_part_size(
110
+ size: int,
111
+ min_part_size: int = 1024 * 1024 * 10,
112
+ max_part_count: int = 10 ** 4,
113
+ ) -> int:
114
+ """确定分片上传(multipart upload)时的分片大小
115
+
116
+ :param size: 数据大小
117
+ :param min_part_size: 用户期望的分片大小
118
+ :param max_part_count: 最大的分片个数
119
+
120
+ :return: 分片大小
121
+ """
122
+ if size <= min_part_size:
123
+ return size
124
+ n = -(-size // max_part_count)
125
+ part_size = min_part_size
126
+ while part_size < n:
127
+ part_size <<= 1
128
+ return part_size
129
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "p115client"
3
- version = "0.0.5.11.8.2"
3
+ version = "0.0.5.11.9"
4
4
  description = "Python 115 webdisk client."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"