p115client 0.0.5.11.8.1__py3-none-any.whl → 0.0.5.11.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.
p115client/_upload.py CHANGED
@@ -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
-
p115client/client.py CHANGED
@@ -220,12 +220,11 @@ def complete_lixian_api(
220
220
  return complete_api(path, base, base_url=base_url)
221
221
 
222
222
 
223
- def try_parse_int(s: int | str, /):
224
- if not isinstance(s, str):
225
- return s
226
- if s == "0":
227
- return 0
228
- if s.startswith("0") or s.strip(digits):
223
+ def try_parse_int(
224
+ s, /,
225
+ _match=re_compile("-?[1-9][0-9]*").fullmatch,
226
+ ):
227
+ if not isinstance(s, str) or not s or not _match(s):
229
228
  return s
230
229
  return int(s)
231
230
 
@@ -1671,7 +1670,7 @@ class ClientRequestMixin:
1671
1670
 
1672
1671
  GET https://qrcodeapi.115.com/api/1.0/web/1.0/qrcode
1673
1672
 
1674
- :params uid: 二维码的 uid
1673
+ :param uid: 二维码的 uid
1675
1674
 
1676
1675
  :return: 图片的二进制数据(PNG 图片)
1677
1676
  """
@@ -3275,7 +3274,6 @@ class P115OpenClient(ClientRequestMixin):
3275
3274
  api = complete_proapi("/open/ufile/downurl", base_url)
3276
3275
  if isinstance(payload, str):
3277
3276
  payload = {"pick_code": payload}
3278
- request_headers = request_kwargs.get("headers")
3279
3277
  headers = request_kwargs.get("headers")
3280
3278
  if headers:
3281
3279
  if isinstance(headers, Mapping):
@@ -3463,7 +3461,7 @@ class P115OpenClient(ClientRequestMixin):
3463
3461
  - fields: str = <default>
3464
3462
  - for: str = <default> 💡 文件格式,例如 "doc"
3465
3463
  - format: str = "json" 💡 返回格式,默认即可
3466
- - hide_data: str = <default>
3464
+ - hide_data: str = <default> 💡 是否返回文件数据
3467
3465
  - is_q: 0 | 1 = <default>
3468
3466
  - is_share: 0 | 1 = <default>
3469
3467
  - min_size: int = 0 💡 最小的文件大小
@@ -3772,8 +3770,8 @@ class P115OpenClient(ClientRequestMixin):
3772
3770
  - search_value: str = "." 💡 搜索文本,可以是 sha1
3773
3771
  - show_dir: 0 | 1 = 1 💡 是否显示目录
3774
3772
  - source: str = <default>
3775
- - star: 0 | 1 = <default>
3776
- - suffix: str = <default>
3773
+ - star: 0 | 1 = <default> 💡 是否星标文件
3774
+ - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
3777
3775
  - type: int = <default> 💡 文件类型
3778
3776
 
3779
3777
  - 0: 全部(仅当前目录)
@@ -6174,6 +6172,8 @@ class P115Client(P115OpenClient):
6174
6172
 
6175
6173
  但一个设备的新登录者,并不总是意味着把较早的登录者下线,一般需要触发某个检查机制后,才会把同一设备下除最近一次登录外的所有 cookies 失效
6176
6174
 
6175
+ 所以你可以用一个设备的 cookies 专门用于扫码登录,获取另一个设备的 cookies 执行网盘操作,第 2 个 cookies 失效了,则用第 1 个 cookies 扫码,如此可避免单个 cookies 失效后,不能自动获取新的
6176
+
6177
6177
  :param app: 要登录的 app,如果为 None,则用当前登录设备,如果无当前登录设备,则报错
6178
6178
  :param replace: 替换某个 client 对象的 cookie
6179
6179
 
@@ -6861,6 +6861,7 @@ class P115Client(P115OpenClient):
6861
6861
  self,
6862
6862
  payload: dict,
6863
6863
  /,
6864
+ app: str = "web",
6864
6865
  base_url: bool | str | Callable[[], str] = False,
6865
6866
  *,
6866
6867
  async_: Literal[False] = False,
@@ -6872,6 +6873,7 @@ class P115Client(P115OpenClient):
6872
6873
  self,
6873
6874
  payload: dict,
6874
6875
  /,
6876
+ app: str = "web",
6875
6877
  base_url: bool | str | Callable[[], str] = False,
6876
6878
  *,
6877
6879
  async_: Literal[True],
@@ -6882,6 +6884,7 @@ class P115Client(P115OpenClient):
6882
6884
  self,
6883
6885
  payload: dict,
6884
6886
  /,
6887
+ app: str = "web",
6885
6888
  base_url: bool | str | Callable[[], str] = False,
6886
6889
  *,
6887
6890
  async_: Literal[False, True] = False,
@@ -6896,7 +6899,7 @@ class P115Client(P115OpenClient):
6896
6899
  - aid: int | str 💡 助愿的 id
6897
6900
  - to_cid: int = <default> 💡 助愿中的分享链接转存到你的网盘中目录的 id
6898
6901
  """
6899
- 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)
6900
6903
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
6901
6904
 
6902
6905
  @overload
@@ -6904,6 +6907,7 @@ class P115Client(P115OpenClient):
6904
6907
  self,
6905
6908
  payload: dict,
6906
6909
  /,
6910
+ app: str = "web",
6907
6911
  base_url: bool | str | Callable[[], str] = False,
6908
6912
  *,
6909
6913
  async_: Literal[False] = False,
@@ -6915,6 +6919,7 @@ class P115Client(P115OpenClient):
6915
6919
  self,
6916
6920
  payload: dict,
6917
6921
  /,
6922
+ app: str = "web",
6918
6923
  base_url: bool | str | Callable[[], str] = False,
6919
6924
  *,
6920
6925
  async_: Literal[True],
@@ -6925,6 +6930,7 @@ class P115Client(P115OpenClient):
6925
6930
  self,
6926
6931
  payload: dict,
6927
6932
  /,
6933
+ app: str = "web",
6928
6934
  base_url: bool | str | Callable[[], str] = False,
6929
6935
  *,
6930
6936
  async_: Literal[False, True] = False,
@@ -6940,7 +6946,7 @@ class P115Client(P115OpenClient):
6940
6946
  - images: int | str = <default> 💡 图片文件在你的网盘的 id,多个用逗号 "," 隔开
6941
6947
  - file_ids: int | str = <default> 💡 文件在你的网盘的 id,多个用逗号 "," 隔开
6942
6948
  """
6943
- 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)
6944
6950
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
6945
6951
 
6946
6952
  @overload
@@ -6948,6 +6954,7 @@ class P115Client(P115OpenClient):
6948
6954
  self,
6949
6955
  payload: int | str | dict,
6950
6956
  /,
6957
+ app: str = "web",
6951
6958
  base_url: bool | str | Callable[[], str] = False,
6952
6959
  *,
6953
6960
  async_: Literal[False] = False,
@@ -6959,6 +6966,7 @@ class P115Client(P115OpenClient):
6959
6966
  self,
6960
6967
  payload: int | str | dict,
6961
6968
  /,
6969
+ app: str = "web",
6962
6970
  base_url: bool | str | Callable[[], str] = False,
6963
6971
  *,
6964
6972
  async_: Literal[True],
@@ -6969,6 +6977,7 @@ class P115Client(P115OpenClient):
6969
6977
  self,
6970
6978
  payload: int | str | dict,
6971
6979
  /,
6980
+ app: str = "web",
6972
6981
  base_url: bool | str | Callable[[], str] = False,
6973
6982
  *,
6974
6983
  async_: Literal[False, True] = False,
@@ -6981,7 +6990,7 @@ class P115Client(P115OpenClient):
6981
6990
  :payload:
6982
6991
  - ids: int | str 💡 助愿的 id,多个用逗号 "," 隔开
6983
6992
  """
6984
- 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)
6985
6994
  if isinstance(payload, (int, str)):
6986
6995
  payload = {"ids": payload}
6987
6996
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -6991,6 +7000,7 @@ class P115Client(P115OpenClient):
6991
7000
  self,
6992
7001
  payload: str | dict,
6993
7002
  /,
7003
+ app: str = "web",
6994
7004
  base_url: bool | str | Callable[[], str] = False,
6995
7005
  *,
6996
7006
  async_: Literal[False] = False,
@@ -7002,6 +7012,7 @@ class P115Client(P115OpenClient):
7002
7012
  self,
7003
7013
  payload: str | dict,
7004
7014
  /,
7015
+ app: str = "web",
7005
7016
  base_url: bool | str | Callable[[], str] = False,
7006
7017
  *,
7007
7018
  async_: Literal[True],
@@ -7012,6 +7023,7 @@ class P115Client(P115OpenClient):
7012
7023
  self,
7013
7024
  payload: str | dict,
7014
7025
  /,
7026
+ app: str = "web",
7015
7027
  base_url: bool | str | Callable[[], str] = False,
7016
7028
  *,
7017
7029
  async_: Literal[False, True] = False,
@@ -7028,7 +7040,7 @@ class P115Client(P115OpenClient):
7028
7040
  - limit: int = 10 💡 分页大小
7029
7041
  - sort: int | str = <default> 💡 排序
7030
7042
  """
7031
- 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)
7032
7044
  if isinstance(payload, str):
7033
7045
  payload = {"start": 0, "page": 1, "limit": 10, "id": payload}
7034
7046
  else:
@@ -7039,6 +7051,7 @@ class P115Client(P115OpenClient):
7039
7051
  def act_xys_get_act_info(
7040
7052
  self,
7041
7053
  /,
7054
+ app: str = "web",
7042
7055
  base_url: bool | str | Callable[[], str] = False,
7043
7056
  *,
7044
7057
  async_: Literal[False] = False,
@@ -7049,6 +7062,7 @@ class P115Client(P115OpenClient):
7049
7062
  def act_xys_get_act_info(
7050
7063
  self,
7051
7064
  /,
7065
+ app: str = "web",
7052
7066
  base_url: bool | str | Callable[[], str] = False,
7053
7067
  *,
7054
7068
  async_: Literal[True],
@@ -7058,6 +7072,7 @@ class P115Client(P115OpenClient):
7058
7072
  def act_xys_get_act_info(
7059
7073
  self,
7060
7074
  /,
7075
+ app: str = "web",
7061
7076
  base_url: bool | str | Callable[[], str] = False,
7062
7077
  *,
7063
7078
  async_: Literal[False, True] = False,
@@ -7067,7 +7082,7 @@ class P115Client(P115OpenClient):
7067
7082
 
7068
7083
  GET https://act.115.com/api/1.0/web/1.0/act2024xys/get_act_info
7069
7084
  """
7070
- 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)
7071
7086
  return self.request(url=api, async_=async_, **request_kwargs)
7072
7087
 
7073
7088
  @overload
@@ -7075,6 +7090,7 @@ class P115Client(P115OpenClient):
7075
7090
  self,
7076
7091
  payload: str | dict,
7077
7092
  /,
7093
+ app: str = "web",
7078
7094
  base_url: bool | str | Callable[[], str] = False,
7079
7095
  *,
7080
7096
  async_: Literal[False] = False,
@@ -7086,6 +7102,7 @@ class P115Client(P115OpenClient):
7086
7102
  self,
7087
7103
  payload: str | dict,
7088
7104
  /,
7105
+ app: str = "web",
7089
7106
  base_url: bool | str | Callable[[], str] = False,
7090
7107
  *,
7091
7108
  async_: Literal[True],
@@ -7096,6 +7113,7 @@ class P115Client(P115OpenClient):
7096
7113
  self,
7097
7114
  payload: str | dict,
7098
7115
  /,
7116
+ app: str = "web",
7099
7117
  base_url: bool | str | Callable[[], str] = False,
7100
7118
  *,
7101
7119
  async_: Literal[False, True] = False,
@@ -7108,7 +7126,7 @@ class P115Client(P115OpenClient):
7108
7126
  :payload:
7109
7127
  - id: str 💡 许愿的 id
7110
7128
  """
7111
- 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)
7112
7130
  if isinstance(payload, str):
7113
7131
  payload = {"id": payload}
7114
7132
  return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@@ -7117,6 +7135,7 @@ class P115Client(P115OpenClient):
7117
7135
  def act_xys_home_list(
7118
7136
  self,
7119
7137
  /,
7138
+ app: str = "web",
7120
7139
  base_url: bool | str | Callable[[], str] = False,
7121
7140
  *,
7122
7141
  async_: Literal[False] = False,
@@ -7127,6 +7146,7 @@ class P115Client(P115OpenClient):
7127
7146
  def act_xys_home_list(
7128
7147
  self,
7129
7148
  /,
7149
+ app: str = "web",
7130
7150
  base_url: bool | str | Callable[[], str] = False,
7131
7151
  *,
7132
7152
  async_: Literal[True],
@@ -7136,6 +7156,7 @@ class P115Client(P115OpenClient):
7136
7156
  def act_xys_home_list(
7137
7157
  self,
7138
7158
  /,
7159
+ app: str = "web",
7139
7160
  base_url: bool | str | Callable[[], str] = False,
7140
7161
  *,
7141
7162
  async_: Literal[False, True] = False,
@@ -7145,7 +7166,7 @@ class P115Client(P115OpenClient):
7145
7166
 
7146
7167
  GET https://act.115.com/api/1.0/web/1.0/act2024xys/home_list
7147
7168
  """
7148
- 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)
7149
7170
  return self.request(url=api, async_=async_, **request_kwargs)
7150
7171
 
7151
7172
  @overload
@@ -7153,6 +7174,7 @@ class P115Client(P115OpenClient):
7153
7174
  self,
7154
7175
  payload: int | str | dict = 0,
7155
7176
  /,
7177
+ app: str = "web",
7156
7178
  base_url: bool | str | Callable[[], str] = False,
7157
7179
  *,
7158
7180
  async_: Literal[False] = False,
@@ -7164,6 +7186,7 @@ class P115Client(P115OpenClient):
7164
7186
  self,
7165
7187
  payload: int | str | dict = 0,
7166
7188
  /,
7189
+ app: str = "web",
7167
7190
  base_url: bool | str | Callable[[], str] = False,
7168
7191
  *,
7169
7192
  async_: Literal[True],
@@ -7174,6 +7197,7 @@ class P115Client(P115OpenClient):
7174
7197
  self,
7175
7198
  payload: int | str | dict = 0,
7176
7199
  /,
7200
+ app: str = "web",
7177
7201
  base_url: bool | str | Callable[[], str] = False,
7178
7202
  *,
7179
7203
  async_: Literal[False, True] = False,
@@ -7194,7 +7218,7 @@ class P115Client(P115OpenClient):
7194
7218
  - page: int = 1 💡 第几页
7195
7219
  - limit: int = 10 💡 分页大小
7196
7220
  """
7197
- 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)
7198
7222
  if isinstance(payload, (int, str)):
7199
7223
  payload = {"start": 0, "page": 1, "limit": 10, "type": payload}
7200
7224
  else:
@@ -7206,6 +7230,7 @@ class P115Client(P115OpenClient):
7206
7230
  self,
7207
7231
  payload: int | str | dict = 0,
7208
7232
  /,
7233
+ app: str = "web",
7209
7234
  base_url: bool | str | Callable[[], str] = False,
7210
7235
  *,
7211
7236
  async_: Literal[False] = False,
@@ -7217,6 +7242,7 @@ class P115Client(P115OpenClient):
7217
7242
  self,
7218
7243
  payload: int | str | dict = 0,
7219
7244
  /,
7245
+ app: str = "web",
7220
7246
  base_url: bool | str | Callable[[], str] = False,
7221
7247
  *,
7222
7248
  async_: Literal[True],
@@ -7227,6 +7253,7 @@ class P115Client(P115OpenClient):
7227
7253
  self,
7228
7254
  payload: int | str | dict = 0,
7229
7255
  /,
7256
+ app: str = "web",
7230
7257
  base_url: bool | str | Callable[[], str] = False,
7231
7258
  *,
7232
7259
  async_: Literal[False, True] = False,
@@ -7247,7 +7274,7 @@ class P115Client(P115OpenClient):
7247
7274
  - page: int = 1 💡 第几页
7248
7275
  - limit: int = 10 💡 分页大小
7249
7276
  """
7250
- 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)
7251
7278
  if isinstance(payload, (int, str)):
7252
7279
  payload = {"start": 0, "page": 1, "limit": 10, "type": payload}
7253
7280
  else:
@@ -7259,6 +7286,7 @@ class P115Client(P115OpenClient):
7259
7286
  self,
7260
7287
  payload: str | dict,
7261
7288
  /,
7289
+ app: str = "web",
7262
7290
  base_url: bool | str | Callable[[], str] = False,
7263
7291
  *,
7264
7292
  async_: Literal[False] = False,
@@ -7270,6 +7298,7 @@ class P115Client(P115OpenClient):
7270
7298
  self,
7271
7299
  payload: str | dict,
7272
7300
  /,
7301
+ app: str = "web",
7273
7302
  base_url: bool | str | Callable[[], str] = False,
7274
7303
  *,
7275
7304
  async_: Literal[True],
@@ -7280,6 +7309,7 @@ class P115Client(P115OpenClient):
7280
7309
  self,
7281
7310
  payload: str | dict,
7282
7311
  /,
7312
+ app: str = "web",
7283
7313
  base_url: bool | str | Callable[[], str] = False,
7284
7314
  *,
7285
7315
  async_: Literal[False, True] = False,
@@ -7294,7 +7324,7 @@ class P115Client(P115OpenClient):
7294
7324
  - rewardSpace: int = 5 💡 奖励容量,单位是 GB
7295
7325
  - images: int | str = <default> 💡 图片文件在你的网盘的 id,多个用逗号 "," 隔开
7296
7326
  """
7297
- 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)
7298
7328
  if isinstance(payload, str):
7299
7329
  payload = {"rewardSpace": 5, "content": payload}
7300
7330
  else:
@@ -7306,6 +7336,7 @@ class P115Client(P115OpenClient):
7306
7336
  self,
7307
7337
  payload: str | dict,
7308
7338
  /,
7339
+ app: str = "web",
7309
7340
  base_url: bool | str | Callable[[], str] = False,
7310
7341
  *,
7311
7342
  async_: Literal[False] = False,
@@ -7317,6 +7348,7 @@ class P115Client(P115OpenClient):
7317
7348
  self,
7318
7349
  payload: str | dict,
7319
7350
  /,
7351
+ app: str = "web",
7320
7352
  base_url: bool | str | Callable[[], str] = False,
7321
7353
  *,
7322
7354
  async_: Literal[True],
@@ -7327,6 +7359,7 @@ class P115Client(P115OpenClient):
7327
7359
  self,
7328
7360
  payload: str | dict,
7329
7361
  /,
7362
+ app: str = "web",
7330
7363
  base_url: bool | str | Callable[[], str] = False,
7331
7364
  *,
7332
7365
  async_: Literal[False, True] = False,
@@ -7339,7 +7372,7 @@ class P115Client(P115OpenClient):
7339
7372
  :payload:
7340
7373
  - ids: str 💡 许愿的 id,多个用逗号 "," 隔开
7341
7374
  """
7342
- 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)
7343
7376
  if isinstance(payload, str):
7344
7377
  payload = {"ids": payload}
7345
7378
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@@ -7955,7 +7988,6 @@ class P115Client(P115OpenClient):
7955
7988
  payload = {"pick_code": payload}
7956
7989
  else:
7957
7990
  payload = {"pick_code": payload["pickcode"]}
7958
- request_headers = request_kwargs.get("headers")
7959
7991
  headers = request_kwargs.get("headers")
7960
7992
  if headers:
7961
7993
  if isinstance(headers, Mapping):
@@ -8216,7 +8248,6 @@ class P115Client(P115OpenClient):
8216
8248
  - full_name: str
8217
8249
  """
8218
8250
  api = complete_proapi("/2.0/ufile/extract_down_file", base_url, app)
8219
- request_headers = request_kwargs.get("headers")
8220
8251
  headers = request_kwargs.get("headers")
8221
8252
  if headers:
8222
8253
  if isinstance(headers, Mapping):
@@ -8272,7 +8303,6 @@ class P115Client(P115OpenClient):
8272
8303
  - full_name: str
8273
8304
  """
8274
8305
  api = complete_webapi("/files/extract_down_file", base_url=base_url)
8275
- request_headers = request_kwargs.get("headers")
8276
8306
  headers = request_kwargs.get("headers")
8277
8307
  if headers:
8278
8308
  if isinstance(headers, Mapping):
@@ -10516,7 +10546,7 @@ class P115Client(P115OpenClient):
10516
10546
  - fields: str = <default>
10517
10547
  - for: str = <default> 💡 文件格式,例如 "doc"
10518
10548
  - format: str = "json" 💡 返回格式,默认即可
10519
- - hide_data: str = <default>
10549
+ - hide_data: str = <default> 💡 是否返回文件数据
10520
10550
  - is_q: 0 | 1 = <default>
10521
10551
  - is_share: 0 | 1 = <default>
10522
10552
  - min_size: int = 0 💡 最小的文件大小
@@ -10646,7 +10676,7 @@ class P115Client(P115OpenClient):
10646
10676
  - fields: str = <default>
10647
10677
  - for: str = <default> 💡 文件格式,例如 "doc"
10648
10678
  - format: str = "json" 💡 返回格式,默认即可
10649
- - hide_data: str = <default>
10679
+ - hide_data: str = <default> 💡 是否返回文件数据
10650
10680
  - is_q: 0 | 1 = <default>
10651
10681
  - is_share: 0 | 1 = <default>
10652
10682
  - min_size: int = 0 💡 最小的文件大小
@@ -10775,7 +10805,7 @@ class P115Client(P115OpenClient):
10775
10805
  - fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
10776
10806
  - fields: str = <default>
10777
10807
  - format: str = "json" 💡 返回格式,默认即可
10778
- - hide_data: str = <default>
10808
+ - hide_data: str = <default> 💡 是否返回文件数据
10779
10809
  - is_asc: 0 | 1 = <default>
10780
10810
  - is_q: 0 | 1 = <default>
10781
10811
  - is_share: 0 | 1 = <default>
@@ -11730,7 +11760,7 @@ class P115Client(P115OpenClient):
11730
11760
  :payload:
11731
11761
  - offset: int = 0
11732
11762
  - limit: int = 1150
11733
- - played_end: 0 | 1 = <default>
11763
+ - played_end: 0 | 1 = <default> 💡 是否已经播放完
11734
11764
  - type: int = <default> 💡 类型(??表示还未搞清楚),多个用逗号 "," 隔开
11735
11765
 
11736
11766
  - 全部: 0
@@ -14605,8 +14635,8 @@ class P115Client(P115OpenClient):
14605
14635
  - search_value: str = "." 💡 搜索文本,可以是 sha1
14606
14636
  - show_dir: 0 | 1 = 1 💡 是否显示目录
14607
14637
  - source: str = <default>
14608
- - star: 0 | 1 = <default>
14609
- - suffix: str = <default>
14638
+ - star: 0 | 1 = <default> 💡 是否星标文件
14639
+ - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
14610
14640
  - type: int = <default> 💡 文件类型
14611
14641
 
14612
14642
  - 0: 全部(仅当前目录)
@@ -14702,8 +14732,8 @@ class P115Client(P115OpenClient):
14702
14732
  - search_value: str = "." 💡 搜索文本,可以是 sha1
14703
14733
  - show_dir: 0 | 1 = 1 💡 是否显示目录
14704
14734
  - source: str = <default>
14705
- - star: 0 | 1 = <default>
14706
- - suffix: str = <default>
14735
+ - star: 0 | 1 = <default> 💡 是否星标文件
14736
+ - suffix: str = <default> 💡 后缀名(优先级高于 `type`)
14707
14737
  - type: int = <default> 💡 文件类型
14708
14738
 
14709
14739
  - 0: 全部(仅当前目录)
@@ -15468,7 +15498,7 @@ class P115Client(P115OpenClient):
15468
15498
  这个接口只支持 web 的 cookies,其它设备会返回空数据,而且获取得到的 m3u8 里的链接,也是 m3u8,会绑定前一次请求时的 user-agent
15469
15499
 
15470
15500
  :param pickcode: 视频文件的 pickcode
15471
- :params definition: 画质,默认列出所有画质。但可进行筛选,常用的为:
15501
+ :param definition: 画质,默认列出所有画质。但可进行筛选,常用的为:
15472
15502
  - 0: 各种分辨率(默认)
15473
15503
  - 1: SD 标清(约为 480p)
15474
15504
  - 3: HD 超清(约为 720p)
@@ -21145,6 +21175,9 @@ class P115Client(P115OpenClient):
21145
21175
  )
21146
21176
  return run_gen_step(gen_step, may_call=False, async_=async_)
21147
21177
 
21178
+ # TODO: 分块上传时,允许一定次数的重试
21179
+ # TODO: 不妨单独为分块上传做一个封装
21180
+ # TODO: 减少参数,简化使用方法
21148
21181
  @overload # type: ignore
21149
21182
  def upload_file(
21150
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
+
p115client/tool/upload.py CHANGED
@@ -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
+
p115client/tool/util.py CHANGED
@@ -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
  Metadata-Version: 2.1
2
2
  Name: p115client
3
- Version: 0.0.5.11.8.1
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
@@ -1,12 +1,13 @@
1
1
  LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
2
  p115client/__init__.py,sha256=1mx7njuAlqcuEWONTjSiiGnXyyNyqOcJyNX1FMHqQ-4,214
3
- p115client/_upload.py,sha256=RTYpM_EDFuI3O7yf08f13NdeG0iD03o_V4H6Je6Nsr8,30754
4
- p115client/client.py,sha256=ro8Qk1RiKMzMRyg19eRvVTBFF-9lgKZoOYq2FiDff5w,779734
3
+ p115client/_upload.py,sha256=3uXwgFsXkZ4X5hjL1N2mx0y8tA8MjVxe49N0BoyYyno,31253
4
+ p115client/client.py,sha256=PF2onKoO9z0xF8mf1Q87SXHmwk1NS7fOo34gERt8Ii8,781183
5
5
  p115client/const.py,sha256=KqDGr9KsOnSkNVM3RIdQGptCMHbidMmZ_ffyFPiniww,7654
6
6
  p115client/exception.py,sha256=4SZ8ubOLMRxtcqc0u1kNzXqH1a6wwXJFwGnRDURoEgQ,3708
7
7
  p115client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- p115client/tool/__init__.py,sha256=NjT9rroMhLwKI7UlpSCksSsnB3GexXzxvhfunNWzjY0,386
8
+ p115client/tool/__init__.py,sha256=TdGjEH7SD_6BaBdNg9Wd2Zd91KCycDOVN9JbH21z5SA,406
9
9
  p115client/tool/attr.py,sha256=RmNm5uar2PVv1Me7xOWyM0JtZr-JqoglIIpF0WHGues,2979
10
+ p115client/tool/auth.py,sha256=jUkVaCACg_K38RzpHTMGvve1xvhAdBeyPGtlNIfA_9M,1679
10
11
  p115client/tool/download.py,sha256=CZJE5f1SHi26eCNTPcF5VjSoht8WTUYravEG219i4gk,61534
11
12
  p115client/tool/edit.py,sha256=kRz-Ee7KuNNlHhcVi4uHdh9DSfF1ZxUREIhK9x4hNIE,17771
12
13
  p115client/tool/export_dir.py,sha256=1r2qDuYIirtDkCBQfYXh6lbm06IStRpiom-ZjtIYdbo,24502
@@ -16,11 +17,11 @@ p115client/tool/iterdir.py,sha256=dXBMPfxPzRcFhBpnBT3X1LYkhc3l31rYCLRKRFbggSk,20
16
17
  p115client/tool/life.py,sha256=ceweN2uNKYxigSOaBQ4Abo5u05zqppRm_P7k-_rHjcA,17301
17
18
  p115client/tool/pool.py,sha256=H65VhoNxQC6xWSL1THq_PximWnBOqB4EfU6kWBTAnlA,13946
18
19
  p115client/tool/request.py,sha256=rjXuQwRganE5Z-4rfgnyPFjE4jzdQSLdIs9s0cIDshU,7043
19
- p115client/tool/upload.py,sha256=i6laoeG7GdNO7hvCDVBy8Yv2Is800wL7O5na6a3v70g,15971
20
- p115client/tool/util.py,sha256=0o9TrXdoPcljgxDDRdxRon41bq1OjuUYCWsR0XLfmPo,3357
20
+ p115client/tool/upload.py,sha256=fJnxozMWeFW7m2T3AKYWGJdABxK0tnMbBaDDc1LHKw4,31278
21
+ p115client/tool/util.py,sha256=pAa8gc4BcnVTpNcXbNZU4tBUMjSB04DGOpzDdzfbto8,3934
21
22
  p115client/tool/xys.py,sha256=vU28Px2yeQzIxxGkopJIpvV6TdOnWJ5xB6NPXpTgM0Y,10306
22
23
  p115client/type.py,sha256=7kOp98uLaYqcTTCgCrb3DRcl8ukMpn7ibsnVvtw2nG8,6250
23
- p115client-0.0.5.11.8.1.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
24
- p115client-0.0.5.11.8.1.dist-info/METADATA,sha256=e-F1jCsLHlXC-jCgUfDJcvKcjZpn7OwX8H9Hu7yu6nE,8194
25
- p115client-0.0.5.11.8.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
26
- p115client-0.0.5.11.8.1.dist-info/RECORD,,
24
+ p115client-0.0.5.11.9.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
25
+ p115client-0.0.5.11.9.dist-info/METADATA,sha256=ZtZoJ6AlBvgr-sNR4TtYBGFDKpqzZ30HZgAzSbgtg0A,8192
26
+ p115client-0.0.5.11.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
27
+ p115client-0.0.5.11.9.dist-info/RECORD,,