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/client.py +180 -94
- p115client/const.py +190 -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.1.dist-info}/METADATA +4 -4
- p115client-0.0.5.10.1.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.1.dist-info}/LICENSE +0 -0
- {p115client-0.0.5.9.3.dist-info → p115client-0.0.5.10.1.dist-info}/WHEEL +0 -0
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
|
|
p115client/tool/pool.py
CHANGED
@@ -18,12 +18,14 @@ from itertools import cycle, repeat
|
|
18
18
|
from math import inf, isinf
|
19
19
|
from threading import Lock
|
20
20
|
from time import time
|
21
|
-
from typing import cast, Any
|
22
21
|
|
23
22
|
from iterutils import run_gen_step
|
24
23
|
from p115client import check_response, P115Client
|
24
|
+
from p115client.const import AVAILABLE_APP_IDS
|
25
25
|
from p115client.exception import P115OSError, AuthenticationError, LoginError
|
26
26
|
|
27
|
+
from .util import get_status, is_timeouterror
|
28
|
+
|
27
29
|
|
28
30
|
@total_ordering
|
29
31
|
class ComparedWithID[T]:
|
@@ -51,35 +53,9 @@ class ComparedWithID[T]:
|
|
51
53
|
return f"{type(self).__qualname__}({self.value!r})"
|
52
54
|
|
53
55
|
|
54
|
-
def get_status(e: BaseException, /) -> None | int:
|
55
|
-
status = (
|
56
|
-
getattr(e, "status", None) or
|
57
|
-
getattr(e, "code", None) or
|
58
|
-
getattr(e, "status_code", None)
|
59
|
-
)
|
60
|
-
if status is None and hasattr(e, "response"):
|
61
|
-
response = e.response
|
62
|
-
status = (
|
63
|
-
getattr(response, "status", None) or
|
64
|
-
getattr(response, "code", None) or
|
65
|
-
getattr(response, "status_code", None)
|
66
|
-
)
|
67
|
-
return status
|
68
|
-
|
69
|
-
|
70
|
-
def is_timeouterror(exc: Exception) -> bool:
|
71
|
-
exctype = type(exc)
|
72
|
-
for exctype in exctype.mro():
|
73
|
-
if exctype is Exception:
|
74
|
-
break
|
75
|
-
if "Timeout" in exctype.__name__:
|
76
|
-
return True
|
77
|
-
return False
|
78
|
-
|
79
|
-
|
80
56
|
def generate_auth_factory(
|
81
57
|
client: str | P115Client,
|
82
|
-
app_ids: Iterable[int] =
|
58
|
+
app_ids: Iterable[int] = AVAILABLE_APP_IDS,
|
83
59
|
**request_kwargs,
|
84
60
|
) -> Callable:
|
85
61
|
"""利用一个已登录设备的 cookies,产生若干开放应用的 access_token
|
@@ -262,7 +238,7 @@ def make_pool[T](
|
|
262
238
|
|
263
239
|
def auth_pool(
|
264
240
|
client: str | P115Client,
|
265
|
-
app_ids: Iterable[int] =
|
241
|
+
app_ids: Iterable[int] = AVAILABLE_APP_IDS,
|
266
242
|
heap: None | list[tuple[float, dict | ComparedWithID[dict]]] = None,
|
267
243
|
cooldown_time: int | float = 1,
|
268
244
|
live_time: int | float = 7000,
|
@@ -404,6 +380,7 @@ def call_wrap_with_pool(get_cert_headers: Callable, /, func: Callable) -> Callab
|
|
404
380
|
return run_gen_step(gen_step, async_=async_)
|
405
381
|
return update_wrapper(wrapper, func)
|
406
382
|
|
383
|
+
|
407
384
|
# TODO: 需要完整的类型签名
|
408
385
|
# TODO: 池子可以被导出,下次继续使用
|
409
386
|
# TODO: 支持多个不同设备的 cookies 组成池,以及刷新(自己刷新自己,或者由另一个 cookies 辅助刷新)
|
p115client/tool/request.py
CHANGED
@@ -126,6 +126,7 @@ def make_request(
|
|
126
126
|
case _:
|
127
127
|
raise ValueError(f"can't make request for {module!r}")
|
128
128
|
|
129
|
+
|
129
130
|
# TODO: 基于 http.client 实现一个 request,并且支持连接池
|
130
131
|
# TODO: 基于 https://asks.readthedocs.io/en/latest/ 实现一个 request
|
131
132
|
# TODO: 基于 https://pypi.org/project/pycurl/ 实现一个 request
|
p115client/tool/util.py
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
5
|
+
__all__ = [
|
6
|
+
"get_status", "is_timeouterror", "posix_escape_name", "reduce_image_url_layers",
|
7
|
+
"share_extract_payload", "unescape_115_charref",
|
8
|
+
]
|
9
|
+
__doc__ = "这个模块提供了一些工具函数"
|
10
|
+
|
11
|
+
from re import compile as re_compile
|
12
|
+
from typing import cast, Final, TypedDict
|
13
|
+
from urllib.parse import urlsplit
|
14
|
+
|
15
|
+
|
16
|
+
CRE_115_CHARREF_sub: Final = re_compile("\\[\x02([0-9]+)\\]").sub
|
17
|
+
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
|
18
|
+
CRE_SHARE_LINK_search2 = re_compile(r"(?P<share_code>[a-z0-9]+)(?:-(?P<receive_code>[a-z0-9]{4}))?").search
|
19
|
+
|
20
|
+
|
21
|
+
class SharePayload(TypedDict):
|
22
|
+
share_code: str
|
23
|
+
receive_code: None | str
|
24
|
+
|
25
|
+
|
26
|
+
def get_status(e: BaseException, /) -> None | int:
|
27
|
+
"""获取 HTTP 请求异常的状态码(如果有的话)
|
28
|
+
"""
|
29
|
+
status = (
|
30
|
+
getattr(e, "status", None) or
|
31
|
+
getattr(e, "code", None) or
|
32
|
+
getattr(e, "status_code", None)
|
33
|
+
)
|
34
|
+
if status is None and hasattr(e, "response"):
|
35
|
+
response = e.response
|
36
|
+
status = (
|
37
|
+
getattr(response, "status", None) or
|
38
|
+
getattr(response, "code", None) or
|
39
|
+
getattr(response, "status_code", None)
|
40
|
+
)
|
41
|
+
return status
|
42
|
+
|
43
|
+
|
44
|
+
def is_timeouterror(exc: BaseException) -> bool:
|
45
|
+
"""判断是不是超时异常
|
46
|
+
"""
|
47
|
+
exctype = type(exc)
|
48
|
+
if issubclass(exctype, TimeoutError):
|
49
|
+
return True
|
50
|
+
for exctype in exctype.mro():
|
51
|
+
if "Timeout" in exctype.__name__:
|
52
|
+
return True
|
53
|
+
return False
|
54
|
+
|
55
|
+
|
56
|
+
def posix_escape_name(name: str, /, repl: str = "|") -> str:
|
57
|
+
"""把文件名中的 "/" 转换为另一个字符(默认为 "|")
|
58
|
+
|
59
|
+
:param name: 文件名
|
60
|
+
:param repl: 替换为的目标字符
|
61
|
+
|
62
|
+
:return: 替换后的名字
|
63
|
+
"""
|
64
|
+
return name.replace("/", repl)
|
65
|
+
|
66
|
+
|
67
|
+
def reduce_image_url_layers(url: str, /, size: str | int = "") -> str:
|
68
|
+
"""从图片的缩略图链接中提取信息,以减少一次 302 访问
|
69
|
+
"""
|
70
|
+
if not url.startswith(("http://thumb.115.com/", "https://thumb.115.com/")):
|
71
|
+
return url
|
72
|
+
urlp = urlsplit(url)
|
73
|
+
sha1, _, size0 = urlp.path.rsplit("/")[-1].partition("_")
|
74
|
+
if size == "":
|
75
|
+
size = size0 or "0"
|
76
|
+
return f"https://imgjump.115.com/?sha1={sha1}&{urlp.query}&size={size}"
|
77
|
+
|
78
|
+
|
79
|
+
def share_extract_payload(link: str, /) -> SharePayload:
|
80
|
+
"""从链接中提取 share_code 和 receive_code
|
81
|
+
|
82
|
+
.. hint::
|
83
|
+
`link` 支持 3 种形式(圆括号中的字符表示可有可无):
|
84
|
+
|
85
|
+
1. http(s)://115.com/s/{share_code}?password={receive_code}(#) 或 http(s)://share.115.com/{share_code}?password={receive_code}(#)
|
86
|
+
2. (/){share_code}-{receive_code}(/)
|
87
|
+
3. {share_code}
|
88
|
+
"""
|
89
|
+
m = CRE_SHARE_LINK_search1(link)
|
90
|
+
if m is None:
|
91
|
+
m = CRE_SHARE_LINK_search2(link)
|
92
|
+
if m is None:
|
93
|
+
raise ValueError("not a valid 115 share link")
|
94
|
+
return cast(SharePayload, m.groupdict())
|
95
|
+
|
96
|
+
|
97
|
+
def unescape_115_charref(s: str, /) -> str:
|
98
|
+
"""对 115 的字符引用进行解码
|
99
|
+
|
100
|
+
:example:
|
101
|
+
|
102
|
+
.. code:: python
|
103
|
+
|
104
|
+
unescape_115_charref("[\x02128074]0号:优质资源") == "👊0号:优质资源"
|
105
|
+
"""
|
106
|
+
return CRE_115_CHARREF_sub(lambda a: chr(int(a[1])), s)
|
107
|
+
|