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/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,因此可以总结出一个更抽象的函数
@@ -3,14 +3,13 @@
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
5
  __all__ = [
6
- "ID_TO_DIRNODE_CACHE", "P115ID", "unescape_115_charref", "posix_escape_name",
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", "share_extract_payload", "share_iterdir", "share_iter_files",
13
- "iter_selected_nodes", "iter_selected_nodes_by_pickcode", "iter_selected_nodes_using_category_get",
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, count, cycle, islice, takewhile
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, TypedDict
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, async_foreach, ensure_aiter, foreach,
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.const import CLASS_TO_TYPE, SUFFIX_TO_TYPE
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
- resp = yield client.download_url_web(pickcode, base_url=True, async_=async_, **request_kwargs)
938
- if file_id := resp.get("file_id"):
939
- msg_code = resp.get("msg_code", False)
940
- resp["is_dir"] = msg_code and msg_code != 50028
941
- return P115ID(file_id, resp, about="pickcode")
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
- resp = yield client.fs_shasearch(sha1, base_url=True, async_=async_, **request_kwargs)
977
- check_response(resp)
978
- resp["data"]["file_sha1"] = sha1.upper()
979
- return P115ID(resp["data"]["file_id"], resp["data"], about="sha1")
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 count, cycle
14
+ from itertools import cycle
15
15
  from time import time, sleep
16
16
  from typing import overload, Any, Final, Literal
17
17