parsehub 2.1.6__tar.gz → 2.1.7__tar.gz
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.
- {parsehub-2.1.6/src/parsehub.egg-info → parsehub-2.1.7}/PKG-INFO +1 -1
- {parsehub-2.1.6 → parsehub-2.1.7}/pyproject.toml +1 -1
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/base/base.py +4 -2
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/bilibili.py +16 -15
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/instagram.py +1 -1
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/threads.py +5 -1
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/twitter.py +3 -2
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/bilibili.py +17 -17
- {parsehub-2.1.6 → parsehub-2.1.7/src/parsehub.egg-info}/PKG-INFO +1 -1
- {parsehub-2.1.6 → parsehub-2.1.7}/test/test_core_offline.py +1 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/LICENSE +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/README.md +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/setup.cfg +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/cli.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/cli_config.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/config/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/config/config.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/errors.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/base/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/base/ytdlp.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/coolapk.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/douyin.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/facebook.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/kuaishou.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/pipix.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/snapchat.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/tieba.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/tiktok.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/weibo.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/weixin.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/xhs.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/xiaoheihe.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/youtube.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/zhihu.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/parsers/parser/zuiyou.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/coolapk.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/douyin.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/instagram.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/kuaishou.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/pipix.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/threads.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/tieba.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/tiktok.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/twitter.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/weibo.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/weixin.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/xhs.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/xiaoheihe.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/zhihu.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/provider_api/zuiyou.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/__init__.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/callback.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/media_file.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/media_ref.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/platform.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/post.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/types/result.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/utils/downloader.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/utils/helpers.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub/utils/media_info.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub.egg-info/SOURCES.txt +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub.egg-info/dependency_links.txt +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub.egg-info/entry_points.txt +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub.egg-info/requires.txt +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/src/parsehub.egg-info/top_level.txt +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/test/test_cli.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/test/test_cli_config.py +0 -0
- {parsehub-2.1.6 → parsehub-2.1.7}/test/test_downloader.py +0 -0
|
@@ -77,11 +77,12 @@ class BaseParser(ABC):
|
|
|
77
77
|
"""
|
|
78
78
|
raise NotImplementedError
|
|
79
79
|
|
|
80
|
-
async def get_raw_url(self, url: str, clean_all: bool = False) -> str:
|
|
80
|
+
async def get_raw_url(self, url: str, *, clean_all: bool = False, headers: dict | None = None) -> str:
|
|
81
81
|
"""
|
|
82
82
|
清除链接中的参数
|
|
83
83
|
:param url: 链接
|
|
84
84
|
:param clean_all: 是否清除全部可清除的参数 (包括解析后才需清除的参数)
|
|
85
|
+
:param headers: 请求头, 默认为 {"User-Agent": UA}, 不需要请求头需传入空字典
|
|
85
86
|
|
|
86
87
|
Example:
|
|
87
88
|
以小红书为例,其解析器配置如下::
|
|
@@ -109,13 +110,14 @@ class BaseParser(ABC):
|
|
|
109
110
|
url = matched_url or url
|
|
110
111
|
if not url.startswith("http"):
|
|
111
112
|
url = f"https://{url}"
|
|
113
|
+
|
|
112
114
|
if any(x in url for x in self.__redirect_keywords__):
|
|
113
115
|
async with httpx.AsyncClient(proxy=self.proxy, timeout=30) as client:
|
|
114
116
|
try:
|
|
115
117
|
r = await client.get(
|
|
116
118
|
url,
|
|
117
119
|
follow_redirects=True,
|
|
118
|
-
headers={"User-Agent": UA},
|
|
120
|
+
headers={"User-Agent": UA} if headers is None else headers,
|
|
119
121
|
)
|
|
120
122
|
except (httpx.ReadTimeout, httpx.ConnectTimeout) as e:
|
|
121
123
|
raise ParseError("获取原始链接超时") from e
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import cast
|
|
5
|
+
from typing import Any, cast
|
|
6
6
|
from urllib.parse import parse_qs, urlparse
|
|
7
7
|
|
|
8
8
|
from loguru import logger
|
|
@@ -34,7 +34,7 @@ class BiliParse(BaseParser):
|
|
|
34
34
|
async def _do_parse(self, raw_url: str) -> YtVideoParseResult | BiliVideoParseResult | ImageParseResult:
|
|
35
35
|
if await self.is_dynamic(raw_url):
|
|
36
36
|
dynamic = await self.get_dynamic_info(raw_url)
|
|
37
|
-
content = self.hashtag_handler(dynamic.content)
|
|
37
|
+
content = self.hashtag_handler(dynamic.content or "")
|
|
38
38
|
photos: list[LivePhotoRef | ImageRef] = []
|
|
39
39
|
if dynamic.images:
|
|
40
40
|
for i in dynamic.images:
|
|
@@ -43,7 +43,7 @@ class BiliParse(BaseParser):
|
|
|
43
43
|
else:
|
|
44
44
|
photos.append(ImageRef(url=i.url, width=i.width, height=i.height))
|
|
45
45
|
return ImageParseResult(
|
|
46
|
-
title=dynamic.title,
|
|
46
|
+
title=dynamic.title or "",
|
|
47
47
|
content=content,
|
|
48
48
|
photo=photos,
|
|
49
49
|
)
|
|
@@ -65,18 +65,18 @@ class BiliParse(BaseParser):
|
|
|
65
65
|
return False
|
|
66
66
|
|
|
67
67
|
@classmethod
|
|
68
|
-
def match(cls,
|
|
69
|
-
if cls._is_bvid(
|
|
68
|
+
def match(cls, text: str) -> bool:
|
|
69
|
+
if cls._is_bvid(text):
|
|
70
70
|
return True
|
|
71
71
|
else:
|
|
72
|
-
return super().match(
|
|
72
|
+
return super().match(text)
|
|
73
73
|
|
|
74
|
-
async def get_raw_url(self, url: str,
|
|
74
|
+
async def get_raw_url(self, url: str, **kwargs: Any) -> str:
|
|
75
75
|
"""获取原始链接"""
|
|
76
76
|
if self._is_bvid(url):
|
|
77
77
|
return f"https://www.bilibili.com/video/{url}"
|
|
78
78
|
else:
|
|
79
|
-
return await super().get_raw_url(url,
|
|
79
|
+
return await super().get_raw_url(url, **kwargs)
|
|
80
80
|
|
|
81
81
|
@staticmethod
|
|
82
82
|
async def is_dynamic(url: str) -> str | None:
|
|
@@ -86,14 +86,15 @@ class BiliParse(BaseParser):
|
|
|
86
86
|
return None
|
|
87
87
|
|
|
88
88
|
async def get_dynamic_info(self, url: str) -> BiliDynamic:
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
try:
|
|
90
|
+
async with BiliAPI(proxy=self.proxy) as bili:
|
|
91
91
|
dynamic_info = await bili.get_dynamic_info(url, cookie=self.cookie.get_value())
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
except Exception as e:
|
|
93
|
+
if "风控" in str(e):
|
|
94
|
+
raise ParseError(f"账号风控\n使用的cookie: {self.cookie}") from e
|
|
95
|
+
raise ParseError(str(e)) from e
|
|
96
|
+
else:
|
|
97
|
+
return cast(BiliDynamic, dynamic_info)
|
|
97
98
|
|
|
98
99
|
async def bili_api_parse(self, url: str) -> BiliVideoParseResult | ImageParseResult:
|
|
99
100
|
async with BiliAPI(proxy=self.proxy) as bili:
|
|
@@ -61,7 +61,7 @@ class InstagramParser(BaseParser):
|
|
|
61
61
|
else:
|
|
62
62
|
raise ParseError("受限视频无法解析: 你必须年满 18 周岁才能观看这个视频") from e
|
|
63
63
|
case _:
|
|
64
|
-
raise ParseError("无法获取帖子内容
|
|
64
|
+
raise ParseError("无法获取帖子内容") from e
|
|
65
65
|
except Exception as e:
|
|
66
66
|
if cookie:
|
|
67
67
|
text = f"Instagram 账号可能已被封禁\n\n使用的Cookie: {cookie}"
|
|
@@ -6,7 +6,11 @@ from ..base.base import BaseParser
|
|
|
6
6
|
class ThreadsParser(BaseParser):
|
|
7
7
|
__platform__ = Platform.THREADS
|
|
8
8
|
__supported_type__ = ["视频", "图文"]
|
|
9
|
-
__match__ = r"^(http(s)?://)?.+threads.com
|
|
9
|
+
__match__ = r"^(http(s)?://)?.+threads.com/(@[\w.]+/post|share)/.*"
|
|
10
|
+
__redirect_keywords__ = ["/share/"]
|
|
11
|
+
|
|
12
|
+
async def get_raw_url(self, url: str, *, clean_all: bool = False, headers: dict | None = None) -> str:
|
|
13
|
+
return await super().get_raw_url(url, clean_all=clean_all, headers={})
|
|
10
14
|
|
|
11
15
|
async def _do_parse(self, raw_url: str) -> "MultimediaParseResult":
|
|
12
16
|
post = await self._parse(raw_url)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from typing import Any
|
|
1
2
|
from urllib.parse import urlparse, urlunparse
|
|
2
3
|
|
|
3
4
|
from ...provider_api.twitter import (
|
|
@@ -29,8 +30,8 @@ class TwitterParser(BaseParser):
|
|
|
29
30
|
tweet = await self._parse(raw_url)
|
|
30
31
|
return await self.media_parse(tweet)
|
|
31
32
|
|
|
32
|
-
async def get_raw_url(self, url: str,
|
|
33
|
-
url = await super().get_raw_url(url,
|
|
33
|
+
async def get_raw_url(self, url: str, **kwargs: Any) -> str:
|
|
34
|
+
url = await super().get_raw_url(url, **kwargs)
|
|
34
35
|
return str(urlunparse(urlparse(url)._replace(netloc="x.com")))
|
|
35
36
|
|
|
36
37
|
async def _parse(self, url: str) -> TwitterTweet:
|
|
@@ -8,7 +8,7 @@ from dataclasses import dataclass
|
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from functools import reduce
|
|
10
10
|
from hashlib import md5
|
|
11
|
-
from typing import Any, cast
|
|
11
|
+
from typing import Any, Self, cast
|
|
12
12
|
|
|
13
13
|
import httpx
|
|
14
14
|
|
|
@@ -245,12 +245,12 @@ class BiliImage:
|
|
|
245
245
|
|
|
246
246
|
@dataclass(kw_only=True)
|
|
247
247
|
class BiliDynamic:
|
|
248
|
-
title: str = ""
|
|
249
|
-
content: str = ""
|
|
248
|
+
title: str | None = ""
|
|
249
|
+
content: str | None = ""
|
|
250
250
|
images: list[BiliImage] | None = None
|
|
251
251
|
|
|
252
252
|
@classmethod
|
|
253
|
-
def parse(cls, data: dict) ->
|
|
253
|
+
def parse(cls, data: dict) -> Self:
|
|
254
254
|
module_dynamic: dict = data["item"]["modules"]["module_dynamic"]
|
|
255
255
|
major: dict | None = module_dynamic.get("major", None)
|
|
256
256
|
if not major:
|
|
@@ -259,9 +259,9 @@ class BiliDynamic:
|
|
|
259
259
|
return cls._parse_major(module_dynamic, major)
|
|
260
260
|
|
|
261
261
|
@classmethod
|
|
262
|
-
def _parse_major(cls, module_dynamic: dict, major: dict) ->
|
|
262
|
+
def _parse_major(cls, module_dynamic: dict, major: dict) -> Self:
|
|
263
263
|
major_type = major["type"]
|
|
264
|
-
major_parsers: dict[MajorType, Callable[[dict, dict],
|
|
264
|
+
major_parsers: dict[MajorType, Callable[[dict, dict], Self]] = {
|
|
265
265
|
MajorType.MAJOR_TYPE_MEDIALIST: cls._parse_medialist,
|
|
266
266
|
MajorType.MAJOR_TYPE_UPOWER_COMMON: cls._parse_upower_common,
|
|
267
267
|
MajorType.MAJOR_TYPE_COMMON: cls._parse_common,
|
|
@@ -279,30 +279,30 @@ class BiliDynamic:
|
|
|
279
279
|
return major_parser(module_dynamic, major)
|
|
280
280
|
|
|
281
281
|
@classmethod
|
|
282
|
-
def _parse_pgc_union(cls, _: dict, major: dict) ->
|
|
282
|
+
def _parse_pgc_union(cls, _: dict, major: dict) -> Self:
|
|
283
283
|
pgc = major["pgc"]
|
|
284
284
|
return cls(title=pgc["title"], images=[BiliImage(url=pgc["cover"])])
|
|
285
285
|
|
|
286
286
|
@classmethod
|
|
287
|
-
def _parse_forward(cls, module_dynamic: dict) ->
|
|
287
|
+
def _parse_forward(cls, module_dynamic: dict) -> Self:
|
|
288
288
|
return cls(content=cls._get_desc_text(module_dynamic))
|
|
289
289
|
|
|
290
290
|
@classmethod
|
|
291
|
-
def _parse_av(cls, module_dynamic: dict, major: dict):
|
|
291
|
+
def _parse_av(cls, module_dynamic: dict, major: dict) -> Self:
|
|
292
292
|
if content := cls._get_desc_text(module_dynamic):
|
|
293
293
|
return cls(content=content)
|
|
294
294
|
archive = major["archive"]
|
|
295
295
|
return cls(title=archive["title"], content=archive["desc"], images=cls._get_major_cover(archive))
|
|
296
296
|
|
|
297
297
|
@classmethod
|
|
298
|
-
def _parse_music(cls, module_dynamic: dict, major: dict):
|
|
298
|
+
def _parse_music(cls, module_dynamic: dict, major: dict) -> Self:
|
|
299
299
|
if content := cls._get_desc_text(module_dynamic):
|
|
300
300
|
return cls(content=content)
|
|
301
301
|
music = major["music"]
|
|
302
302
|
return cls(title=music["title"], images=cls._get_major_cover(music))
|
|
303
303
|
|
|
304
304
|
@classmethod
|
|
305
|
-
def _parse_opus(cls, _: dict, major: dict) ->
|
|
305
|
+
def _parse_opus(cls, _: dict, major: dict) -> Self:
|
|
306
306
|
opus = major["opus"]
|
|
307
307
|
images = None
|
|
308
308
|
if pics := opus["pics"]:
|
|
@@ -312,14 +312,14 @@ class BiliDynamic:
|
|
|
312
312
|
return cls(title=opus["title"], content=opus["summary"]["text"], images=images)
|
|
313
313
|
|
|
314
314
|
@classmethod
|
|
315
|
-
def _parse_common(cls, module_dynamic: dict, major: dict):
|
|
315
|
+
def _parse_common(cls, module_dynamic: dict, major: dict) -> Self:
|
|
316
316
|
if content := cls._get_desc_text(module_dynamic):
|
|
317
317
|
return cls(content=content)
|
|
318
318
|
common = major["common"]
|
|
319
319
|
return cls(title=common["title"], content=common["desc"], images=cls._get_major_cover(common))
|
|
320
320
|
|
|
321
321
|
@classmethod
|
|
322
|
-
def _parse_live(cls, module_dynamic: dict, major: dict):
|
|
322
|
+
def _parse_live(cls, module_dynamic: dict, major: dict) -> Self:
|
|
323
323
|
if content := cls._get_desc_text(module_dynamic):
|
|
324
324
|
return cls(content=content)
|
|
325
325
|
live = major["live"]
|
|
@@ -327,14 +327,14 @@ class BiliDynamic:
|
|
|
327
327
|
return cls(title=live["title"], content=content, images=cls._get_major_cover(live))
|
|
328
328
|
|
|
329
329
|
@classmethod
|
|
330
|
-
def _parse_medialist(cls, module_dynamic: dict, major: dict):
|
|
330
|
+
def _parse_medialist(cls, module_dynamic: dict, major: dict) -> Self:
|
|
331
331
|
if content := cls._get_desc_text(module_dynamic):
|
|
332
332
|
return cls(content=content)
|
|
333
333
|
medialist = major["medialist"]
|
|
334
334
|
return cls(title=medialist["title"], content=medialist["sub_title"], images=cls._get_major_cover(medialist))
|
|
335
335
|
|
|
336
336
|
@classmethod
|
|
337
|
-
def _parse_courses(cls, module_dynamic: dict, major: dict):
|
|
337
|
+
def _parse_courses(cls, module_dynamic: dict, major: dict) -> Self:
|
|
338
338
|
if content := cls._get_desc_text(module_dynamic):
|
|
339
339
|
return cls(content=content)
|
|
340
340
|
courses = major["courses"]
|
|
@@ -342,14 +342,14 @@ class BiliDynamic:
|
|
|
342
342
|
return cls(title=courses["title"], content=content, images=cls._get_major_cover(courses))
|
|
343
343
|
|
|
344
344
|
@classmethod
|
|
345
|
-
def _parse_ugc_season(cls, module_dynamic: dict, major: dict):
|
|
345
|
+
def _parse_ugc_season(cls, module_dynamic: dict, major: dict) -> Self:
|
|
346
346
|
if content := cls._get_desc_text(module_dynamic):
|
|
347
347
|
return cls(content=content)
|
|
348
348
|
ugc_season = major["ugc_season"]
|
|
349
349
|
return cls(title=ugc_season["title"], content=ugc_season["desc"], images=cls._get_major_cover(ugc_season))
|
|
350
350
|
|
|
351
351
|
@classmethod
|
|
352
|
-
def _parse_upower_common(cls, module_dynamic: dict, major: dict):
|
|
352
|
+
def _parse_upower_common(cls, module_dynamic: dict, major: dict) -> Self:
|
|
353
353
|
if content := cls._get_desc_text(module_dynamic):
|
|
354
354
|
return cls(content=content)
|
|
355
355
|
upower_common = major["upower_common"]
|
|
@@ -348,6 +348,7 @@ class TestPlatformUrlMatching(unittest.TestCase):
|
|
|
348
348
|
Platform.THREADS: [
|
|
349
349
|
"https://www.threads.com/@zaborona.magazine/post/DBuqMBwMfxW",
|
|
350
350
|
"https://www.threads.com/@user_name/post/DBuqMBwMfxW",
|
|
351
|
+
"https://www.threads.com/share/Dq6fjYWK-/",
|
|
351
352
|
],
|
|
352
353
|
Platform.TIEBA: [
|
|
353
354
|
"https://tieba.baidu.com/p/9939510114",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|