p115client 0.0.5.9.3__py3-none-any.whl → 0.0.5.10__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/client.py +159 -74
- p115client/const.py +183 -1
- p115client/exception.py +42 -18
- p115client/tool/__init__.py +2 -0
- p115client/tool/attr.py +104 -0
- p115client/tool/download.py +7 -22
- p115client/tool/edit.py +1 -2
- p115client/tool/fs_files.py +4 -13
- p115client/tool/iterdir.py +56 -101
- p115client/tool/life.py +1 -1
- p115client/tool/pool.py +6 -29
- p115client/tool/request.py +1 -0
- p115client/tool/util.py +107 -0
- p115client/tool/xys.py +317 -50
- p115client/type.py +16 -1
- {p115client-0.0.5.9.3.dist-info → p115client-0.0.5.10.dist-info}/METADATA +3 -3
- p115client-0.0.5.10.dist-info/RECORD +26 -0
- p115client-0.0.5.9.3.dist-info/RECORD +0 -24
- {p115client-0.0.5.9.3.dist-info → p115client-0.0.5.10.dist-info}/LICENSE +0 -0
- {p115client-0.0.5.9.3.dist-info → p115client-0.0.5.10.dist-info}/WHEEL +0 -0
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
|
-
|
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
|
-
|
43
|
+
raise KeyError(key)
|
48
44
|
|
49
45
|
@cached_property
|
50
46
|
def message(self, /):
|
51
|
-
args
|
52
|
-
|
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
|
+
|
p115client/tool/__init__.py
CHANGED
@@ -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 *
|
p115client/tool/attr.py
ADDED
@@ -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
|
+
|
p115client/tool/download.py
CHANGED
@@ -3,58 +3,43 @@
|
|
3
3
|
|
4
4
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
5
5
|
__all__ = [
|
6
|
-
"
|
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,
|
13
|
-
from collections.abc import AsyncIterator, Callable, Coroutine, Iterable, Iterator
|
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
|
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,
|
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
|
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
|
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
|
p115client/tool/fs_files.py
CHANGED
@@ -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
|
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,因此可以总结出一个更抽象的函数
|
p115client/tool/iterdir.py
CHANGED
@@ -3,14 +3,13 @@
|
|
3
3
|
|
4
4
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
5
5
|
__all__ = [
|
6
|
-
"ID_TO_DIRNODE_CACHE", "
|
7
|
-
"type_of_attr", "get_path_to_cid", "get_file_count", "get_ancestors",
|
6
|
+
"ID_TO_DIRNODE_CACHE", "DirNode", "get_path_to_cid", "get_file_count", "get_ancestors",
|
8
7
|
"get_ancestors_to_cid", "get_id_to_path", "get_id_to_sha1", "get_id_to_pickcode",
|
9
8
|
"iter_nodes_skim", "iter_stared_dirs_raw", "iter_stared_dirs", "ensure_attr_path",
|
10
9
|
"ensure_attr_path_by_category_get", "iterdir_raw", "iterdir", "iterdir_limited",
|
11
10
|
"iter_files_raw", "iter_files", "traverse_files", "iter_dirs", "iter_dupfiles",
|
12
|
-
"iter_image_files", "
|
13
|
-
"
|
11
|
+
"iter_image_files", "share_iterdir", "share_iter_files", "iter_selected_nodes",
|
12
|
+
"iter_selected_nodes_by_pickcode", "iter_selected_nodes_using_category_get",
|
14
13
|
"iter_selected_nodes_using_edit", "iter_selected_nodes_using_star_event",
|
15
14
|
"iter_selected_dirs_using_star", "iter_files_with_dirname", "iter_files_with_path",
|
16
15
|
"iter_files_with_path_by_export_dir", "iter_parents_3_level", "iter_dir_nodes",
|
@@ -26,22 +25,21 @@ from collections.abc import (
|
|
26
25
|
from dataclasses import dataclass
|
27
26
|
from errno import EIO, ENOENT, ENOTDIR
|
28
27
|
from functools import partial
|
29
|
-
from itertools import chain,
|
28
|
+
from itertools import chain, cycle, islice
|
30
29
|
from math import inf
|
31
30
|
from operator import itemgetter
|
32
|
-
from re import compile as re_compile
|
33
31
|
from string import digits, hexdigits
|
34
32
|
from threading import Lock
|
35
33
|
from time import sleep, time
|
36
34
|
from types import EllipsisType
|
37
|
-
from typing import cast, overload, Any, Final, Literal, NamedTuple
|
35
|
+
from typing import cast, overload, Any, Final, Literal, NamedTuple
|
38
36
|
from warnings import warn
|
39
37
|
from weakref import WeakValueDictionary
|
40
38
|
|
41
39
|
from asynctools import async_chain, async_filter, async_map, to_list
|
42
40
|
from concurrenttools import run_as_thread, taskgroup_map, threadpool_map
|
43
41
|
from iterutils import (
|
44
|
-
as_gen_step, bfs_gen, chunked,
|
42
|
+
as_gen_step, bfs_gen, chunked, ensure_aiter, foreach,
|
45
43
|
flatten, iter_unique, run_gen_step, run_gen_step_iter, through,
|
46
44
|
async_through, with_iter_next, Yield, YieldFrom,
|
47
45
|
)
|
@@ -51,18 +49,16 @@ from p115client import (
|
|
51
49
|
check_response, normalize_attr, normalize_attr_simple,
|
52
50
|
P115Client, P115OSError, P115Warning,
|
53
51
|
)
|
54
|
-
from p115client.
|
55
|
-
from p115client.type import P115DictAttrLike
|
52
|
+
from p115client.type import P115ID
|
56
53
|
from posixpatht import joins, path_is_dir_form, splitext, splits
|
57
54
|
|
55
|
+
from .attr import type_of_attr
|
58
56
|
from .edit import update_desc, update_star
|
59
57
|
from .fs_files import is_timeouterror, iter_fs_files, iter_fs_files_threaded, iter_fs_files_asynchronized
|
60
58
|
from .life import iter_life_behavior_once, life_show
|
59
|
+
from .util import posix_escape_name, share_extract_payload, unescape_115_charref
|
61
60
|
|
62
61
|
|
63
|
-
CRE_SHARE_LINK_search1 = re_compile(r"(?:/s/|share\.115\.com/)(?P<share_code>[a-z0-9]+)\?password=(?:(?P<receive_code>[a-z0-9]{4}))?").search
|
64
|
-
CRE_SHARE_LINK_search2 = re_compile(r"(?P<share_code>[a-z0-9]+)(?:-(?P<receive_code>[a-z0-9]{4}))?").search
|
65
|
-
CRE_115_CHARREF_sub = re_compile("\\[\x02([0-9]+)\\]").sub
|
66
62
|
WEBAPI_BASE_URLS = (
|
67
63
|
"http://webapi.115.com",
|
68
64
|
"https://webapi.115.com",
|
@@ -111,11 +107,6 @@ class OverviewAttr:
|
|
111
107
|
ID_TO_DIRNODE_CACHE: Final[defaultdict[int, dict[int, tuple[str, int] | DirNode]]] = defaultdict(dict)
|
112
108
|
|
113
109
|
|
114
|
-
class SharePayload(TypedDict):
|
115
|
-
share_code: str
|
116
|
-
receive_code: None | str
|
117
|
-
|
118
|
-
|
119
110
|
def _overview_attr(info: Mapping, /) -> OverviewAttr:
|
120
111
|
if "n" in info:
|
121
112
|
is_dir = "fid" not in info
|
@@ -154,58 +145,6 @@ def _overview_attr(info: Mapping, /) -> OverviewAttr:
|
|
154
145
|
return OverviewAttr(is_dir, id, pid, name, ctime, mtime)
|
155
146
|
|
156
147
|
|
157
|
-
def posix_escape_name(name: str, /, repl: str = "|") -> str:
|
158
|
-
"""把文件名中的 "/" 转换为另一个字符(默认为 "|")
|
159
|
-
|
160
|
-
:param name: 文件名
|
161
|
-
:param repl: 替换为的目标字符
|
162
|
-
|
163
|
-
:return: 替换后的名字
|
164
|
-
"""
|
165
|
-
return name.replace("/", repl)
|
166
|
-
|
167
|
-
|
168
|
-
def unescape_115_charref(s: str, /) -> str:
|
169
|
-
"""对 115 的字符引用进行解码
|
170
|
-
|
171
|
-
:example:
|
172
|
-
|
173
|
-
.. code:: python
|
174
|
-
|
175
|
-
unescape_115_charref("[\x02128074]0号:优质资源") == "👊0号:优质资源"
|
176
|
-
"""
|
177
|
-
return CRE_115_CHARREF_sub(lambda a: chr(int(a[1])), s)
|
178
|
-
|
179
|
-
|
180
|
-
def type_of_attr(attr: Mapping, /) -> int:
|
181
|
-
"""推断文件信息所属类型(试验版,未必准确)
|
182
|
-
|
183
|
-
:param attr: 文件信息
|
184
|
-
|
185
|
-
:return: 返回类型代码
|
186
|
-
|
187
|
-
- 0: 目录
|
188
|
-
- 1: 文档
|
189
|
-
- 2: 图片
|
190
|
-
- 3: 音频
|
191
|
-
- 4: 视频
|
192
|
-
- 5: 压缩包
|
193
|
-
- 6: 应用
|
194
|
-
- 7: 书籍
|
195
|
-
- 99: 其它文件
|
196
|
-
"""
|
197
|
-
if attr.get("is_dir") or attr.get("is_directory"):
|
198
|
-
return 0
|
199
|
-
type: None | int
|
200
|
-
if type := CLASS_TO_TYPE.get(attr.get("class", "")):
|
201
|
-
return type
|
202
|
-
if type := SUFFIX_TO_TYPE.get(splitext(attr["name"])[1].lower()):
|
203
|
-
return type
|
204
|
-
if attr.get("is_video") or "defination" in attr:
|
205
|
-
return 4
|
206
|
-
return 99
|
207
|
-
|
208
|
-
|
209
148
|
@overload
|
210
149
|
def get_path_to_cid(
|
211
150
|
client: str | P115Client,
|
@@ -657,10 +596,6 @@ def get_ancestors_to_cid(
|
|
657
596
|
return run_gen_step(gen_step, async_=async_)
|
658
597
|
|
659
598
|
|
660
|
-
class P115ID(P115DictAttrLike, int):
|
661
|
-
|
662
|
-
def __str__(self, /) -> str:
|
663
|
-
return int.__repr__(self)
|
664
599
|
|
665
600
|
|
666
601
|
# TODO: 使用 search 接口以在特定目录之下搜索某个名字,以便减少风控
|
@@ -908,6 +843,7 @@ def get_id_to_path(
|
|
908
843
|
def get_id_to_pickcode(
|
909
844
|
client: str | P115Client,
|
910
845
|
pickcode: str,
|
846
|
+
app: str = "web",
|
911
847
|
*,
|
912
848
|
async_: Literal[False] = False,
|
913
849
|
**request_kwargs,
|
@@ -917,6 +853,7 @@ def get_id_to_pickcode(
|
|
917
853
|
def get_id_to_pickcode(
|
918
854
|
client: str | P115Client,
|
919
855
|
pickcode: str,
|
856
|
+
app: str = "web",
|
920
857
|
*,
|
921
858
|
async_: Literal[True],
|
922
859
|
**request_kwargs,
|
@@ -925,21 +862,34 @@ def get_id_to_pickcode(
|
|
925
862
|
def get_id_to_pickcode(
|
926
863
|
client: str | P115Client,
|
927
864
|
pickcode: str,
|
865
|
+
app: str = "web",
|
928
866
|
*,
|
929
867
|
async_: Literal[False, True] = False,
|
930
868
|
**request_kwargs,
|
931
869
|
) -> P115ID | Coroutine[Any, Any, P115ID]:
|
870
|
+
"""获取 pickcode 对应的 id
|
871
|
+
|
872
|
+
:param client: 115 客户端或 cookies
|
873
|
+
:param pickcode: 提取码
|
874
|
+
:param app: 使用某个 app (设备)的接口
|
875
|
+
:param async_: 是否异步
|
876
|
+
:param request_kwargs: 其它请求参数
|
877
|
+
|
878
|
+
:return: 文件或目录的 id
|
879
|
+
"""
|
932
880
|
if not 17 <= len(pickcode) <= 18 or not pickcode.isalnum():
|
933
881
|
raise ValueError(f"bad pickcode: {pickcode!r}")
|
934
882
|
if isinstance(client, str):
|
935
883
|
client = P115Client(client, check_for_relogin=True)
|
936
884
|
def gen_step():
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
885
|
+
if app in ("", "web", "desktop", "harmony"):
|
886
|
+
fs_supervision: Callable = client.fs_supervision
|
887
|
+
else:
|
888
|
+
fs_supervision = partial(client.fs_supervision_app, app=app)
|
889
|
+
resp = yield fs_supervision(pickcode, async_=async_, **request_kwargs)
|
942
890
|
check_response(resp)
|
891
|
+
data = resp["data"]
|
892
|
+
return P115ID(data["file_id"], data, about="pickcode")
|
943
893
|
return run_gen_step(gen_step, async_=async_)
|
944
894
|
|
945
895
|
|
@@ -947,6 +897,7 @@ def get_id_to_pickcode(
|
|
947
897
|
def get_id_to_sha1(
|
948
898
|
client: str | P115Client,
|
949
899
|
sha1: str,
|
900
|
+
app: str = "web",
|
950
901
|
*,
|
951
902
|
async_: Literal[False] = False,
|
952
903
|
**request_kwargs,
|
@@ -956,6 +907,7 @@ def get_id_to_sha1(
|
|
956
907
|
def get_id_to_sha1(
|
957
908
|
client: str | P115Client,
|
958
909
|
sha1: str,
|
910
|
+
app: str = "web",
|
959
911
|
*,
|
960
912
|
async_: Literal[True],
|
961
913
|
**request_kwargs,
|
@@ -964,19 +916,40 @@ def get_id_to_sha1(
|
|
964
916
|
def get_id_to_sha1(
|
965
917
|
client: str | P115Client,
|
966
918
|
sha1: str,
|
919
|
+
app: str = "web",
|
967
920
|
*,
|
968
921
|
async_: Literal[False, True] = False,
|
969
922
|
**request_kwargs,
|
970
923
|
) -> P115ID | Coroutine[Any, Any, P115ID]:
|
924
|
+
"""获取 sha1 对应的文件的 id
|
925
|
+
|
926
|
+
:param client: 115 客户端或 cookies
|
927
|
+
:param sha1: sha1 摘要值
|
928
|
+
:param app: 使用某个 app (设备)的接口
|
929
|
+
:param async_: 是否异步
|
930
|
+
:param request_kwargs: 其它请求参数
|
931
|
+
|
932
|
+
:return: 文件或目录的 id
|
933
|
+
"""
|
971
934
|
if len(sha1) != 40 or sha1.strip(hexdigits):
|
972
935
|
raise ValueError(f"bad sha1: {sha1!r}")
|
973
936
|
if isinstance(client, str):
|
974
937
|
client = P115Client(client, check_for_relogin=True)
|
975
938
|
def gen_step():
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
939
|
+
file_sha1 = sha1.upper()
|
940
|
+
if app in ("", "web", "desktop", "harmony"):
|
941
|
+
resp = yield client.fs_shasearch(sha1, async_=async_, **request_kwargs)
|
942
|
+
check_response(resp)
|
943
|
+
data = resp["data"]
|
944
|
+
else:
|
945
|
+
resp = yield client.fs_search_app(sha1, async_=async_, **request_kwargs)
|
946
|
+
check_response(resp)
|
947
|
+
for data in resp["data"]:
|
948
|
+
if data["sha1"] == file_sha1:
|
949
|
+
break
|
950
|
+
else:
|
951
|
+
raise FileNotFoundError(ENOENT, file_sha1)
|
952
|
+
return P115ID(data["file_id"], data, about="sha1", file_sha1=file_sha1)
|
980
953
|
return run_gen_step(gen_step, async_=async_)
|
981
954
|
|
982
955
|
|
@@ -3135,24 +3108,6 @@ def iter_image_files(
|
|
3135
3108
|
return run_gen_step_iter(gen_step, async_=async_)
|
3136
3109
|
|
3137
3110
|
|
3138
|
-
def share_extract_payload(link: str, /) -> SharePayload:
|
3139
|
-
"""从链接中提取 share_code 和 receive_code
|
3140
|
-
|
3141
|
-
.. hint::
|
3142
|
-
`link` 支持 3 种形式(圆括号中的字符表示可有可无):
|
3143
|
-
|
3144
|
-
1. http(s)://115.com/s/{share_code}?password={receive_code}(#) 或 http(s)://share.115.com/{share_code}?password={receive_code}(#)
|
3145
|
-
2. (/){share_code}-{receive_code}(/)
|
3146
|
-
3. {share_code}
|
3147
|
-
"""
|
3148
|
-
m = CRE_SHARE_LINK_search1(link)
|
3149
|
-
if m is None:
|
3150
|
-
m = CRE_SHARE_LINK_search2(link)
|
3151
|
-
if m is None:
|
3152
|
-
raise ValueError("not a valid 115 share link")
|
3153
|
-
return cast(SharePayload, m.groupdict())
|
3154
|
-
|
3155
|
-
|
3156
3111
|
@overload
|
3157
3112
|
def share_iterdir(
|
3158
3113
|
client: str | P115Client,
|
p115client/tool/life.py
CHANGED
@@ -11,7 +11,7 @@ __doc__ = "这个模块提供了一些和 115 生活操作事件有关的函数"
|
|
11
11
|
from asyncio import sleep as async_sleep
|
12
12
|
from collections.abc import AsyncIterator, Container, Coroutine, Iterator
|
13
13
|
from functools import partial
|
14
|
-
from itertools import
|
14
|
+
from itertools import cycle
|
15
15
|
from time import time, sleep
|
16
16
|
from typing import overload, Any, Final, Literal
|
17
17
|
|