p115client 0.0.5.8.7__py3-none-any.whl → 0.0.5.9.1__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/tool/pool.py CHANGED
@@ -1,25 +1,54 @@
1
1
  #!/usr/bin/env python3
2
2
  # encoding: utf-8
3
3
 
4
+ from __future__ import annotations
5
+
4
6
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
7
  __all__ = [
6
- "generate_cookies_factory", "generate_client_factory", "cookies_pool", "client_pool",
7
- "call_wrap_with_cookies_pool",
8
+ "generate_auth_factory", "generate_cookies_factory", "generate_client_factory",
9
+ "auth_pool", "cookies_pool", "client_pool", "call_wrap_with_pool",
8
10
  ]
9
11
  __doc__ = "这个模块提供了一些和 cookies 池有关的函数"
10
12
 
11
13
  from asyncio import Lock as AsyncLock
12
- from collections import deque
13
- from collections.abc import Callable, Iterable, Sequence
14
- from functools import partial, update_wrapper
14
+ from collections.abc import Callable, Iterable
15
+ from functools import partial, total_ordering, update_wrapper
16
+ from heapq import heappop, heappush, heapify
17
+ from itertools import cycle, repeat
18
+ from math import inf, isinf
15
19
  from threading import Lock
16
20
  from time import time
17
- from typing import Any
21
+ from typing import cast, Any
18
22
 
19
23
  from iterutils import run_gen_step
20
24
  from p115client import check_response, P115Client
21
- from p115client.const import APP_TO_SSOENT
22
- from p115client.exception import P115OSError, AuthenticationError
25
+ from p115client.exception import P115OSError, AuthenticationError, LoginError
26
+
27
+
28
+ @total_ordering
29
+ class ComparedWithID[T]:
30
+ value: T
31
+
32
+ def __new__(cls, value: T | ComparedWithID[T], /):
33
+ if isinstance(value, ComparedWithID):
34
+ return value
35
+ else:
36
+ self = super().__new__(cls)
37
+ self.value = value
38
+ return self
39
+
40
+ def __eq__(self, other, /) -> bool:
41
+ if isinstance(other, ComparedWithID):
42
+ return id(self) == id(other.value)
43
+ return id(self) == id(other)
44
+
45
+ def __lt__(self, other, /) -> bool:
46
+ if isinstance(other, ComparedWithID):
47
+ return id(self) < id(other.value)
48
+ return id(self) < id(other)
49
+
50
+ def __repr__(self, /) -> str:
51
+ return f"{type(self).__qualname__}({self.value!r})"
23
52
 
24
53
 
25
54
  def get_status(e: BaseException, /) -> None | int:
@@ -48,107 +77,171 @@ def is_timeouterror(exc: Exception) -> bool:
48
77
  return False
49
78
 
50
79
 
51
- def generate_cookies_factory(
80
+ def generate_auth_factory(
52
81
  client: str | P115Client,
53
- app: str = "",
82
+ app_ids: Iterable[int] = range(100195123, 100196659, 2),
54
83
  **request_kwargs,
55
84
  ) -> Callable:
56
- """利用一个已登录设备的 cookies,产生另一个设备的若干 cookies
85
+ """利用一个已登录设备的 cookies,产生若干开放应用的 access_token
57
86
 
58
87
  :param client: 115 客户端或 cookies
59
- :param app: 自动扫码后绑定的 app
88
+ :param app_ids: 一组开放应用的 AppID
60
89
  :param request_kwargs: 其它请求参数
61
90
 
62
- :return: 函数,调用以返回一个 cookies
91
+ :return: 函数,调用以返回一个字典,包含 authorization 请求头
63
92
  """
64
93
  if isinstance(client, str):
65
94
  client = P115Client(client, check_for_relogin=True)
66
- if app:
67
- if APP_TO_SSOENT.get(app) == client.login_ssoent:
68
- raise ValueError("same login device will cause conflicts")
69
- else:
70
- app = "tv" if client.login_ssoent == "R2" else "alipaymini"
71
- login = client.login_with_app
95
+ login = client.login_with_open
96
+ get_app_id = cycle(app_ids).__next__
72
97
  def make_cookies(async_: bool = False):
73
- def call():
98
+ def gen_step():
74
99
  while True:
100
+ app_id = get_app_id()
75
101
  try:
76
- resp = yield login(app, async_=async_, **request_kwargs) # type: ignore
102
+ resp = yield login(
103
+ get_app_id(),
104
+ async_=async_, # type: ignore
105
+ **request_kwargs,
106
+ )
77
107
  except Exception as e:
78
108
  if not is_timeouterror(e):
79
109
  raise
80
110
  check_response(resp)
81
- return "; ".join(f"{k}={v}" for k, v in resp["data"]["cookie"].items())
82
- return run_gen_step(call, async_=async_)
111
+ return {
112
+ "authorization": "Bearer " + resp["data"]["access_token"],
113
+ "app_id": str(app_id),
114
+ }
115
+ return run_gen_step(gen_step, async_=async_)
83
116
  return make_cookies
84
117
 
85
118
 
86
- def generate_client_factory(
119
+ def generate_cookies_factory(
87
120
  client: str | P115Client,
88
- app: str = "",
121
+ app: str | Iterable[str] = "",
89
122
  **request_kwargs,
90
123
  ) -> Callable:
91
- """利用一个已登录设备的 client,产生另一个设备的若干 client
124
+ """利用一个已登录设备的 cookies,产生另一个设备的若干 cookies
92
125
 
93
126
  :param client: 115 客户端或 cookies
94
- :param app: 自动扫码后绑定的 app
127
+ :param app: 自动扫码后绑定的 app(多个则传入一组 app 的可迭代对象)
95
128
  :param request_kwargs: 其它请求参数
96
129
 
97
- :return: 函数,调用以返回一个 client
130
+ :return: 函数,调用以返回一个字典,包含 cookie 请求头
98
131
  """
99
132
  if isinstance(client, str):
100
133
  client = P115Client(client, check_for_relogin=True)
101
- if app:
102
- if APP_TO_SSOENT.get(app) == client.login_ssoent:
103
- raise ValueError("same login device will cause conflicts")
134
+ if isinstance(app, str):
135
+ if app:
136
+ if app == client.login_app():
137
+ raise ValueError(f"same login device (app={app!r}) will cause conflicts")
138
+ else:
139
+ app = "tv" if client.login_ssoent == "R2" else "alipaymini"
140
+ get_app = repeat(app).__next__
104
141
  else:
105
- app = "tv" if client.login_ssoent == "R2" else "alipaymini"
106
- login = client.login_another_app
107
- def make_client(async_: bool = False):
108
- def call():
142
+ app = tuple(app)
143
+ if client.login_app() in app:
144
+ raise ValueError(f"same login device (app={app!r}) will cause conflicts")
145
+ elif not app:
146
+ app = "tv" if client.login_ssoent == "R2" else "alipaymini"
147
+ get_app = repeat(app).__next__
148
+ else:
149
+ get_app = cycle(app).__next__
150
+ login = client.login_with_app
151
+ def make_cookies(async_: bool = False):
152
+ def gen_step():
109
153
  while True:
154
+ app = get_app()
110
155
  try:
111
- return (yield login(app, async_=async_, **request_kwargs)) # type: ignore
156
+ resp = yield login(
157
+ app,
158
+ async_=async_, # type: ignore
159
+ **request_kwargs,
160
+ )
112
161
  except Exception as e:
113
162
  if not is_timeouterror(e):
114
163
  raise
115
- return run_gen_step(call, async_=async_)
164
+ check_response(resp)
165
+ return {
166
+ "cookie": "; ".join(f"{k}={v}" for k, v in resp["data"]["cookie"].items()),
167
+ "app": app,
168
+ }
169
+ return run_gen_step(gen_step, async_=async_)
170
+ return make_cookies
171
+
172
+
173
+ def generate_client_factory(
174
+ client: str | P115Client,
175
+ app: str | Iterable[str] = "",
176
+ **request_kwargs,
177
+ ) -> Callable:
178
+ """利用一个已登录设备的 client,产生另一个设备的若干 client
179
+
180
+ :param client: 115 客户端或 cookies
181
+ :param app: 自动扫码后绑定的 app(多个则传入一组 app 的可迭代对象)
182
+ :param request_kwargs: 其它请求参数
183
+
184
+ :return: 函数,调用以返回一个 client
185
+ """
186
+ cls = type(client)
187
+ call = generate_cookies_factory(client, app, **request_kwargs)
188
+ def make_client(async_: bool = False):
189
+ def gen_step():
190
+ headers = yield call(async_=async_)
191
+ return cls(headers["cookie"])
192
+ return run_gen_step(gen_step, async_=async_)
116
193
  return make_client
117
194
 
118
195
 
119
- def make_pool(
196
+ def make_pool[T](
120
197
  generate_factory: Callable,
121
- initial_values: Iterable = (),
198
+ heap: None | list[tuple[float, T | ComparedWithID[T]]] = None,
122
199
  cooldown_time: int | float = 1,
200
+ live_time: int | float = inf,
123
201
  lock: bool = True,
124
202
  **request_kwargs,
125
203
  ) -> Callable:
126
- """值の池
204
+ """创建池
127
205
 
128
206
  :param generate_factory: 产生值的工厂函数
129
- :param initial_values: 一组初始值
130
- :param cooldown_time: cookies 的冷却时间
207
+ :param heap: 最小堆,可以包含一组初始值,各是一个元组,包含(上一次获取时刻, 值)
208
+ :param cooldown_time: 值的冷却时间
209
+ :param live_time: 值的存活时间,默认是无穷大
131
210
  :param lock: 是否需要锁
132
211
  :param request_kwargs: 其它请求参数
133
212
 
134
213
  :return: 返回一个函数,调用后返回一个元组,包含 值 和 一个调用以在完成后把 值 返还池中
135
214
  """
136
215
  generate = generate_factory(**request_kwargs)
137
- dq: deque[tuple[Any, float, int]] = deque(((a, time(), 0) for a in initial_values))
138
- push, pop = dq.append, dq.popleft
216
+ if heap is None:
217
+ heap_: list[tuple[float, ComparedWithID[T]]] = []
218
+ else:
219
+ for i, (a, b) in enumerate(heap):
220
+ heap[i] = (a, ComparedWithID(b))
221
+ heapify(heap)
222
+ heap_ = heap # type: ignore
139
223
  def get_value(async_: bool = False):
140
224
  def call():
141
- n = 0
142
- if dq and dq[0][1] + cooldown_time < time():
143
- value, _, n = pop()
144
- elif async_:
145
- value = yield generate(async_=True)
225
+ now = time()
226
+ if not isinf(live_time):
227
+ watermark = now - live_time
228
+ while heap_:
229
+ if heap_[0][0] > watermark:
230
+ break
231
+ heappop(heap_)
232
+ if heap_ and heap_[0][0] + cooldown_time <= now:
233
+ _, val = heappop(heap_)
234
+ value = val.value
146
235
  else:
147
- value = generate()
148
- return value + f"; n={n}", partial(push, (value, time(), n+1))
236
+ if async_:
237
+ value = yield generate(async_=True)
238
+ else:
239
+ value = generate()
240
+ val = ComparedWithID(value)
241
+ return value, partial(heappush, heap_, (time(), val))
149
242
  return run_gen_step(call, async_=async_)
150
243
  if not lock:
151
- setattr(get_value, "deque", dq)
244
+ setattr(get_value, "heap", heap_)
152
245
  return get_value
153
246
  lock_sync = Lock()
154
247
  lock_async = AsyncLock()
@@ -163,35 +256,71 @@ def make_pool(
163
256
  with lock_sync:
164
257
  return get_value()
165
258
  return locked_get_value
166
- setattr(locked_get_value, "deque", dq)
259
+ setattr(locked_get_value, "heap", heap_)
167
260
  return locked_get_value
168
261
 
169
262
 
263
+ def auth_pool(
264
+ client: str | P115Client,
265
+ app_ids: Iterable[int] = range(100195123, 100196659, 2),
266
+ heap: None | list[tuple[float, dict | ComparedWithID[dict]]] = None,
267
+ cooldown_time: int | float = 1,
268
+ live_time: int | float = 7000,
269
+ lock: bool = False,
270
+ **request_kwargs,
271
+ ) -> Callable:
272
+ """authorization 请求头池
273
+
274
+ :param client: 115 客户端或 cookies
275
+ :param app_ids: 一组开放应用的 AppID
276
+ :param heap: 最小堆,可以包含一组初始值,各是一个元组,包含(上一次获取时刻, 值)
277
+ :param cooldown_time: 值的冷却时间
278
+ :param live_time: 值的存活时间,默认是无穷大
279
+ :param lock: 锁,如果不需要锁,传入 False
280
+ :param request_kwargs: 其它请求参数
281
+
282
+ :return: 返回一个函数,调用后返回一个元组,包含值 和 一个调用(以在完成后把值返还池中)
283
+ """
284
+ return make_pool(
285
+ generate_auth_factory,
286
+ client=client,
287
+ app_ids=app_ids,
288
+ heap=heap,
289
+ cooldown_time=cooldown_time,
290
+ live_time=live_time,
291
+ lock=lock,
292
+ **request_kwargs,
293
+ )
294
+
295
+
170
296
  def cookies_pool(
171
297
  client: str | P115Client,
172
- app: None | str = None,
173
- initial_values: Iterable[str] = (),
298
+ app: str | Iterable[str] = "",
299
+ heap: None | list[tuple[float, dict | ComparedWithID[dict]]] = None,
174
300
  cooldown_time: int | float = 1,
301
+ live_time: int | float = inf,
175
302
  lock: bool = False,
176
303
  **request_kwargs,
177
304
  ) -> Callable:
178
- """cookies
305
+ """cookie 请求头池
179
306
 
180
307
  :param client: 115 客户端或 cookies
181
- :param app: 自动扫码后绑定的 app
182
- :param initial_values: 一组初始值
183
- :param cooldown_time: cookies 的冷却时间
308
+ :param app: 自动扫码后绑定的 app(多个则传入一组 app 的可迭代对象)
309
+ :param heap: 最小堆,可以包含一组初始值,各是一个元组,包含(上一次获取时刻, 值)
310
+ :param cooldown_time: 值的冷却时间
311
+ :param live_time: 值的存活时间,默认是无穷大
184
312
  :param lock: 锁,如果不需要锁,传入 False
185
313
  :param request_kwargs: 其它请求参数
186
314
 
187
- :return: 返回一个函数,调用后返回一个元组,包含 cookies 一个调用以在完成后把 cookies 返还池中
315
+ :return: 返回一个函数,调用后返回一个元组,包含值一个调用(以在完成后把值返还池中)
188
316
  """
189
317
  return make_pool(
190
318
  generate_cookies_factory,
191
319
  client=client,
192
320
  app=app,
193
- initial_values=initial_values,
321
+ heap=heap,
194
322
  cooldown_time=cooldown_time,
323
+ live_time=live_time,
195
324
  lock=lock,
196
325
  **request_kwargs,
197
326
  )
@@ -199,84 +328,70 @@ def cookies_pool(
199
328
 
200
329
  def client_pool(
201
330
  client: str | P115Client,
202
- app: None | str = None,
203
- initial_values: Iterable[P115Client] = (),
331
+ app: str | Iterable[str] = "",
332
+ heap: None | list[tuple[float, P115Client | ComparedWithID[P115Client]]] = None,
204
333
  cooldown_time: int | float = 1,
334
+ live_time: int | float = inf,
205
335
  lock: bool = False,
206
336
  **request_kwargs,
207
337
  ) -> Callable:
208
338
  """client 池
209
339
 
210
340
  :param client: 115 客户端或 cookies
211
- :param app: 自动扫码后绑定的 app
212
- :param initial_values: 一组初始值
213
- :param cooldown_time: cookies 的冷却时间
341
+ :param app: 自动扫码后绑定的 app(多个则传入一组 app 的可迭代对象)
342
+ :param heap: 最小堆,可以包含一组初始值,各是一个元组,包含(上一次获取时刻, 值)
343
+ :param cooldown_time: 值的冷却时间
344
+ :param live_time: 值的存活时间,默认是无穷大
214
345
  :param lock: 锁,如果不需要锁,传入 False
215
346
  :param request_kwargs: 其它请求参数
216
347
 
217
- :return: 返回一个函数,调用后返回一个元组,包含 client 一个调用以在完成后把 client 返还池中
348
+ :return: 返回一个函数,调用后返回一个元组,包含值一个调用(以在完成后把值返还池中)
218
349
  """
219
350
  return make_pool(
220
351
  generate_client_factory,
221
352
  client=client,
222
353
  app=app,
223
- initial_values=initial_values,
354
+ heap=heap,
224
355
  cooldown_time=cooldown_time,
356
+ live_time=live_time,
225
357
  lock=lock,
226
358
  **request_kwargs,
227
359
  )
228
360
 
229
361
 
230
- def call_wrap_with_cookies_pool(
231
- get_cookies: Callable,
232
- /,
233
- func: Callable = P115Client("").fs_files,
234
- check: bool | Callable = True,
235
- base_url_seq: None | Sequence = None,
236
- ) -> Callable:
237
- """包装函数,使得用 cookies 池执行请求
362
+ def call_wrap_with_pool(get_cert_headers: Callable, /, func: Callable) -> Callable:
363
+ """包装函数,用认证信息请求头的分发池执行请求
238
364
 
239
- :param get_cookies: 获取 cookies 的函数
365
+ :param get_cert_headers: 获取认证信息的请求头的函数
240
366
  :param func: 执行请求的函数
241
367
  """
242
368
  def wrapper(*args, headers=None, async_: bool = False, **kwds):
243
- def call():
369
+ def gen_step():
244
370
  nonlocal headers
245
- if async_:
246
- cookies, revert = yield get_cookies(async_=True)
247
- else:
248
- cookies, revert = get_cookies()
249
- if "base_url" not in kwds and base_url_seq:
250
- kwds["base_url"] = base_url_seq[int(cookies.rpartition("=")[-1]) % len(base_url_seq)]
251
371
  while True:
372
+ if async_:
373
+ cert_headers, revert = yield get_cert_headers(async_=True)
374
+ else:
375
+ cert_headers, revert = get_cert_headers()
252
376
  if headers:
253
- headers = dict(headers, Cookie=cookies)
377
+ headers = dict(headers, **cert_headers)
254
378
  else:
255
- headers = {"Cookie": cookies}
379
+ headers = cert_headers
256
380
  try:
257
381
  if async_:
258
382
  resp = yield func(*args, headers=headers, async_=True, **kwds)
259
383
  else:
260
384
  resp = func(*args, headers=headers, **kwds)
261
- if check:
262
- if check is True:
263
- check_response(resp)
264
- else:
265
- check(resp)
266
- revert()
385
+ if not isinstance(resp, dict) or resp.get("errno") != 40101004:
386
+ revert()
267
387
  return resp
268
388
  except BaseException as e:
269
389
  if isinstance(e, P115OSError) and e.args[1].get("errno") == 40101004:
270
390
  raise
271
- elif isinstance(e, AuthenticationError) or get_status(e) == 405:
272
- if async_:
273
- cookies, revert = yield get_cookies(async_=True)
274
- else:
275
- cookies, revert = get_cookies()
276
- continue
277
- revert()
278
- raise
279
- return run_gen_step(call, async_=async_)
391
+ if not isinstance(e, (AuthenticationError, LoginError)) and get_status(e) != 405:
392
+ revert()
393
+ raise
394
+ return run_gen_step(gen_step, async_=async_)
280
395
  return update_wrapper(wrapper, func)
281
396
 
282
397
  # TODO: 需要完整的类型签名
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: p115client
3
- Version: 0.0.5.8.7
3
+ Version: 0.0.5.9.1
4
4
  Summary: Python 115 webdisk client.
5
5
  Home-page: https://github.com/ChenyangGao/p115client
6
6
  License: MIT
@@ -1,23 +1,24 @@
1
1
  LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
2
  p115client/__init__.py,sha256=1mx7njuAlqcuEWONTjSiiGnXyyNyqOcJyNX1FMHqQ-4,214
3
3
  p115client/_upload.py,sha256=DOckFLU_P7Fl0BNu_0-2od6pPsCnzroYY6zZE5_EMnM,30735
4
- p115client/client.py,sha256=GhLFoGzmZunVqMajS6xZZqs5k-I_2sR9LQlP102MFjE,718070
4
+ p115client/client.py,sha256=XsOy9BVOqu2jof7rGIMUldVZSiuQNAku379kIiZPr-E,721350
5
5
  p115client/const.py,sha256=maIZfJAiUuEnXIKc8TMAyW_UboDUJPwYpPS8LjPFp_U,4321
6
6
  p115client/exception.py,sha256=Ugjr__aSlYRDYwoOz7273ngV-gFX2z-ohsJmCba8nnQ,2657
7
7
  p115client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- p115client/tool/__init__.py,sha256=2YrKoAcFYOuqu2nUBoPVhxMOseAvcLE_LcnbZV11UKw,324
8
+ p115client/tool/__init__.py,sha256=x1x4yt3Ylim5CbFO4M-Fu62Gd99pUFpb3-5ssL6l-_Y,346
9
9
  p115client/tool/download.py,sha256=DneVsbryn4d47RkS62ghF5Xz8vkDq6U0n2hj1W5g4iY,61176
10
10
  p115client/tool/edit.py,sha256=3hQ5J3hHQx4yNsGcWSechBYAvZRSQUxfXLXuqXiDKmk,17789
11
11
  p115client/tool/export_dir.py,sha256=iMnKtnESi8HKvW9WhIvOdEoMXSBpAnhFlGeyKXHpQbE,24545
12
12
  p115client/tool/fs_files.py,sha256=hkezLKrtTAGPDkPxwq6jMrm8s2-unHZQBR7cDvh41qs,16027
13
+ p115client/tool/history.py,sha256=2S26BH7uBNfVHlbYFNy-aNOLMcM-HrlR7TLHAZacUJI,7458
13
14
  p115client/tool/iterdir.py,sha256=T6bsD8A896nHgVxKYu2dkIo-5Az2svL4iLm2uHQWyHg,187517
14
- p115client/tool/life.py,sha256=SJ1dAvRYCWTUz1gPrCWv-_G1Rq5yPfxDgY7KT-2UP4A,17528
15
- p115client/tool/pool.py,sha256=vM5ItMxELtT7_bvbmdhwWj81rQ0zyaj3uPMny4KDw_E,9757
15
+ p115client/tool/life.py,sha256=9ncoB2dNibhgOjPb9mHr873fCZmb65ZeteVLH4Tsujc,17330
16
+ p115client/tool/pool.py,sha256=_3eFHTHW5Bhs17AwF8awitE4n1UuvFGIkZUZ-saJ7zQ,14233
16
17
  p115client/tool/request.py,sha256=SWsezW9EYZGS3R-TbZxMG-8bN3YWJ0-GzgvKlvRBSCM,7042
17
18
  p115client/tool/upload.py,sha256=qK1OQYxP-Faq2eMDhc5sBXJiSr8m8EZ_gb0O_iA2TrI,15915
18
19
  p115client/tool/xys.py,sha256=n89n9OLBXx6t20L61wJgfrP6V4jW3sHgyaQNBLdUwUQ,3578
19
20
  p115client/type.py,sha256=e4g9URQBE23XN2dGomldj8wC6NlDWBBSVC5Bmd8giBc,5993
20
- p115client-0.0.5.8.7.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
21
- p115client-0.0.5.8.7.dist-info/METADATA,sha256=v74Y9_1ZIk2j2CvMT-9q4RIUDH1FqsRnYCSbqt2ZifU,8233
22
- p115client-0.0.5.8.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
- p115client-0.0.5.8.7.dist-info/RECORD,,
21
+ p115client-0.0.5.9.1.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
22
+ p115client-0.0.5.9.1.dist-info/METADATA,sha256=_NxUFNOa5O4B2Ek-NsKRlEFhR-sWQ5OUZT8G2Xe1nFY,8233
23
+ p115client-0.0.5.9.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
24
+ p115client-0.0.5.9.1.dist-info/RECORD,,