p115client 0.0.5.12.3__py3-none-any.whl → 0.0.5.13.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/edit.py CHANGED
@@ -8,14 +8,14 @@ __all__ = [
8
8
  ]
9
9
  __doc__ = "这个模块提供了一些和修改文件或目录信息有关的函数"
10
10
 
11
- from collections.abc import AsyncIterable, AsyncIterator, Coroutine, Iterable, Iterator
11
+ from collections.abc import AsyncIterable, Coroutine, Iterable
12
12
  from functools import partial
13
- from typing import cast, overload, Any, Literal
13
+ from typing import overload, Any, Literal
14
14
 
15
- from asynctools import to_list
16
- from concurrenttools import taskgroup_map, threadpool_map
17
- from iterutils import chunked, run_gen_step, foreach, through, async_through
15
+ from concurrenttools import conmap
16
+ from iterutils import chunked, map as do_map, run_gen_step, through
18
17
  from p115client import check_response, P115Client
18
+ from p115pickcode import to_id
19
19
 
20
20
 
21
21
  @overload
@@ -61,7 +61,7 @@ def update_abstract(
61
61
  """批量设置文件或目录
62
62
 
63
63
  :param client: 115 客户端或 cookies
64
- :param ids: 一组文件或目录的 id
64
+ :param ids: 一组文件或目录的 id 或 pickcode
65
65
  :param method: 方法名
66
66
  :param value: 要设置的值
67
67
  :param batch_size: 批次大小,分批次,每次提交的 id 数
@@ -73,24 +73,17 @@ def update_abstract(
73
73
  client = P115Client(client, check_for_relogin=True)
74
74
  if max_workers is None or max_workers <= 0:
75
75
  max_workers = 20 if async_ else None
76
+ ids = do_map(to_id, ids)
76
77
  def gen_step():
77
78
  setter = partial(getattr(client, method), async_=async_, **request_kwargs)
78
79
  def call(batch, /):
79
80
  return check_response(setter(batch, value))
80
- if max_workers == 1:
81
- yield foreach(call, chunked(ids, batch_size))
82
- elif async_:
83
- yield async_through(taskgroup_map(
84
- call,
85
- chunked(ids, batch_size),
86
- max_workers=max_workers,
87
- ))
88
- else:
89
- through(threadpool_map(
90
- call,
91
- chunked(ids, batch_size),
92
- max_workers=max_workers
93
- ))
81
+ yield through(conmap(
82
+ call,
83
+ chunked(ids, batch_size),
84
+ max_workers=max_workers,
85
+ async_=async_,
86
+ ))
94
87
  return run_gen_step(gen_step, async_)
95
88
 
96
89
 
@@ -611,28 +604,27 @@ def batch_unstar(
611
604
  raise KeyError
612
605
  def gen_step():
613
606
  from .iterdir import _iter_fs_files
614
- it = _iter_fs_files(
615
- client,
616
- payload={"cid": 0, "count_folders": 1, "cur": 0, "fc_mix": 0, "offset": 0, "show_dir": 1, "star": 1},
617
- ensure_file=ensure_file,
618
- app=app,
619
- cooldown=0.5,
620
- async_=async_,
621
- **request_kwargs,
622
- )
623
- if async_:
624
- ids: list[int] = yield to_list(get_id(info) async for info in cast(AsyncIterator[dict], it))
625
- else:
626
- ids = [get_id(info) for info in cast(Iterator[dict], it)]
627
607
  yield update_star(
628
608
  client,
629
- ids,
609
+ do_map(get_id, _iter_fs_files(
610
+ client,
611
+ payload={
612
+ "cid": 0, "count_folders": 1, "cur": 0, "fc_mix": 0,
613
+ "offset": 0, "show_dir": 1, "star": 1
614
+ },
615
+ ensure_file=ensure_file,
616
+ app=app,
617
+ cooldown=0.5,
618
+ async_=async_,
619
+ **request_kwargs,
620
+ )),
630
621
  star=False,
631
622
  batch_size=batch_size,
632
623
  max_workers=max_workers,
633
624
  app=app,
634
- async_=async_, # type: ignore
625
+ async_=async_,
635
626
  **request_kwargs,
636
627
  )
637
628
  return run_gen_step(gen_step, async_)
638
629
 
630
+ # TODO: 上面这些,要支持 open 接口
@@ -3,13 +3,16 @@
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
  __all__ = [
6
- "parse_export_dir_as_dict_iter", "parse_export_dir_as_path_iter", "parse_export_dir_as_patht_iter",
7
- "export_dir", "export_dir_result", "export_dir_parse_iter",
6
+ "parse_export_dir_as_dict_iter", "parse_export_dir_as_path_iter",
7
+ "parse_export_dir_as_patht_iter", "export_dir", "export_dir_result",
8
+ "export_dir_parse_iter",
8
9
  ]
9
10
  __doc__ = "这个模块提供了一些和导出目录树有关的函数"
10
11
 
11
12
  from asyncio import sleep as async_sleep
12
- from collections.abc import AsyncIterable, AsyncIterator, Callable, Coroutine, Iterable, Iterator
13
+ from collections.abc import (
14
+ AsyncIterable, AsyncIterator, Callable, Coroutine, Iterable, Iterator,
15
+ )
13
16
  from functools import partial
14
17
  from io import BufferedReader, TextIOWrapper
15
18
  from itertools import count
@@ -20,8 +23,12 @@ from typing import cast, overload, Any, Final, IO, Literal
20
23
 
21
24
  from asynctools import ensure_async, ensure_aiter
22
25
  from filewrap import AsyncBufferedReader, AsyncTextIOWrapper
23
- from iterutils import backgroud_loop, context, run_gen_step, run_gen_step_iter, Yield, YieldFrom
26
+ from iterutils import (
27
+ as_gen_step, backgroud_loop, context, run_gen_step, run_gen_step_iter,
28
+ Yield, YieldFrom,
29
+ )
24
30
  from p115client import check_response, P115Client
31
+ from p115pickcode import to_id, is_valid_pickcode
25
32
 
26
33
 
27
34
  CRE_TREE_PREFIX_match: Final = re_compile(r"^(?:\| )+\|-(.*)").match
@@ -177,7 +184,7 @@ def parse_export_dir_as_path_iter(
177
184
  if escape:
178
185
  from posixpatht import escape
179
186
  else:
180
- from .iterdir import posix_escape_name
187
+ from .util import posix_escape_name as escape
181
188
  escape = cast(None | Callable[[str], str], escape)
182
189
  def gen_step():
183
190
  it = ensure_aiter(file, threaded=True) if async_ else file
@@ -190,10 +197,12 @@ def parse_export_dir_as_path_iter(
190
197
  root = root.removesuffix("\n")[3:]
191
198
  if root == "根目录":
192
199
  stack = [""]
200
+ root = "/"
193
201
  else:
194
202
  if escape is not None:
195
203
  root = escape(root)
196
- stack = ["/" + root]
204
+ root = "/" + root
205
+ stack = [root]
197
206
  push = stack.append
198
207
  try:
199
208
  depth = 0
@@ -208,7 +217,7 @@ def parse_export_dir_as_path_iter(
208
217
  elif depth:
209
218
  yield Yield(stack[depth])
210
219
  else:
211
- yield "/" if root == "根目录" else root
220
+ yield Yield(root)
212
221
  name = m[1]
213
222
  depth = (len(line) - len(name)) // 2 - 1
214
223
  if escape is not None:
@@ -354,8 +363,8 @@ def export_dir(
354
363
  """导出目录树
355
364
 
356
365
  :param client: 115 客户端或 cookies
357
- :param export_file_ids: 待导出的目录 id 或 路径(如果有多个,需传入可迭代对象)
358
- :param target_pid: 导出到的目标目录 id 或 路径
366
+ :param export_file_ids: 待导出的目录 id 或 pickcode
367
+ :param target_pid: 导出到的目标目录 id 或 pickcode
359
368
  :param layer_limit: 层级深度,小于等于 0 时不限
360
369
  :param async_: 是否异步
361
370
  :param request_kwargs: 其它请求参数
@@ -366,41 +375,14 @@ def export_dir(
366
375
  client = P115Client(client, check_for_relogin=True)
367
376
  def gen_step():
368
377
  nonlocal export_file_ids, target_pid
369
- from .iterdir import get_id_to_path
370
378
  if isinstance(export_file_ids, int):
371
379
  pass
372
380
  elif isinstance(export_file_ids, str):
373
- export_file_ids = yield get_id_to_path(
374
- client,
375
- export_file_ids,
376
- ensure_file=False,
377
- async_=async_,
378
- **request_kwargs,
379
- )
381
+ if "," not in export_file_ids:
382
+ export_file_ids = to_id(export_file_ids)
380
383
  else:
381
- cids: set[int] = set()
382
- add_cid = cids.add
383
- for cid in export_file_ids:
384
- if isinstance(cid, str):
385
- cid = yield get_id_to_path(
386
- client,
387
- cid,
388
- ensure_file=False,
389
- async_=async_,
390
- **request_kwargs,
391
- )
392
- add_cid(cast(int, cid))
393
- if not cids:
394
- raise ValueError("`export_file_ids` is empty")
395
- export_file_ids = ",".join(map(str, cids))
396
- if isinstance(target_pid, str):
397
- target_pid = yield get_id_to_path(
398
- client,
399
- target_pid,
400
- ensure_file=False,
401
- async_=async_,
402
- **request_kwargs,
403
- )
384
+ export_file_ids = ",".join(str(to_id(eid)) for eid in export_file_ids)
385
+ target_pid = to_id(target_pid)
404
386
  payload = {"file_ids": export_file_ids, "target": f"U_0_{target_pid}"}
405
387
  if layer_limit > 0:
406
388
  payload["layer_limit"] = layer_limit
@@ -544,9 +526,9 @@ def export_dir_parse_iter(
544
526
  """导出目录树到文件,读取文件并解析后返回迭代器,关闭后自动删除导出的文件
545
527
 
546
528
  :param client: 115 客户端或 cookies
547
- :param export_file_ids: 待导出的目录 id 或 路径(如果有多个,需传入可迭代对象)
529
+ :param export_file_ids: 待导出的目录 id、pickcode 或 路径(如果有多个,需传入可迭代对象)
548
530
  :param export_id: 优先级高于 `export_file_ids`,之前提交的 `export_dir` 任务的 id,如果是 str,则视为导出的目录树文件的提取码(因此无需导出)
549
- :param target_pid: 导出到的目标目录 id 或 路径
531
+ :param target_pid: 导出到的目标目录 id 或 pickcode
550
532
  :param layer_limit: 层级深度,小于等于 0 时不限
551
533
  :param parse_iter: 解析打开的二进制文件,返回可迭代对象
552
534
  :param delete: 最终删除目录树文件(如果 export_id 为 str(即提取码),则这个值不生效,必不删除)
@@ -115,20 +115,20 @@ def iter_fs_files(
115
115
  "limit": first_page_size, "show_dir": 1, **payload,
116
116
  }
117
117
  cid = int(payload["cid"])
118
- @as_gen_step
119
- def get_files(payload: dict, /):
118
+ def gen_step():
120
119
  nonlocal count
121
120
  while True:
122
- try:
123
- resp = yield fs_files(payload, async_=async_)
124
- check_response(resp)
125
- except DataError:
126
- if payload["limit"] <= 1150:
127
- raise
128
- payload["limit"] -= 1_000
129
- if payload["limit"] < 1150:
130
- payload["limit"] = 1150
131
- else:
121
+ while True:
122
+ try:
123
+ resp = yield fs_files(payload, async_=async_)
124
+ check_response(resp)
125
+ except DataError:
126
+ if payload["limit"] <= 1150:
127
+ raise
128
+ payload["limit"] -= 1_000
129
+ if payload["limit"] < 1150:
130
+ payload["limit"] = 1150
131
+ continue
132
132
  if cid and int(resp["path"][-1]["cid"]) != cid:
133
133
  if count < 0:
134
134
  raise NotADirectoryError(ENOTDIR, cid)
@@ -146,10 +146,7 @@ def iter_fs_files(
146
146
  count = count_new
147
147
  if callback is not None:
148
148
  resp["callback"] = yield callback(resp)
149
- return resp
150
- def gen_step():
151
- while True:
152
- resp = yield get_files(payload)
149
+ break
153
150
  payload["limit"] = page_size
154
151
  yield Yield(resp)
155
152
  payload["offset"] += len(resp["data"])
@@ -2,26 +2,42 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __all__ = ["iter_history_list_once", "iter_history_list"]
5
+ __all__ = [
6
+ "HISTORY_NAME_TO_TYPE", "HISTORY_TYPE_TO_NAME",
7
+ "iter_history_once", "iter_history", "iter_history_list",
8
+ ]
6
9
  __doc__ = "这个模块提供了一些和 115 的历史记录有关的函数"
7
10
 
8
11
  from asyncio import sleep as async_sleep
9
- from collections.abc import AsyncIterator, Iterator
12
+ from collections.abc import AsyncIterator, Container, Iterator
10
13
  from functools import partial
11
14
  from itertools import cycle
12
15
  from time import time, sleep
13
- from typing import overload, Literal
16
+ from typing import overload, Final, Literal
14
17
 
15
18
  from iterutils import run_gen_step_iter, with_iter_next, Yield
16
19
  from p115client import check_response, P115Client
17
20
 
18
21
 
22
+ #: 115 生活操作事件名称到类型的映射
23
+ HISTORY_NAME_TO_TYPE: Final = {
24
+ "offline_download": 2,
25
+ "browse_video": 3,
26
+ "upload": 4,
27
+ "receive": 7,
28
+ "move": 8,
29
+ }
30
+ #: 115 生活操作事件类型到名称的映射
31
+ HISTORY_TYPE_TO_NAME: Final = {v: k for k, v in HISTORY_NAME_TO_TYPE.items()}
32
+
33
+
19
34
  @overload
20
- def iter_history_list_once(
35
+ def iter_history_once(
21
36
  client: str | P115Client,
22
37
  from_time: int | float = 0,
23
38
  from_id: int = 0,
24
39
  type: int | str = 0,
40
+ ignore_types: None | Container[int] = None,
25
41
  first_batch_size = 0,
26
42
  app: str = "web",
27
43
  cooldown: int | float = 0,
@@ -31,11 +47,12 @@ def iter_history_list_once(
31
47
  ) -> Iterator[dict]:
32
48
  ...
33
49
  @overload
34
- def iter_history_list_once(
50
+ def iter_history_once(
35
51
  client: str | P115Client,
36
52
  from_time: int | float = 0,
37
53
  from_id: int = 0,
38
54
  type: int | str = 0,
55
+ ignore_types: None | Container[int] = None,
39
56
  first_batch_size = 0,
40
57
  app: str = "web",
41
58
  cooldown: int | float = 0,
@@ -44,11 +61,12 @@ def iter_history_list_once(
44
61
  **request_kwargs,
45
62
  ) -> AsyncIterator[dict]:
46
63
  ...
47
- def iter_history_list_once(
64
+ def iter_history_once(
48
65
  client: str | P115Client,
49
66
  from_time: int | float = 0,
50
67
  from_id: int = 0,
51
68
  type: int | str = 0,
69
+ ignore_types: None | Container[int] = None,
52
70
  first_batch_size = 0,
53
71
  app: str = "web",
54
72
  cooldown: int | float = 0,
@@ -73,6 +91,7 @@ def iter_history_list_once(
73
91
  - 接收: 7
74
92
  - 移动: 8
75
93
 
94
+ :param ignore_types: 一组要被忽略的操作事件类型代码,仅当 `type` 为空时生效
76
95
  :param first_batch_size: 首批的拉取数目
77
96
  :param app: 使用某个 app (设备)的接口
78
97
  :param cooldown: 冷却时间,大于 0 时,两次接口调用之间至少间隔这么多秒
@@ -105,7 +124,8 @@ def iter_history_list_once(
105
124
  if from_id and event_id <= from_id or from_time and int(event["update_time"]) < from_time:
106
125
  return
107
126
  if event_id not in seen:
108
- yield Yield(event)
127
+ if type or not ignore_types or event["type"] not in ignore_types:
128
+ yield Yield(event)
109
129
  seen_add(event_id)
110
130
  offset += len(events)
111
131
  if offset >= int(resp["data"]["total"]):
@@ -123,11 +143,12 @@ def iter_history_list_once(
123
143
 
124
144
 
125
145
  @overload
126
- def iter_history_list(
146
+ def iter_history(
127
147
  client: str | P115Client,
128
148
  from_time: int | float = 0,
129
149
  from_id: int = 0,
130
150
  type: int | str = 0,
151
+ ignore_types: None | Container[int] = None,
131
152
  app: str = "web",
132
153
  cooldown: int | float = 0,
133
154
  interval: int | float = 0,
@@ -137,11 +158,12 @@ def iter_history_list(
137
158
  ) -> Iterator[dict]:
138
159
  ...
139
160
  @overload
140
- def iter_history_list(
161
+ def iter_history(
141
162
  client: str | P115Client,
142
163
  from_time: int | float = 0,
143
164
  from_id: int = 0,
144
165
  type: int | str = 0,
166
+ ignore_types: None | Container[int] = None,
145
167
  app: str = "web",
146
168
  cooldown: int | float = 0,
147
169
  interval: int | float = 0,
@@ -150,11 +172,12 @@ def iter_history_list(
150
172
  **request_kwargs,
151
173
  ) -> AsyncIterator[dict]:
152
174
  ...
153
- def iter_history_list(
175
+ def iter_history(
154
176
  client: str | P115Client,
155
177
  from_time: int | float = 0,
156
178
  from_id: int = 0,
157
179
  type: int | str = 0,
180
+ ignore_types: None | Container[int] = None,
158
181
  app: str = "web",
159
182
  cooldown: int | float = 0,
160
183
  interval: int | float = 0,
@@ -179,6 +202,7 @@ def iter_history_list(
179
202
  - 接收: 7
180
203
  - 移动: 8
181
204
 
205
+ :param ignore_types: 一组要被忽略的操作事件类型代码,仅当 `type` 为空时生效
182
206
  :param cooldown: 冷却时间,大于 0 时,两次接口调用之间至少间隔这么多秒
183
207
  :param interval: 两个批量拉取之间的睡眠时间间隔,如果小于等于 0,则不睡眠
184
208
  :param app: 使用某个 app (设备)的接口
@@ -202,7 +226,7 @@ def iter_history_list(
202
226
  yield async_sleep(interval)
203
227
  else:
204
228
  sleep(interval)
205
- with with_iter_next(iter_history_list_once(
229
+ with with_iter_next(iter_history_once(
206
230
  client,
207
231
  from_time,
208
232
  from_id,
@@ -219,6 +243,106 @@ def iter_history_list(
219
243
  from_id = int(event["id"])
220
244
  from_time = int(event["update_time"])
221
245
  sub_first_loop = False
246
+ if not type and ignore_types and event["type"] in ignore_types:
247
+ continue
222
248
  yield Yield(event)
223
249
  return run_gen_step_iter(gen_step, async_)
224
250
 
251
+
252
+ @overload
253
+ def iter_history_list(
254
+ client: str | P115Client,
255
+ from_time: int | float = 0,
256
+ from_id: int = 0,
257
+ type: int | str = 0,
258
+ ignore_types: None | Container[int] = None,
259
+ app: str = "web",
260
+ cooldown: int | float = 0,
261
+ *,
262
+ async_: Literal[False] = False,
263
+ **request_kwargs,
264
+ ) -> Iterator[dict]:
265
+ ...
266
+ @overload
267
+ def iter_history_list(
268
+ client: str | P115Client,
269
+ from_time: int | float = 0,
270
+ from_id: int = 0,
271
+ type: int | str = 0,
272
+ ignore_types: None | Container[int] = None,
273
+ app: str = "web",
274
+ cooldown: int | float = 0,
275
+ *,
276
+ async_: Literal[True],
277
+ **request_kwargs,
278
+ ) -> AsyncIterator[dict]:
279
+ ...
280
+ def iter_history_list(
281
+ client: str | P115Client,
282
+ from_time: int | float = 0,
283
+ from_id: int = 0,
284
+ type: int | str = 0,
285
+ ignore_types: None | Container[int] = None,
286
+ app: str = "web",
287
+ cooldown: int | float = 0,
288
+ *,
289
+ async_: Literal[False, True] = False,
290
+ **request_kwargs,
291
+ ) -> AsyncIterator[dict] | Iterator[dict]:
292
+ """持续拉取 115 的历史记录
293
+
294
+ :param client: 115 客户端或 cookies
295
+ :param from_time: 开始时间(含),若为 0 则从当前时间开始,若小于 0 则从最早开始
296
+ :param from_id: 开始的事件 id (不含)
297
+ :param type: 拉取指定类型的历史记录(??表示还未搞清楚),多个用逗号 "," 隔开
298
+
299
+ - 全部: 0
300
+ - ??: 1
301
+ - 离线下载: 2
302
+ - 播放视频: 3
303
+ - 上传: 4
304
+ - ??: 5
305
+ - ??: 6
306
+ - 接收: 7
307
+ - 移动: 8
308
+
309
+ :param ignore_types: 一组要被忽略的操作事件类型代码,仅当 `type` 为空时生效
310
+ :param cooldown: 冷却时间,大于 0 时,两次接口调用之间至少间隔这么多秒
311
+ :param app: 使用某个 app (设备)的接口
312
+ :param async_: 是否异步
313
+ :param request_kwargs: 其它请求参数
314
+
315
+ :return: 迭代器,产生 115 的历史记录数据字典
316
+ """
317
+ if isinstance(client, str):
318
+ client = P115Client(client, check_for_relogin=True)
319
+ def gen_step():
320
+ nonlocal from_time, from_id
321
+ if from_time == 0:
322
+ from_time = time()
323
+ while True:
324
+ ls: list[dict] = []
325
+ push = ls.append
326
+ with with_iter_next(iter_history_once(
327
+ client,
328
+ from_time,
329
+ from_id,
330
+ type=type,
331
+ app=app,
332
+ cooldown=cooldown,
333
+ async_=async_,
334
+ **request_kwargs,
335
+ )) as get_next:
336
+ first_loop = True
337
+ while True:
338
+ event = yield get_next()
339
+ if first_loop:
340
+ from_id = int(event["id"])
341
+ from_time = int(event["update_time"])
342
+ first_loop = False
343
+ if not type and ignore_types and event["type"] in ignore_types:
344
+ continue
345
+ push(event)
346
+ yield Yield(ls)
347
+ return run_gen_step_iter(gen_step, async_)
348
+