webscout 8.3.7__py3-none-any.whl → 2025.10.11__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.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/AIauto.py +250 -250
- webscout/AIbase.py +379 -379
- webscout/AIutel.py +60 -60
- webscout/Bard.py +1012 -1012
- webscout/Bing_search.py +417 -417
- webscout/DWEBS.py +529 -529
- webscout/Extra/Act.md +309 -309
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/README.md +110 -110
- webscout/Extra/GitToolkit/gitapi/__init__.py +11 -11
- webscout/Extra/GitToolkit/gitapi/repository.py +195 -195
- webscout/Extra/GitToolkit/gitapi/user.py +96 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +61 -61
- webscout/Extra/YTToolkit/README.md +375 -375
- webscout/Extra/YTToolkit/YTdownloader.py +956 -956
- webscout/Extra/YTToolkit/__init__.py +2 -2
- webscout/Extra/YTToolkit/transcriber.py +475 -475
- webscout/Extra/YTToolkit/ytapi/README.md +44 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +6 -6
- webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +118 -118
- webscout/Extra/YTToolkit/ytapi/https.py +88 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
- webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
- webscout/Extra/YTToolkit/ytapi/query.py +39 -39
- webscout/Extra/YTToolkit/ytapi/stream.py +62 -62
- webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
- webscout/Extra/YTToolkit/ytapi/video.py +232 -232
- webscout/Extra/autocoder/__init__.py +9 -9
- webscout/Extra/autocoder/autocoder.py +1105 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +332 -332
- webscout/Extra/gguf.md +429 -429
- webscout/Extra/gguf.py +1213 -1213
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +27 -27
- webscout/Extra/tempmail/async_utils.py +140 -140
- webscout/Extra/tempmail/base.py +160 -160
- webscout/Extra/tempmail/cli.py +186 -186
- webscout/Extra/tempmail/emailnator.py +84 -84
- webscout/Extra/tempmail/mail_tm.py +360 -360
- webscout/Extra/tempmail/temp_mail_io.py +291 -291
- webscout/Extra/weather.md +281 -281
- webscout/Extra/weather.py +193 -193
- webscout/Litlogger/README.md +10 -10
- webscout/Litlogger/__init__.py +15 -15
- webscout/Litlogger/formats.py +13 -13
- webscout/Litlogger/handlers.py +121 -121
- webscout/Litlogger/levels.py +13 -13
- webscout/Litlogger/logger.py +134 -134
- webscout/Provider/AISEARCH/Perplexity.py +332 -332
- webscout/Provider/AISEARCH/README.md +279 -279
- webscout/Provider/AISEARCH/__init__.py +16 -1
- webscout/Provider/AISEARCH/felo_search.py +206 -206
- webscout/Provider/AISEARCH/genspark_search.py +323 -323
- webscout/Provider/AISEARCH/hika_search.py +185 -185
- webscout/Provider/AISEARCH/iask_search.py +410 -410
- webscout/Provider/AISEARCH/monica_search.py +219 -219
- webscout/Provider/AISEARCH/scira_search.py +316 -316
- webscout/Provider/AISEARCH/stellar_search.py +177 -177
- webscout/Provider/AISEARCH/webpilotai_search.py +255 -255
- webscout/Provider/Aitopia.py +314 -314
- webscout/Provider/Apriel.py +306 -0
- webscout/Provider/ChatGPTClone.py +236 -236
- webscout/Provider/ChatSandbox.py +343 -343
- webscout/Provider/Cloudflare.py +324 -324
- webscout/Provider/Cohere.py +208 -208
- webscout/Provider/Deepinfra.py +370 -366
- webscout/Provider/ExaAI.py +260 -260
- webscout/Provider/ExaChat.py +308 -308
- webscout/Provider/Flowith.py +221 -221
- webscout/Provider/GMI.py +293 -0
- webscout/Provider/Gemini.py +164 -164
- webscout/Provider/GeminiProxy.py +167 -167
- webscout/Provider/GithubChat.py +371 -372
- webscout/Provider/Groq.py +800 -800
- webscout/Provider/HeckAI.py +383 -383
- webscout/Provider/Jadve.py +282 -282
- webscout/Provider/K2Think.py +307 -307
- webscout/Provider/Koboldai.py +205 -205
- webscout/Provider/LambdaChat.py +423 -423
- webscout/Provider/Nemotron.py +244 -244
- webscout/Provider/Netwrck.py +248 -248
- webscout/Provider/OLLAMA.py +395 -395
- webscout/Provider/OPENAI/Cloudflare.py +393 -393
- webscout/Provider/OPENAI/FalconH1.py +451 -451
- webscout/Provider/OPENAI/FreeGemini.py +296 -296
- webscout/Provider/OPENAI/K2Think.py +431 -431
- webscout/Provider/OPENAI/NEMOTRON.py +240 -240
- webscout/Provider/OPENAI/PI.py +427 -427
- webscout/Provider/OPENAI/README.md +959 -959
- webscout/Provider/OPENAI/TogetherAI.py +345 -345
- webscout/Provider/OPENAI/TwoAI.py +465 -465
- webscout/Provider/OPENAI/__init__.py +33 -18
- webscout/Provider/OPENAI/base.py +248 -248
- webscout/Provider/OPENAI/chatglm.py +528 -0
- webscout/Provider/OPENAI/chatgpt.py +592 -592
- webscout/Provider/OPENAI/chatgptclone.py +521 -521
- webscout/Provider/OPENAI/chatsandbox.py +202 -202
- webscout/Provider/OPENAI/deepinfra.py +318 -314
- webscout/Provider/OPENAI/e2b.py +1665 -1665
- webscout/Provider/OPENAI/exaai.py +420 -420
- webscout/Provider/OPENAI/exachat.py +452 -452
- webscout/Provider/OPENAI/friendli.py +232 -232
- webscout/Provider/OPENAI/{refact.py → gmi.py} +324 -274
- webscout/Provider/OPENAI/groq.py +364 -364
- webscout/Provider/OPENAI/heckai.py +314 -314
- webscout/Provider/OPENAI/llmchatco.py +337 -337
- webscout/Provider/OPENAI/netwrck.py +355 -355
- webscout/Provider/OPENAI/oivscode.py +290 -290
- webscout/Provider/OPENAI/opkfc.py +518 -518
- webscout/Provider/OPENAI/pydantic_imports.py +1 -1
- webscout/Provider/OPENAI/scirachat.py +535 -535
- webscout/Provider/OPENAI/sonus.py +308 -308
- webscout/Provider/OPENAI/standardinput.py +442 -442
- webscout/Provider/OPENAI/textpollinations.py +340 -340
- webscout/Provider/OPENAI/toolbaz.py +419 -416
- webscout/Provider/OPENAI/typefully.py +362 -362
- webscout/Provider/OPENAI/utils.py +295 -295
- webscout/Provider/OPENAI/venice.py +436 -436
- webscout/Provider/OPENAI/wisecat.py +387 -387
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +378 -378
- webscout/Provider/OPENAI/yep.py +389 -389
- webscout/Provider/OpenGPT.py +230 -230
- webscout/Provider/Openai.py +243 -243
- webscout/Provider/PI.py +405 -405
- webscout/Provider/Perplexitylabs.py +430 -430
- webscout/Provider/QwenLM.py +272 -272
- webscout/Provider/STT/__init__.py +16 -1
- webscout/Provider/Sambanova.py +257 -257
- webscout/Provider/StandardInput.py +309 -309
- webscout/Provider/TTI/README.md +82 -82
- webscout/Provider/TTI/__init__.py +33 -18
- webscout/Provider/TTI/aiarta.py +413 -413
- webscout/Provider/TTI/base.py +136 -136
- webscout/Provider/TTI/bing.py +243 -243
- webscout/Provider/TTI/gpt1image.py +149 -149
- webscout/Provider/TTI/imagen.py +196 -196
- webscout/Provider/TTI/infip.py +211 -211
- webscout/Provider/TTI/magicstudio.py +232 -232
- webscout/Provider/TTI/monochat.py +219 -219
- webscout/Provider/TTI/piclumen.py +214 -214
- webscout/Provider/TTI/pixelmuse.py +232 -232
- webscout/Provider/TTI/pollinations.py +232 -232
- webscout/Provider/TTI/together.py +288 -288
- webscout/Provider/TTI/utils.py +12 -12
- webscout/Provider/TTI/venice.py +367 -367
- webscout/Provider/TTS/README.md +192 -192
- webscout/Provider/TTS/__init__.py +33 -18
- webscout/Provider/TTS/parler.py +110 -110
- webscout/Provider/TTS/streamElements.py +333 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TeachAnything.py +237 -237
- webscout/Provider/TextPollinationsAI.py +310 -310
- webscout/Provider/TogetherAI.py +356 -356
- webscout/Provider/TwoAI.py +312 -312
- webscout/Provider/TypliAI.py +311 -311
- webscout/Provider/UNFINISHED/ChatHub.py +208 -208
- webscout/Provider/UNFINISHED/ChutesAI.py +313 -313
- webscout/Provider/UNFINISHED/GizAI.py +294 -294
- webscout/Provider/UNFINISHED/Marcus.py +198 -198
- webscout/Provider/UNFINISHED/Qodo.py +477 -477
- webscout/Provider/UNFINISHED/VercelAIGateway.py +338 -338
- webscout/Provider/UNFINISHED/XenAI.py +324 -324
- webscout/Provider/UNFINISHED/Youchat.py +330 -330
- webscout/Provider/UNFINISHED/liner.py +334 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +262 -262
- webscout/Provider/UNFINISHED/puterjs.py +634 -634
- webscout/Provider/UNFINISHED/samurai.py +223 -223
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
- webscout/Provider/Venice.py +250 -250
- webscout/Provider/VercelAI.py +256 -256
- webscout/Provider/WiseCat.py +231 -231
- webscout/Provider/WrDoChat.py +366 -366
- webscout/Provider/__init__.py +33 -18
- webscout/Provider/ai4chat.py +174 -174
- webscout/Provider/akashgpt.py +331 -331
- webscout/Provider/cerebras.py +446 -446
- webscout/Provider/chatglm.py +394 -301
- webscout/Provider/cleeai.py +211 -211
- webscout/Provider/elmo.py +282 -282
- webscout/Provider/geminiapi.py +208 -208
- webscout/Provider/granite.py +261 -261
- webscout/Provider/hermes.py +263 -263
- webscout/Provider/julius.py +223 -223
- webscout/Provider/learnfastai.py +309 -309
- webscout/Provider/llama3mitril.py +214 -214
- webscout/Provider/llmchat.py +243 -243
- webscout/Provider/llmchatco.py +290 -290
- webscout/Provider/meta.py +801 -801
- webscout/Provider/oivscode.py +309 -309
- webscout/Provider/scira_chat.py +383 -383
- webscout/Provider/searchchat.py +292 -292
- webscout/Provider/sonus.py +258 -258
- webscout/Provider/toolbaz.py +370 -367
- webscout/Provider/turboseek.py +273 -273
- webscout/Provider/typefully.py +207 -207
- webscout/Provider/yep.py +372 -372
- webscout/__init__.py +30 -31
- webscout/__main__.py +5 -5
- webscout/auth/api_key_manager.py +189 -189
- webscout/auth/config.py +175 -175
- webscout/auth/models.py +185 -185
- webscout/auth/routes.py +664 -664
- webscout/auth/simple_logger.py +236 -236
- webscout/cli.py +523 -523
- webscout/conversation.py +438 -438
- webscout/exceptions.py +361 -361
- webscout/litagent/Readme.md +298 -298
- webscout/litagent/__init__.py +28 -28
- webscout/litagent/agent.py +581 -581
- webscout/litagent/constants.py +59 -59
- webscout/litprinter/__init__.py +58 -58
- webscout/models.py +181 -181
- webscout/optimizers.py +419 -419
- webscout/prompt_manager.py +288 -288
- webscout/sanitize.py +1078 -1078
- webscout/scout/README.md +401 -401
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +6 -6
- webscout/scout/core/crawler.py +297 -297
- webscout/scout/core/scout.py +706 -706
- webscout/scout/core/search_result.py +95 -95
- webscout/scout/core/text_analyzer.py +62 -62
- webscout/scout/core/text_utils.py +277 -277
- webscout/scout/core/web_analyzer.py +51 -51
- webscout/scout/element.py +599 -599
- webscout/scout/parsers/__init__.py +69 -69
- webscout/scout/parsers/html5lib_parser.py +172 -172
- webscout/scout/parsers/html_parser.py +236 -236
- webscout/scout/parsers/lxml_parser.py +178 -178
- webscout/scout/utils.py +37 -37
- webscout/swiftcli/Readme.md +323 -323
- webscout/swiftcli/__init__.py +95 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +308 -308
- webscout/swiftcli/core/context.py +104 -104
- webscout/swiftcli/core/group.py +241 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +221 -221
- webscout/swiftcli/decorators/options.py +220 -220
- webscout/swiftcli/decorators/output.py +302 -302
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +135 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +59 -59
- webscout/swiftcli/utils/formatting.py +252 -252
- webscout/swiftcli/utils/parsing.py +267 -267
- webscout/update_checker.py +117 -117
- webscout/version.py +1 -1
- webscout/webscout_search.py +1183 -1183
- webscout/webscout_search_async.py +649 -649
- webscout/yep_search.py +346 -346
- webscout/zeroart/README.md +89 -89
- webscout/zeroart/__init__.py +134 -134
- webscout/zeroart/base.py +66 -66
- webscout/zeroart/effects.py +100 -100
- webscout/zeroart/fonts.py +1238 -1238
- {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/METADATA +937 -937
- webscout-2025.10.11.dist-info/RECORD +300 -0
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/OPENAI/Qwen3.py +0 -303
- webscout/Provider/OPENAI/qodo.py +0 -630
- webscout/Provider/OPENAI/xenai.py +0 -514
- webscout/tempid.py +0 -134
- webscout-8.3.7.dist-info/RECORD +0 -301
- {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/WHEEL +0 -0
- {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import concurrent.futures
|
|
2
|
-
from typing import Callable, List, Any
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def collect(func: Callable, args: List[Any]) -> List[Any]:
|
|
6
|
-
max_workers = len(args) or 1
|
|
7
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers) as exe:
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
from typing import Callable, List, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def collect(func: Callable, args: List[Any]) -> List[Any]:
|
|
6
|
+
max_workers = len(args) or 1
|
|
7
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers) as exe:
|
|
8
8
|
return list(exe.map(func, args))
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
from .utils import dup_filter
|
|
2
|
-
from .video import Video
|
|
3
|
-
from .channel import Channel
|
|
4
|
-
from .playlist import Playlist
|
|
5
|
-
from .patterns import _QueryPatterns as Patterns
|
|
6
|
-
from typing import Optional, Dict, Any, List
|
|
7
|
-
from .https import find_videos, find_channels, find_playlists
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Search:
|
|
11
|
-
|
|
12
|
-
@staticmethod
|
|
13
|
-
def video(keywords: str) -> Optional[Video]:
|
|
14
|
-
video_ids = Patterns.video_id.findall(find_videos(keywords))
|
|
15
|
-
return Video(video_ids[0]) if video_ids else None
|
|
16
|
-
|
|
17
|
-
@staticmethod
|
|
18
|
-
def channel(keywords: str) -> Optional[Channel]:
|
|
19
|
-
channel_ids = Patterns.channel_id.findall(find_channels(keywords))
|
|
20
|
-
return Channel(channel_ids[0]) if channel_ids else None
|
|
21
|
-
|
|
22
|
-
@staticmethod
|
|
23
|
-
def playlist(keywords: str) -> Optional[Playlist]:
|
|
24
|
-
playlist_ids = Patterns.playlist_id.findall(find_playlists(keywords))
|
|
25
|
-
return Playlist(playlist_ids[0]) if playlist_ids else None
|
|
26
|
-
|
|
27
|
-
@staticmethod
|
|
28
|
-
def videos(keywords: str, limit: int = 20) -> Optional[List[str]]:
|
|
29
|
-
return dup_filter(Patterns.video_id.findall(find_videos(keywords)), limit)
|
|
30
|
-
|
|
31
|
-
@staticmethod
|
|
32
|
-
def channels(keywords: str, limit: int = 20) -> Optional[List[str]]:
|
|
33
|
-
return dup_filter(Patterns.channel_id.findall(find_channels(keywords)), limit)
|
|
34
|
-
|
|
35
|
-
@staticmethod
|
|
36
|
-
def playlists(keywords: str, limit: int = 20) -> Optional[List[str]]:
|
|
37
|
-
return dup_filter(Patterns.playlist_id.findall(find_playlists(keywords)), limit)
|
|
38
|
-
|
|
39
|
-
if __name__ == "__main__":
|
|
1
|
+
from .utils import dup_filter
|
|
2
|
+
from .video import Video
|
|
3
|
+
from .channel import Channel
|
|
4
|
+
from .playlist import Playlist
|
|
5
|
+
from .patterns import _QueryPatterns as Patterns
|
|
6
|
+
from typing import Optional, Dict, Any, List
|
|
7
|
+
from .https import find_videos, find_channels, find_playlists
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Search:
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def video(keywords: str) -> Optional[Video]:
|
|
14
|
+
video_ids = Patterns.video_id.findall(find_videos(keywords))
|
|
15
|
+
return Video(video_ids[0]) if video_ids else None
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def channel(keywords: str) -> Optional[Channel]:
|
|
19
|
+
channel_ids = Patterns.channel_id.findall(find_channels(keywords))
|
|
20
|
+
return Channel(channel_ids[0]) if channel_ids else None
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def playlist(keywords: str) -> Optional[Playlist]:
|
|
24
|
+
playlist_ids = Patterns.playlist_id.findall(find_playlists(keywords))
|
|
25
|
+
return Playlist(playlist_ids[0]) if playlist_ids else None
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def videos(keywords: str, limit: int = 20) -> Optional[List[str]]:
|
|
29
|
+
return dup_filter(Patterns.video_id.findall(find_videos(keywords)), limit)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def channels(keywords: str, limit: int = 20) -> Optional[List[str]]:
|
|
33
|
+
return dup_filter(Patterns.channel_id.findall(find_channels(keywords)), limit)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def playlists(keywords: str, limit: int = 20) -> Optional[List[str]]:
|
|
37
|
+
return dup_filter(Patterns.playlist_id.findall(find_playlists(keywords)), limit)
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
40
|
print(Search.videos("java"))
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
|
|
4
|
-
from .pool import collect
|
|
5
|
-
from .https import video_data
|
|
6
|
-
from .patterns import _VideoPatterns as Patterns
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Video:
|
|
10
|
-
|
|
11
|
-
_HEAD = 'https://www.youtube.com/watch?v='
|
|
12
|
-
|
|
13
|
-
def __init__(self, video_id: str):
|
|
14
|
-
pattern = re.compile(r'.be/(.*?)$|=(.*?)$|^(\w{11})$') # noqa
|
|
15
|
-
self._matched_id = (
|
|
16
|
-
pattern.search(video_id).group(1)
|
|
17
|
-
or pattern.search(video_id).group(2)
|
|
18
|
-
or pattern.search(video_id).group(3)
|
|
19
|
-
)
|
|
20
|
-
if self._matched_id:
|
|
21
|
-
self._url = self._HEAD + self._matched_id
|
|
22
|
-
self._video_data = video_data(self._matched_id)
|
|
23
|
-
else:
|
|
24
|
-
raise ValueError('invalid video id or url')
|
|
25
|
-
|
|
26
|
-
def __repr__(self):
|
|
27
|
-
return f'<Video {self._url}>'
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def metadata(self) -> Dict[str, Any]:
|
|
31
|
-
patterns = [
|
|
32
|
-
Patterns.title,
|
|
33
|
-
Patterns.views,
|
|
34
|
-
Patterns.likes,
|
|
35
|
-
Patterns.duration,
|
|
36
|
-
Patterns.author_id,
|
|
37
|
-
Patterns.upload_date,
|
|
38
|
-
Patterns.thumbnail,
|
|
39
|
-
Patterns.tags,
|
|
40
|
-
Patterns.description,
|
|
41
|
-
Patterns.is_streamed,
|
|
42
|
-
Patterns.is_premiered
|
|
43
|
-
]
|
|
44
|
-
ext = collect(lambda x: x.findall(self._video_data) or None, patterns)
|
|
45
|
-
data = [i[0] if i else i for i in ext]
|
|
46
|
-
return {
|
|
47
|
-
'title': data[0],
|
|
48
|
-
'id': self._matched_id,
|
|
49
|
-
'views': data[1][:-6] if data[1] else None,
|
|
50
|
-
'likes': data[2],
|
|
51
|
-
'streamed': data[9] is not None,
|
|
52
|
-
'premiered': data[10],
|
|
53
|
-
'duration': int(data[3]) / 1000 if data[3] else None,
|
|
54
|
-
'author': data[4],
|
|
55
|
-
'upload_date': data[5],
|
|
56
|
-
'url': self._url,
|
|
57
|
-
'thumbnail': data[6],
|
|
58
|
-
'tags': data[7].split(',') if data[7] else None,
|
|
59
|
-
'description': data[8].replace('\\n', '\n') if data[8] else None
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if __name__ == '__main__':
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
|
|
4
|
+
from .pool import collect
|
|
5
|
+
from .https import video_data
|
|
6
|
+
from .patterns import _VideoPatterns as Patterns
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Video:
|
|
10
|
+
|
|
11
|
+
_HEAD = 'https://www.youtube.com/watch?v='
|
|
12
|
+
|
|
13
|
+
def __init__(self, video_id: str):
|
|
14
|
+
pattern = re.compile(r'.be/(.*?)$|=(.*?)$|^(\w{11})$') # noqa
|
|
15
|
+
self._matched_id = (
|
|
16
|
+
pattern.search(video_id).group(1)
|
|
17
|
+
or pattern.search(video_id).group(2)
|
|
18
|
+
or pattern.search(video_id).group(3)
|
|
19
|
+
)
|
|
20
|
+
if self._matched_id:
|
|
21
|
+
self._url = self._HEAD + self._matched_id
|
|
22
|
+
self._video_data = video_data(self._matched_id)
|
|
23
|
+
else:
|
|
24
|
+
raise ValueError('invalid video id or url')
|
|
25
|
+
|
|
26
|
+
def __repr__(self):
|
|
27
|
+
return f'<Video {self._url}>'
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def metadata(self) -> Dict[str, Any]:
|
|
31
|
+
patterns = [
|
|
32
|
+
Patterns.title,
|
|
33
|
+
Patterns.views,
|
|
34
|
+
Patterns.likes,
|
|
35
|
+
Patterns.duration,
|
|
36
|
+
Patterns.author_id,
|
|
37
|
+
Patterns.upload_date,
|
|
38
|
+
Patterns.thumbnail,
|
|
39
|
+
Patterns.tags,
|
|
40
|
+
Patterns.description,
|
|
41
|
+
Patterns.is_streamed,
|
|
42
|
+
Patterns.is_premiered
|
|
43
|
+
]
|
|
44
|
+
ext = collect(lambda x: x.findall(self._video_data) or None, patterns)
|
|
45
|
+
data = [i[0] if i else i for i in ext]
|
|
46
|
+
return {
|
|
47
|
+
'title': data[0],
|
|
48
|
+
'id': self._matched_id,
|
|
49
|
+
'views': data[1][:-6] if data[1] else None,
|
|
50
|
+
'likes': data[2],
|
|
51
|
+
'streamed': data[9] is not None,
|
|
52
|
+
'premiered': data[10],
|
|
53
|
+
'duration': int(data[3]) / 1000 if data[3] else None,
|
|
54
|
+
'author': data[4],
|
|
55
|
+
'upload_date': data[5],
|
|
56
|
+
'url': self._url,
|
|
57
|
+
'thumbnail': data[6],
|
|
58
|
+
'tags': data[7].split(',') if data[7] else None,
|
|
59
|
+
'description': data[8].replace('\\n', '\n') if data[8] else None
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
63
|
print(Video('https://www.youtube.com/watch?v=9bZkp7q19f0').metadata)
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
from urllib.request import Request, urlopen
|
|
2
|
-
from collections import OrderedDict
|
|
3
|
-
from urllib.error import HTTPError
|
|
4
|
-
from .errors import TooManyRequests, InvalidURL, RequestError
|
|
5
|
-
from webscout.litagent import LitAgent
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
__all__ = ['dup_filter', 'request']
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
_USER_AGENT_GENERATOR = LitAgent()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def request(url: str, retry_attempts: int = 3) -> str:
|
|
15
|
-
"""
|
|
16
|
-
Send a request with a random user agent and built-in retry mechanism.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
url (str): The URL to request
|
|
20
|
-
retry_attempts (int, optional): Number of retry attempts. Defaults to 3.
|
|
21
|
-
|
|
22
|
-
Raises:
|
|
23
|
-
InvalidURL: If the URL cannot be found
|
|
24
|
-
TooManyRequests: If rate-limited
|
|
25
|
-
RequestError: For other request-related errors
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
str: Decoded response content
|
|
29
|
-
"""
|
|
30
|
-
for attempt in range(retry_attempts):
|
|
31
|
-
try:
|
|
32
|
-
headers = {
|
|
33
|
-
"User-Agent": _USER_AGENT_GENERATOR.random()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
req = Request(url, headers=headers)
|
|
37
|
-
response = urlopen(req)
|
|
38
|
-
return response.read().decode('utf-8')
|
|
39
|
-
|
|
40
|
-
except HTTPError as e:
|
|
41
|
-
if e.code == 404:
|
|
42
|
-
raise InvalidURL(f'Cannot find anything with the requested URL: {url}')
|
|
43
|
-
if e.code == 429:
|
|
44
|
-
raise TooManyRequests(f'Rate-limited on attempt {attempt + 1}')
|
|
45
|
-
|
|
46
|
-
if attempt == retry_attempts - 1:
|
|
47
|
-
raise RequestError(f'HTTP Error {e.code}: {e.reason}') from e
|
|
48
|
-
|
|
49
|
-
except Exception as e:
|
|
50
|
-
if attempt == retry_attempts - 1:
|
|
51
|
-
raise RequestError(f'Request failed: {e!r}') from None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def dup_filter(iterable: list, limit: int = None) -> list:
|
|
55
|
-
if not iterable:
|
|
56
|
-
return []
|
|
57
|
-
lim = limit if limit else len(iterable)
|
|
58
|
-
converted = list(OrderedDict.fromkeys(iterable))
|
|
59
|
-
if len(converted) - lim > 0:
|
|
60
|
-
return converted[:-len(converted) + lim]
|
|
61
|
-
else:
|
|
62
|
-
return converted
|
|
1
|
+
from urllib.request import Request, urlopen
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from urllib.error import HTTPError
|
|
4
|
+
from .errors import TooManyRequests, InvalidURL, RequestError
|
|
5
|
+
from webscout.litagent import LitAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = ['dup_filter', 'request']
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_USER_AGENT_GENERATOR = LitAgent()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def request(url: str, retry_attempts: int = 3) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Send a request with a random user agent and built-in retry mechanism.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
url (str): The URL to request
|
|
20
|
+
retry_attempts (int, optional): Number of retry attempts. Defaults to 3.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
InvalidURL: If the URL cannot be found
|
|
24
|
+
TooManyRequests: If rate-limited
|
|
25
|
+
RequestError: For other request-related errors
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: Decoded response content
|
|
29
|
+
"""
|
|
30
|
+
for attempt in range(retry_attempts):
|
|
31
|
+
try:
|
|
32
|
+
headers = {
|
|
33
|
+
"User-Agent": _USER_AGENT_GENERATOR.random()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
req = Request(url, headers=headers)
|
|
37
|
+
response = urlopen(req)
|
|
38
|
+
return response.read().decode('utf-8')
|
|
39
|
+
|
|
40
|
+
except HTTPError as e:
|
|
41
|
+
if e.code == 404:
|
|
42
|
+
raise InvalidURL(f'Cannot find anything with the requested URL: {url}')
|
|
43
|
+
if e.code == 429:
|
|
44
|
+
raise TooManyRequests(f'Rate-limited on attempt {attempt + 1}')
|
|
45
|
+
|
|
46
|
+
if attempt == retry_attempts - 1:
|
|
47
|
+
raise RequestError(f'HTTP Error {e.code}: {e.reason}') from e
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
if attempt == retry_attempts - 1:
|
|
51
|
+
raise RequestError(f'Request failed: {e!r}') from None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def dup_filter(iterable: list, limit: int = None) -> list:
|
|
55
|
+
if not iterable:
|
|
56
|
+
return []
|
|
57
|
+
lim = limit if limit else len(iterable)
|
|
58
|
+
converted = list(OrderedDict.fromkeys(iterable))
|
|
59
|
+
if len(converted) - lim > 0:
|
|
60
|
+
return converted[:-len(converted) + lim]
|
|
61
|
+
else:
|
|
62
|
+
return converted
|