p115client 0.0.5.9.3__py3-none-any.whl → 0.0.5.10.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/const.py CHANGED
@@ -1,16 +1,28 @@
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
- __all = ["AVAILABLE_APPS", "APP_TO_SSOENT", "SSOENT_TO_APP", "CLIENT_API_MAP"]
7
+ __all__ = [
8
+ "AVAILABLE_APP_IDS", "AVAILABLE_APPS", "APP_TO_SSOENT", "SSOENT_TO_APP",
9
+ "CLIENT_API_MAP", "CLASS_TO_TYPE", "SUFFIX_TO_TYPE", "errno",
10
+ ]
6
11
 
12
+ from enum import IntEnum
7
13
  from typing import Final
8
14
 
15
+
16
+ #: 可用的 AppID 列表
17
+ AVAILABLE_APP_IDS = range(100195123, 100196837, 2)
18
+
19
+
9
20
  #: 目前可用的登录设备
10
21
  AVAILABLE_APPS: Final[tuple[str, ...]] = (
11
22
  "web", "ios", "115ios", "android", "115android", "115ipad", "tv", "qandroid",
12
23
  "windows", "mac", "linux", "wechatmini", "alipaymini", "harmony",
13
24
  )
25
+
14
26
  #: 目前已知的登录设备和对应的 ssoent
15
27
  APP_TO_SSOENT: Final[dict[str, str]] = {
16
28
  "web": "A1",
@@ -33,6 +45,7 @@ APP_TO_SSOENT: Final[dict[str, str]] = {
33
45
  "alipaymini": "R2",
34
46
  "harmony": "S1",
35
47
  }
48
+
36
49
  #: 目前已知的 ssoent 和对应的登录设备,一部分因为不知道具体的设备名,所以使用目前可用的设备名,作为临时代替
37
50
  SSOENT_TO_APP: Final[dict[str, str]] = {
38
51
  "A1": "web",
@@ -59,8 +72,10 @@ SSOENT_TO_APP: Final[dict[str, str]] = {
59
72
  "R2": "alipaymini",
60
73
  "S1": "harmony",
61
74
  }
75
+
62
76
  #: 所有已封装的 115 接口以及对应的方法名
63
77
  CLIENT_API_MAP: Final[dict[str, str]] = {}
78
+
64
79
  #: 文件的 class 属性对应的所属类型的整数代码
65
80
  CLASS_TO_TYPE: Final[dict[str, int]] = {
66
81
  "JG_DOC": 1,
@@ -79,6 +94,7 @@ CLASS_TO_TYPE: Final[dict[str, int]] = {
79
94
  "JG_BOOK": 7,
80
95
  "BOOK": 7
81
96
  }
97
+
82
98
  #: 文件后缀对应的所属类型的整数代码(尚需补充)
83
99
  SUFFIX_TO_TYPE: Final[dict[str, int]] = {
84
100
  ".ass": 1,
@@ -203,3 +219,176 @@ SUFFIX_TO_TYPE: Final[dict[str, int]] = {
203
219
  ".mobi": 7,
204
220
  ".prc": 7,
205
221
  }
222
+
223
+ class errno(IntEnum):
224
+ """OSError 的错误码的枚举
225
+
226
+ .. admonition:: Reference
227
+
228
+ https://docs.python.org/3/library/errno.html
229
+ """
230
+ EPERM = 1
231
+ ENOENT = 2
232
+ ESRCH = 3
233
+ EINTR = 4
234
+ EIO = 5
235
+ ENXIO = 6
236
+ E2BIG = 7
237
+ ENOEXEC = 8
238
+ EBADF = 9
239
+ ECHILD = 10
240
+ EAGAIN = 11
241
+ ENOMEM = 12
242
+ EACCES = 13
243
+ EFAULT = 14
244
+ ENOTBLK = 15
245
+ EBUSY = 16
246
+ EEXIST = 17
247
+ EXDEV = 18
248
+ ENODEV = 19
249
+ ENOTDIR = 20
250
+ EISDIR = 21
251
+ EINVAL = 22
252
+ ENFILE = 23
253
+ EMFILE = 24
254
+ ENOTTY = 25
255
+ ETXTBSY = 26
256
+ EFBIG = 27
257
+ ENOSPC = 28
258
+ ESPIPE = 29
259
+ EROFS = 30
260
+ EMLINK = 31
261
+ EPIPE = 32
262
+ EDOM = 33
263
+ ERANGE = 34
264
+ EDEADLK = 35
265
+ ENAMETOOLONG = 36
266
+ ENOLCK = 37
267
+ ENOSYS = 38
268
+ ENOTEMPTY = 39
269
+ ELOOP = 40
270
+ EWOULDBLOCK = 41
271
+ ENOMSG = 42
272
+ EIDRM = 43
273
+ ECHRNG = 44
274
+ EL2NSYNC = 45
275
+ EL3HLT = 46
276
+ EL3RST = 47
277
+ ELNRNG = 48
278
+ EUNATCH = 49
279
+ ENOCSI = 50
280
+ EL2HLT = 51
281
+ EBADE = 52
282
+ EBADR = 53
283
+ EXFULL = 54
284
+ ENOANO = 55
285
+ EBADRQC = 56
286
+ EBADSLT = 57
287
+ EDEADLOCK = 58
288
+ EBFONT = 59
289
+ ENOSTR = 60
290
+ ENODATA = 61
291
+ ETIME = 62
292
+ ENOSR = 63
293
+ ENONET = 64
294
+ ENOPKG = 65
295
+ EREMOTE = 66
296
+ ENOLINK = 67
297
+ EADV = 68
298
+ ESRMNT = 69
299
+ ECOMM = 70
300
+ EPROTO = 71
301
+ EMULTIHOP = 72
302
+ EDOTDOT = 73
303
+ EBADMSG = 74
304
+ EOVERFLOW = 75
305
+ ENOTUNIQ = 76
306
+ EBADFD = 77
307
+ EREMCHG = 78
308
+ ELIBACC = 79
309
+ ELIBBAD = 80
310
+ ELIBSCN = 81
311
+ ELIBMAX = 82
312
+ ELIBEXEC = 83
313
+ EILSEQ = 84
314
+ ERESTART = 85
315
+ ESTRPIPE = 86
316
+ EUSERS = 87
317
+ ENOTSOCK = 88
318
+ EDESTADDRREQ = 89
319
+ EMSGSIZE = 90
320
+ EPROTOTYPE = 91
321
+ ENOPROTOOPT = 92
322
+ EPROTONOSUPPORT = 93
323
+ ESOCKTNOSUPPORT = 94
324
+ EOPNOTSUPP = 95
325
+ ENOTSUP = 96
326
+ EPFNOSUPPORT = 97
327
+ EAFNOSUPPORT = 98
328
+ EADDRINUSE = 99
329
+ EADDRNOTAVAIL = 100
330
+ ENETDOWN = 101
331
+ ENETUNREACH = 102
332
+ ENETRESET = 103
333
+ ECONNABORTED = 104
334
+ ECONNRESET = 105
335
+ ENOBUFS = 106
336
+ EISCONN = 107
337
+ ENOTCONN = 108
338
+ ESHUTDOWN = 109
339
+ ETOOMANYREFS = 110
340
+ ETIMEDOUT = 111
341
+ ECONNREFUSED = 112
342
+ EHOSTDOWN = 113
343
+ EHOSTUNREACH = 114
344
+ EALREADY = 115
345
+ EINPROGRESS = 116
346
+ ESTALE = 117
347
+ EUCLEAN = 118
348
+ ENOTNAM = 119
349
+ ENAVAIL = 120
350
+ EISNAM = 121
351
+ EREMOTEIO = 122
352
+ EDQUOT = 123
353
+ EQFULL = 124
354
+ ENOMEDIUM = 125
355
+ EMEDIUMTYPE = 126
356
+ ENOKEY = 127
357
+ EKEYEXPIRED = 128
358
+ EKEYREVOKED = 129
359
+ EKEYREJECTED = 130
360
+ ERFKILL = 131
361
+ ELOCKUNMAPPED = 132
362
+ ENOTACTIVE = 133
363
+ EAUTH = 134
364
+ EBADARCH = 135
365
+ EBADEXEC = 136
366
+ EBADMACHO = 137
367
+ EDEVERR = 138
368
+ EFTYPE = 139
369
+ ENEEDAUTH = 140
370
+ ENOATTR = 141
371
+ ENOPOLICY = 142
372
+ EPROCLIM = 143
373
+ EPROCUNAVAIL = 144
374
+ EPROGMISMATCH = 145
375
+ EPROGUNAVAIL = 146
376
+ EPWROFF = 147
377
+ EBADRPC = 148
378
+ ERPCMISMATCH = 149
379
+ ESHLIBVERS = 150
380
+ ENOTCAPABLE = 151
381
+ ECANCELED = 152
382
+ EOWNERDEAD = 153
383
+ ENOTRECOVERABLE = 154
384
+
385
+ def of(key: int | str | errno, /) -> errno:
386
+ if isinstance(key, errno):
387
+ return key
388
+ if isinstance(key, int):
389
+ return errno(key)
390
+ try:
391
+ return errno[key]
392
+ except KeyError as e:
393
+ raise ValueError(key) from e
394
+
p115client/exception.py CHANGED
@@ -4,6 +4,8 @@
4
4
  __all__ = [
5
5
  "P115Warning", "P115OSError", "AuthenticationError", "BusyOSError", "DataError",
6
6
  "LoginError", "MultipartUploadAbort", "NotSupportedError", "OperationalError",
7
+ "P115FileExistsError", "P115FileNotFoundError", "P115IsADirectoryError",
8
+ "P115NotADirectoryError", "P115PermissionError", "P115TimeoutError",
7
9
  ]
8
10
 
9
11
  import warnings
@@ -15,44 +17,36 @@ from functools import cached_property
15
17
  from .type import MultipartResumeData
16
18
 
17
19
 
20
+ warnings.filterwarnings("always", category=UserWarning)
21
+ setattr(warnings, "formatwarning", lambda message, category, filename, lineno, line=None, _getid=count(1).__next__:
22
+ f"\r\x1b[K\x1b[1;31;43m{category.__qualname__}\x1b[0m(\x1b[32m{_getid()}\x1b[0m) @ \x1b[3;4;34m{filename}\x1b[0m:\x1b[36m{lineno}\x1b[0m \x1b[5;31m➜\x1b[0m \x1b[1m{message}\x1b[0m\n"
23
+ )
24
+
18
25
  class P115Warning(UserWarning):
19
26
  """本模块的最基础警示类
20
27
  """
21
28
 
22
29
 
23
- _count = count(1).__next__
24
- warnings.filterwarnings("always", category=UserWarning)
25
- warnings.formatwarning = lambda message, category, filename, lineno, line=None: f"\r\x1b[K\x1b[1;31;43m{category.__qualname__}\x1b[0m(\x1b[32m{_count()}\x1b[0m) @ \x1b[3;4;34m{filename}\x1b[0m:\x1b[36m{lineno}\x1b[0m \x1b[5;31m➜\x1b[0m \x1b[1m{message}\x1b[0m\n"
26
-
27
-
28
30
  class P115OSError(OSError):
29
31
  """本模块的最基础异常类
30
32
  """
31
- def __init__(self, /, *args):
32
- super().__init__(*args)
33
-
34
33
  def __getattr__(self, attr, /):
35
- message = self.message
36
34
  try:
37
- if isinstance(message, Mapping):
38
- return message[attr]
35
+ return self[attr]
39
36
  except KeyError as e:
40
37
  raise AttributeError(attr) from e
41
- raise AttributeError(attr)
42
38
 
43
39
  def __getitem__(self, key, /):
44
40
  message = self.message
45
41
  if isinstance(message, Mapping):
46
42
  return message[key]
47
- return message
43
+ raise KeyError(key)
48
44
 
49
45
  @cached_property
50
46
  def message(self, /):
51
- args = self.args
52
- if len(args) >= 2:
53
- if not isinstance(args[0], int):
47
+ if args := self.args:
48
+ if len(args) >= 2 and isinstance(args[0], int):
54
49
  return args[1]
55
- if args:
56
50
  return args[0]
57
51
 
58
52
 
@@ -88,7 +82,7 @@ class MultipartUploadAbort(P115OSError):
88
82
 
89
83
 
90
84
  class NotSupportedError(P115OSError):
91
- """当调用不存在的接口时抛出
85
+ """当调用不存在的接口或者接口不支持此操作时抛出
92
86
  """
93
87
 
94
88
 
@@ -96,3 +90,33 @@ class OperationalError(P115OSError):
96
90
  """当接口使用方法错误时抛出,例如参数错误、空间不足、超出允许数量范围等
97
91
  """
98
92
 
93
+
94
+ class P115FileExistsError(P115OSError, FileExistsError):
95
+ """扩展 FileExistsError,同时是 P115OSError 的子类
96
+ """
97
+
98
+
99
+ class P115FileNotFoundError(P115OSError, FileNotFoundError):
100
+ """扩展 FileNotFoundError,同时是 P115OSError 的子类
101
+ """
102
+
103
+
104
+ class P115IsADirectoryError(P115OSError, IsADirectoryError):
105
+ """扩展 IsADirectoryError,同时是 P115OSError 的子类
106
+ """
107
+
108
+
109
+ class P115NotADirectoryError(P115OSError, NotADirectoryError):
110
+ """扩展 NotADirectoryError,同时是 P115OSError 的子类
111
+ """
112
+
113
+
114
+ class P115PermissionError(P115OSError, PermissionError):
115
+ """扩展 PermissionError,同时是 P115OSError 的子类
116
+ """
117
+
118
+
119
+ class P115TimeoutError(P115OSError, TimeoutError):
120
+ """扩展 TimeoutError,同时是 P115OSError 的子类
121
+ """
122
+
@@ -3,6 +3,7 @@
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
 
6
+ from .attr import *
6
7
  from .download import *
7
8
  from .edit import *
8
9
  from .export_dir import *
@@ -13,4 +14,5 @@ from .life import *
13
14
  from .pool import *
14
15
  from .request import *
15
16
  from .upload import *
17
+ from .util import *
16
18
  from .xys import *
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
+ __all__ = ["get_attr", "type_of_attr"]
6
+ __doc__ = "这个模块提供了一些和文件或目录信息有关的函数"
7
+
8
+ from collections.abc import Mapping
9
+ from typing import overload, Literal
10
+
11
+ from iterutils import run_gen_step
12
+ from p115client import check_response, normalize_attr_web, P115Client
13
+ from p115client.const import CLASS_TO_TYPE, SUFFIX_TO_TYPE
14
+ from posixpatht import splitext
15
+
16
+
17
+ @overload
18
+ def get_attr(
19
+ client: str | P115Client,
20
+ id: int,
21
+ skim: bool = False,
22
+ *,
23
+ async_: Literal[False] = False,
24
+ **request_kwargs,
25
+ ) -> dict:
26
+ ...
27
+ @overload
28
+ def get_attr(
29
+ client: str | P115Client,
30
+ id: int,
31
+ skim: bool = False,
32
+ *,
33
+ async_: Literal[True],
34
+ **request_kwargs,
35
+ ) -> dict:
36
+ ...
37
+ def get_attr(
38
+ client: str | P115Client,
39
+ id: int,
40
+ skim: bool = False,
41
+ *,
42
+ async_: Literal[False, True] = False,
43
+ **request_kwargs,
44
+ ) -> dict:
45
+ """获取文件或目录的信息
46
+
47
+ :param client: 115 客户端或 cookies
48
+ :param id: 文件或目录的 id
49
+ :param skim: 是否获取简要信息(可避免风控)
50
+ :param async_: 是否异步
51
+ :param request_kwargs: 其它请求参数
52
+
53
+ :return: 文件或目录的信息
54
+ """
55
+ if isinstance(client, str):
56
+ client = P115Client(client, check_for_relogin=True)
57
+ def gen_step():
58
+ if skim:
59
+ from dictattr import AttrDict
60
+ resp = yield client.fs_file_skim(id, async_=async_, **request_kwargs)
61
+ check_response(resp)
62
+ info = resp["data"][0]
63
+ return AttrDict(
64
+ id=int(info["file_id"]),
65
+ name=info["file_name"],
66
+ pickcode=info["pick_code"],
67
+ sha1=info["sha1"],
68
+ size=int(info["file_size"]),
69
+ )
70
+ else:
71
+ resp = yield client.fs_file(id, async_=async_, **request_kwargs)
72
+ check_response(resp)
73
+ return normalize_attr_web(resp["data"][0])
74
+ return run_gen_step(gen_step, async_=async_)
75
+
76
+
77
+ def type_of_attr(attr: Mapping, /) -> int:
78
+ """推断文件信息所属类型(试验版,未必准确)
79
+
80
+ :param attr: 文件信息
81
+
82
+ :return: 返回类型代码
83
+
84
+ - 0: 目录
85
+ - 1: 文档
86
+ - 2: 图片
87
+ - 3: 音频
88
+ - 4: 视频
89
+ - 5: 压缩包
90
+ - 6: 应用
91
+ - 7: 书籍
92
+ - 99: 其它文件
93
+ """
94
+ if attr.get("is_dir") or attr.get("is_directory"):
95
+ return 0
96
+ type: None | int
97
+ if type := CLASS_TO_TYPE.get(attr.get("class", "")):
98
+ return type
99
+ if type := SUFFIX_TO_TYPE.get(splitext(attr["name"])[1].lower()):
100
+ return type
101
+ if attr.get("is_video") or "defination" in attr:
102
+ return 4
103
+ return 99
104
+
@@ -3,58 +3,43 @@
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
  __all__ = [
6
- "reduce_image_url_layers", "batch_get_url", "iter_url_batches", "iter_files_with_url",
6
+ "batch_get_url", "iter_url_batches", "iter_files_with_url",
7
7
  "iter_images_with_url", "iter_subtitles_with_url", "iter_subtitle_batches", "make_strm",
8
8
  "iter_download_nodes", "iter_download_files", "get_remaining_open_count",
9
9
  ]
10
10
  __doc__ = "这个模块提供了一些和下载有关的函数"
11
11
 
12
- from asyncio import create_task, to_thread, Queue as AsyncQueue, Semaphore, TaskGroup
13
- from collections.abc import AsyncIterator, Callable, Coroutine, Iterable, Iterator, Sequence
12
+ from asyncio import create_task, to_thread, Queue as AsyncQueue, TaskGroup
13
+ from collections.abc import AsyncIterator, Callable, Coroutine, Iterable, Iterator
14
14
  from concurrent.futures import ThreadPoolExecutor
15
- from errno import ENOENT, ENOTDIR
15
+ from errno import ENOTDIR
16
16
  from functools import partial
17
17
  from glob import iglob
18
- from inspect import isawaitable
19
18
  from itertools import chain, count, cycle, islice
20
- from mimetypes import guess_type
21
19
  from os import fsdecode, makedirs, remove, PathLike
22
20
  from os.path import abspath, dirname, join as joinpath, normpath, splitext
23
21
  from queue import SimpleQueue
24
22
  from shutil import rmtree
25
23
  from threading import Lock
26
24
  from time import time
27
- from typing import cast, overload, Any, Final, Literal, TypedDict
25
+ from typing import cast, overload, Any, Literal
28
26
  from types import EllipsisType
29
- from urllib.parse import quote, urlsplit
30
27
  from urllib.request import urlopen, Request
31
28
  from uuid import uuid4
32
29
  from warnings import warn
33
30
 
34
31
  from asynctools import async_chain_from_iterable
35
- from concurrenttools import run_as_async, run_as_thread, thread_batch, async_batch
32
+ from concurrenttools import run_as_thread, thread_batch, async_batch
36
33
  from encode_uri import encode_uri_component_loose
37
34
  from iterutils import chunked, run_gen_step, run_gen_step_iter, with_iter_next, Yield, YieldFrom
38
35
  from p115client import check_response, normalize_attr, normalize_attr_simple, P115Client, P115URL
39
36
  from p115client.exception import P115Warning
40
37
 
41
- from .export_dir import export_dir_parse_iter
42
38
  from .iterdir import (
43
39
  get_path_to_cid, iterdir, iter_files, iter_files_raw, iter_files_with_path,
44
40
  unescape_115_charref, posix_escape_name, DirNode, ID_TO_DIRNODE_CACHE,
45
41
  )
46
-
47
-
48
- def reduce_image_url_layers(url: str, /, size: str | int = "") -> str:
49
- """从图片的缩略图链接中提取信息,以减少一次 302 访问
50
- """
51
- if not url.startswith(("http://thumb.115.com/", "https://thumb.115.com/")):
52
- return url
53
- urlp = urlsplit(url)
54
- sha1, _, size0 = urlp.path.rsplit("/")[-1].partition("_")
55
- if size == "":
56
- size = size0 or "0"
57
- return f"https://imgjump.115.com/?sha1={sha1}&{urlp.query}&size={size}"
42
+ from .util import reduce_image_url_layers
58
43
 
59
44
 
60
45
  @overload
p115client/tool/edit.py CHANGED
@@ -8,9 +8,8 @@ __all__ = [
8
8
  ]
9
9
  __doc__ = "这个模块提供了一些和修改文件或目录信息有关的函数"
10
10
 
11
- from collections.abc import AsyncIterable, AsyncIterator, Coroutine, Iterable, Iterator, Sequence
11
+ from collections.abc import AsyncIterable, AsyncIterator, Coroutine, Iterable, Iterator
12
12
  from functools import partial
13
- from itertools import batched, pairwise
14
13
  from typing import cast, overload, Any, Literal
15
14
 
16
15
  from asynctools import to_list
@@ -15,7 +15,7 @@ from functools import partial
15
15
  from inspect import isawaitable
16
16
  from itertools import cycle
17
17
  from time import time
18
- from typing import cast, overload, Any, Final, Literal
18
+ from typing import overload, Any, Final, Literal
19
19
  from warnings import warn
20
20
 
21
21
  from iterutils import run_gen_step, run_gen_step_iter, Yield
@@ -23,23 +23,13 @@ from p115client import check_response, P115Client, P115OpenClient
23
23
  from p115client.client import get_status_code
24
24
  from p115client.exception import BusyOSError, DataError, P115Warning
25
25
 
26
+ from .util import is_timeouterror
27
+
26
28
 
27
29
  get_webapi_origin: Final = cycle(("http://webapi.115.com", "https://webapi.115.com")).__next__
28
30
  get_proapi_origin: Final = cycle(("http://proapi.115.com", "https://proapi.115.com")).__next__
29
31
 
30
32
 
31
- def is_timeouterror(exc: BaseException, /) -> bool:
32
- """通过名字来判断一个异常是不是 Timeout
33
- """
34
- exctype = type(exc)
35
- for exctype in exctype.mro():
36
- if exctype is Exception:
37
- break
38
- if "Timeout" in exctype.__name__:
39
- return True
40
- return False
41
-
42
-
43
33
  @overload
44
34
  def iter_fs_files(
45
35
  client: str | P115Client | P115OpenClient,
@@ -403,4 +393,5 @@ async def iter_fs_files_asynchronized(
403
393
  t.cancel()
404
394
  await tg.__aexit__(None, None, None)
405
395
 
396
+
406
397
  # TODO: 以上的数据获取方式某种程度上应该是通用的,只要是涉及到 offset 和 count,因此可以总结出一个更抽象的函数