webscout 7.1__py3-none-any.whl → 7.3__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 +191 -191
- webscout/AIbase.py +122 -122
- webscout/AIutel.py +440 -440
- webscout/Bard.py +343 -161
- webscout/DWEBS.py +489 -492
- webscout/Extra/YTToolkit/YTdownloader.py +995 -995
- webscout/Extra/YTToolkit/__init__.py +2 -2
- webscout/Extra/YTToolkit/transcriber.py +476 -479
- webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
- webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
- webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
- webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
- webscout/Extra/YTToolkit/ytapi/video.py +103 -103
- webscout/Extra/autocoder/__init__.py +9 -9
- webscout/Extra/autocoder/autocoder_utiles.py +199 -199
- webscout/Extra/autocoder/rawdog.py +5 -7
- webscout/Extra/autollama.py +230 -230
- webscout/Extra/gguf.py +3 -3
- webscout/Extra/weather.py +171 -171
- webscout/LLM.py +442 -442
- webscout/Litlogger/__init__.py +67 -681
- webscout/Litlogger/core/__init__.py +6 -0
- webscout/Litlogger/core/level.py +23 -0
- webscout/Litlogger/core/logger.py +166 -0
- webscout/Litlogger/handlers/__init__.py +12 -0
- webscout/Litlogger/handlers/console.py +33 -0
- webscout/Litlogger/handlers/file.py +143 -0
- webscout/Litlogger/handlers/network.py +173 -0
- webscout/Litlogger/styles/__init__.py +7 -0
- webscout/Litlogger/styles/colors.py +249 -0
- webscout/Litlogger/styles/formats.py +460 -0
- webscout/Litlogger/styles/text.py +87 -0
- webscout/Litlogger/utils/__init__.py +6 -0
- webscout/Litlogger/utils/detectors.py +154 -0
- webscout/Litlogger/utils/formatters.py +200 -0
- webscout/Provider/AISEARCH/DeepFind.py +250 -250
- webscout/Provider/AISEARCH/ISou.py +277 -0
- webscout/Provider/AISEARCH/__init__.py +2 -1
- webscout/Provider/Blackboxai.py +3 -3
- webscout/Provider/ChatGPTGratis.py +226 -0
- webscout/Provider/Cloudflare.py +3 -4
- webscout/Provider/DeepSeek.py +218 -0
- webscout/Provider/Deepinfra.py +40 -24
- webscout/Provider/Free2GPT.py +131 -124
- webscout/Provider/Gemini.py +100 -115
- webscout/Provider/Glider.py +3 -3
- webscout/Provider/Groq.py +5 -1
- webscout/Provider/Jadve.py +3 -3
- webscout/Provider/Marcus.py +191 -192
- webscout/Provider/Netwrck.py +3 -3
- webscout/Provider/PI.py +2 -2
- webscout/Provider/PizzaGPT.py +2 -3
- webscout/Provider/QwenLM.py +311 -0
- webscout/Provider/TTI/AiForce/__init__.py +22 -22
- webscout/Provider/TTI/AiForce/async_aiforce.py +257 -257
- webscout/Provider/TTI/AiForce/sync_aiforce.py +242 -242
- webscout/Provider/TTI/FreeAIPlayground/__init__.py +9 -0
- webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +206 -0
- webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +192 -0
- webscout/Provider/TTI/Nexra/__init__.py +22 -22
- webscout/Provider/TTI/Nexra/async_nexra.py +286 -286
- webscout/Provider/TTI/Nexra/sync_nexra.py +258 -258
- webscout/Provider/TTI/PollinationsAI/__init__.py +23 -23
- webscout/Provider/TTI/PollinationsAI/async_pollinations.py +330 -330
- webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +285 -285
- webscout/Provider/TTI/__init__.py +2 -1
- webscout/Provider/TTI/artbit/__init__.py +22 -22
- webscout/Provider/TTI/artbit/async_artbit.py +184 -184
- webscout/Provider/TTI/artbit/sync_artbit.py +176 -176
- webscout/Provider/TTI/blackbox/__init__.py +4 -4
- webscout/Provider/TTI/blackbox/async_blackbox.py +212 -212
- webscout/Provider/TTI/blackbox/sync_blackbox.py +199 -199
- webscout/Provider/TTI/deepinfra/__init__.py +4 -4
- webscout/Provider/TTI/deepinfra/async_deepinfra.py +227 -227
- webscout/Provider/TTI/deepinfra/sync_deepinfra.py +199 -199
- webscout/Provider/TTI/huggingface/__init__.py +22 -22
- webscout/Provider/TTI/huggingface/async_huggingface.py +199 -199
- webscout/Provider/TTI/huggingface/sync_huggingface.py +195 -195
- webscout/Provider/TTI/imgninza/__init__.py +4 -4
- webscout/Provider/TTI/imgninza/async_ninza.py +214 -214
- webscout/Provider/TTI/imgninza/sync_ninza.py +209 -209
- webscout/Provider/TTI/talkai/__init__.py +4 -4
- webscout/Provider/TTI/talkai/async_talkai.py +229 -229
- webscout/Provider/TTI/talkai/sync_talkai.py +207 -207
- webscout/Provider/TTS/deepgram.py +182 -182
- webscout/Provider/TTS/elevenlabs.py +136 -136
- webscout/Provider/TTS/gesserit.py +150 -150
- webscout/Provider/TTS/murfai.py +138 -138
- webscout/Provider/TTS/parler.py +133 -134
- webscout/Provider/TTS/streamElements.py +360 -360
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TTS/voicepod.py +116 -116
- webscout/Provider/TextPollinationsAI.py +28 -8
- webscout/Provider/WiseCat.py +193 -0
- webscout/Provider/__init__.py +146 -134
- webscout/Provider/cerebras.py +242 -227
- webscout/Provider/chatglm.py +204 -204
- webscout/Provider/dgaf.py +2 -3
- webscout/Provider/freeaichat.py +221 -0
- webscout/Provider/gaurish.py +2 -3
- webscout/Provider/geminiapi.py +208 -208
- webscout/Provider/granite.py +223 -0
- webscout/Provider/hermes.py +218 -218
- webscout/Provider/llama3mitril.py +179 -179
- webscout/Provider/llamatutor.py +3 -3
- webscout/Provider/llmchat.py +2 -3
- webscout/Provider/meta.py +794 -794
- webscout/Provider/multichat.py +331 -331
- webscout/Provider/typegpt.py +359 -359
- webscout/Provider/yep.py +3 -3
- webscout/__init__.py +1 -0
- webscout/__main__.py +5 -5
- webscout/cli.py +319 -319
- webscout/conversation.py +241 -242
- webscout/exceptions.py +328 -328
- webscout/litagent/__init__.py +28 -28
- webscout/litagent/agent.py +2 -3
- webscout/litprinter/__init__.py +0 -58
- webscout/scout/__init__.py +8 -8
- webscout/scout/core.py +884 -884
- webscout/scout/element.py +459 -459
- 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 +38 -38
- webscout/swiftcli/__init__.py +811 -811
- webscout/update_checker.py +2 -12
- webscout/version.py +1 -1
- webscout/webscout_search.py +87 -6
- webscout/webscout_search_async.py +58 -1
- webscout/yep_search.py +297 -0
- webscout/zeroart/__init__.py +54 -54
- webscout/zeroart/base.py +60 -60
- webscout/zeroart/effects.py +99 -99
- webscout/zeroart/fonts.py +816 -816
- {webscout-7.1.dist-info → webscout-7.3.dist-info}/METADATA +62 -22
- webscout-7.3.dist-info/RECORD +223 -0
- {webscout-7.1.dist-info → webscout-7.3.dist-info}/WHEEL +1 -1
- webstoken/__init__.py +30 -30
- webstoken/classifier.py +189 -189
- webstoken/keywords.py +216 -216
- webstoken/language.py +128 -128
- webstoken/ner.py +164 -164
- webstoken/normalizer.py +35 -35
- webstoken/processor.py +77 -77
- webstoken/sentiment.py +206 -206
- webstoken/stemmer.py +73 -73
- webstoken/tagger.py +60 -60
- webstoken/tokenizer.py +158 -158
- webscout-7.1.dist-info/RECORD +0 -198
- {webscout-7.1.dist-info → webscout-7.3.dist-info}/LICENSE.md +0 -0
- {webscout-7.1.dist-info → webscout-7.3.dist-info}/entry_points.txt +0 -0
- {webscout-7.1.dist-info → webscout-7.3.dist-info}/top_level.txt +0 -0
webscout/update_checker.py
CHANGED
|
@@ -15,7 +15,7 @@ import requests
|
|
|
15
15
|
from packaging import version
|
|
16
16
|
import re
|
|
17
17
|
|
|
18
|
-
from webscout import
|
|
18
|
+
from webscout.Litlogger import Logger
|
|
19
19
|
from importlib.metadata import version as get_package_version
|
|
20
20
|
from importlib.metadata import PackageNotFoundError
|
|
21
21
|
from importlib.metadata import metadata as get_package_metadata
|
|
@@ -23,19 +23,9 @@ from importlib.metadata import metadata as get_package_metadata
|
|
|
23
23
|
# Setting up that clean logger format, no cap! 💯
|
|
24
24
|
CUSTOM_FORMAT = """{message}"""
|
|
25
25
|
|
|
26
|
-
logger =
|
|
26
|
+
logger = Logger(
|
|
27
27
|
name="WebscoutUpdate",
|
|
28
28
|
format=CUSTOM_FORMAT,
|
|
29
|
-
color_scheme=ColorScheme.OCEAN,
|
|
30
|
-
level_styles={
|
|
31
|
-
"TRACE": "DIM",
|
|
32
|
-
"DEBUG": "NORMAL",
|
|
33
|
-
"INFO": "BOLD",
|
|
34
|
-
"SUCCESS": "BOLD",
|
|
35
|
-
"WARNING": "BOLD",
|
|
36
|
-
"ERROR": "BOLD",
|
|
37
|
-
"CRITICAL": "BOLD"
|
|
38
|
-
}
|
|
39
29
|
)
|
|
40
30
|
|
|
41
31
|
def get_installed_version() -> Optional[str]:
|
webscout/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "7.
|
|
1
|
+
__version__ = "7.3"
|
|
2
2
|
__prog__ = "webscout"
|
webscout/webscout_search.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
# import logging
|
|
4
|
+
import json
|
|
5
|
+
from urllib.parse import quote
|
|
4
6
|
import warnings
|
|
5
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
6
8
|
from datetime import datetime, timezone
|
|
@@ -11,7 +13,7 @@ from random import choice, shuffle
|
|
|
11
13
|
from threading import Event
|
|
12
14
|
from time import sleep, time
|
|
13
15
|
from types import TracebackType
|
|
14
|
-
from typing import cast
|
|
16
|
+
from typing import Any, cast
|
|
15
17
|
|
|
16
18
|
import primp # type: ignore
|
|
17
19
|
|
|
@@ -178,7 +180,8 @@ class WEBS:
|
|
|
178
180
|
"""
|
|
179
181
|
models_deprecated = {
|
|
180
182
|
"gpt-3.5": "gpt-4o-mini",
|
|
181
|
-
"llama-3-70b": "llama-3.
|
|
183
|
+
"llama-3.1-70b": "llama-3.3-70b",
|
|
184
|
+
"mixtral-8x7b": "mistral-24B"
|
|
182
185
|
}
|
|
183
186
|
if model in models_deprecated:
|
|
184
187
|
# logger.info(f"{model=} is deprecated, using {models_deprecated[model]}")
|
|
@@ -186,9 +189,9 @@ class WEBS:
|
|
|
186
189
|
models = {
|
|
187
190
|
"claude-3-haiku": "claude-3-haiku-20240307",
|
|
188
191
|
"gpt-4o-mini": "gpt-4o-mini",
|
|
189
|
-
"llama-3.
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
+
"llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
193
|
+
"o3-mini":"o3-mini",
|
|
194
|
+
"mistral-24B": "mistralai/Mistral-Small-24B-Instruct-2501"
|
|
192
195
|
}
|
|
193
196
|
# vqd
|
|
194
197
|
if not self._chat_vqd:
|
|
@@ -1139,4 +1142,82 @@ class WEBS:
|
|
|
1139
1142
|
except Exception as e:
|
|
1140
1143
|
raise e
|
|
1141
1144
|
|
|
1142
|
-
return results
|
|
1145
|
+
return results
|
|
1146
|
+
|
|
1147
|
+
def weather(
|
|
1148
|
+
self,
|
|
1149
|
+
location: str,
|
|
1150
|
+
language: str = "en",
|
|
1151
|
+
) -> dict[str, Any]:
|
|
1152
|
+
"""Get weather information for a location from DuckDuckGo.
|
|
1153
|
+
|
|
1154
|
+
Args:
|
|
1155
|
+
location: Location to get weather for.
|
|
1156
|
+
language: Language code (e.g. 'en', 'es'). Defaults to "en".
|
|
1157
|
+
|
|
1158
|
+
Returns:
|
|
1159
|
+
Dictionary containing weather data with structure described in docstring.
|
|
1160
|
+
|
|
1161
|
+
Raises:
|
|
1162
|
+
WebscoutE: Base exception for webscout errors.
|
|
1163
|
+
RatelimitE: Inherits from WebscoutE, raised for exceeding API request rate limits.
|
|
1164
|
+
TimeoutE: Inherits from WebscoutE, raised for API request timeouts.
|
|
1165
|
+
"""
|
|
1166
|
+
assert location, "location is mandatory"
|
|
1167
|
+
lang = language.split('-')[0]
|
|
1168
|
+
url = f"https://duckduckgo.com/js/spice/forecast/{quote(location)}/{lang}"
|
|
1169
|
+
|
|
1170
|
+
resp = self._get_url("GET", url)
|
|
1171
|
+
resp_text = resp.decode('utf-8')
|
|
1172
|
+
|
|
1173
|
+
if "ddg_spice_forecast(" not in resp_text:
|
|
1174
|
+
raise WebscoutE(f"No weather data found for {location}")
|
|
1175
|
+
|
|
1176
|
+
json_text = resp_text[resp_text.find('(') + 1:resp_text.rfind(')')]
|
|
1177
|
+
try:
|
|
1178
|
+
result = json.loads(json_text)
|
|
1179
|
+
except Exception as e:
|
|
1180
|
+
raise WebscoutE(f"Error parsing weather JSON: {e}")
|
|
1181
|
+
|
|
1182
|
+
if not result or 'currentWeather' not in result or 'forecastDaily' not in result:
|
|
1183
|
+
raise WebscoutE(f"Invalid weather data format for {location}")
|
|
1184
|
+
|
|
1185
|
+
formatted_data = {
|
|
1186
|
+
"location": result["currentWeather"]["metadata"].get("ddg-location", "Unknown"),
|
|
1187
|
+
"current": {
|
|
1188
|
+
"condition": result["currentWeather"].get("conditionCode"),
|
|
1189
|
+
"temperature_c": result["currentWeather"].get("temperature"),
|
|
1190
|
+
"feels_like_c": result["currentWeather"].get("temperatureApparent"),
|
|
1191
|
+
"humidity": result["currentWeather"].get("humidity"),
|
|
1192
|
+
"wind_speed_ms": result["currentWeather"].get("windSpeed"),
|
|
1193
|
+
"wind_direction": result["currentWeather"].get("windDirection"),
|
|
1194
|
+
"visibility_m": result["currentWeather"].get("visibility"),
|
|
1195
|
+
},
|
|
1196
|
+
"daily_forecast": [],
|
|
1197
|
+
"hourly_forecast": []
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
for day in result["forecastDaily"]["days"]:
|
|
1201
|
+
formatted_data["daily_forecast"].append({
|
|
1202
|
+
"date": datetime.fromisoformat(day["forecastStart"].replace("Z", "+00:00")).strftime("%Y-%m-%d"),
|
|
1203
|
+
"condition": day["daytimeForecast"].get("conditionCode"),
|
|
1204
|
+
"max_temp_c": day["temperatureMax"],
|
|
1205
|
+
"min_temp_c": day["temperatureMin"],
|
|
1206
|
+
"sunrise": datetime.fromisoformat(day["sunrise"].replace("Z", "+00:00")).strftime("%H:%M"),
|
|
1207
|
+
"sunset": datetime.fromisoformat(day["sunset"].replace("Z", "+00:00")).strftime("%H:%M"),
|
|
1208
|
+
})
|
|
1209
|
+
|
|
1210
|
+
if 'forecastHourly' in result and 'hours' in result['forecastHourly']:
|
|
1211
|
+
for hour in result['forecastHourly']['hours']:
|
|
1212
|
+
formatted_data["hourly_forecast"].append({
|
|
1213
|
+
"time": datetime.fromisoformat(hour["forecastStart"].replace("Z", "+00:00")).strftime("%H:%M"),
|
|
1214
|
+
"condition": hour.get("conditionCode"),
|
|
1215
|
+
"temperature_c": hour.get("temperature"),
|
|
1216
|
+
"feels_like_c": hour.get("temperatureApparent"),
|
|
1217
|
+
"humidity": hour.get("humidity"),
|
|
1218
|
+
"wind_speed_ms": hour.get("windSpeed"),
|
|
1219
|
+
"wind_direction": hour.get("windDirection"),
|
|
1220
|
+
"visibility_m": hour.get("visibility"),
|
|
1221
|
+
})
|
|
1222
|
+
|
|
1223
|
+
return formatted_data
|
|
@@ -633,4 +633,61 @@ class AsyncWEBS:
|
|
|
633
633
|
from_,
|
|
634
634
|
to,
|
|
635
635
|
)
|
|
636
|
-
return result
|
|
636
|
+
return result
|
|
637
|
+
|
|
638
|
+
async def aweather(
|
|
639
|
+
self,
|
|
640
|
+
location: str,
|
|
641
|
+
language: str = "en",
|
|
642
|
+
) -> dict[str, Any]:
|
|
643
|
+
"""Async version of weather information retrieval from DuckDuckGo.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
location: Location to get weather for.
|
|
647
|
+
language: Language code (e.g. 'en', 'es'). Defaults to "en".
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
Dictionary containing weather data with the following structure:
|
|
651
|
+
{
|
|
652
|
+
"location": str,
|
|
653
|
+
"current": {
|
|
654
|
+
"condition": str,
|
|
655
|
+
"temperature_c": float,
|
|
656
|
+
"feels_like_c": float,
|
|
657
|
+
"humidity": float,
|
|
658
|
+
"wind_speed_ms": float,
|
|
659
|
+
"wind_direction": float,
|
|
660
|
+
"visibility_m": float
|
|
661
|
+
},
|
|
662
|
+
"daily_forecast": List[{
|
|
663
|
+
"date": str,
|
|
664
|
+
"condition": str,
|
|
665
|
+
"max_temp_c": float,
|
|
666
|
+
"min_temp_c": float,
|
|
667
|
+
"sunrise": str,
|
|
668
|
+
"sunset": str
|
|
669
|
+
}],
|
|
670
|
+
"hourly_forecast": List[{
|
|
671
|
+
"time": str,
|
|
672
|
+
"condition": str,
|
|
673
|
+
"temperature_c": float,
|
|
674
|
+
"feels_like_c": float,
|
|
675
|
+
"humidity": float,
|
|
676
|
+
"wind_speed_ms": float,
|
|
677
|
+
"wind_direction": float,
|
|
678
|
+
"visibility_m": float
|
|
679
|
+
}]
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
Raises:
|
|
683
|
+
WebscoutE: Base exception for webscout errors.
|
|
684
|
+
RatelimitE: Inherits from WebscoutE, raised for exceeding API request rate limits.
|
|
685
|
+
TimeoutE: Inherits from WebscoutE, raised for API request timeouts.
|
|
686
|
+
"""
|
|
687
|
+
result = await self._loop.run_in_executor(
|
|
688
|
+
self._executor,
|
|
689
|
+
super().weather,
|
|
690
|
+
location,
|
|
691
|
+
language,
|
|
692
|
+
)
|
|
693
|
+
return result
|
webscout/yep_search.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import cloudscraper
|
|
2
|
+
from urllib.parse import urlencode
|
|
3
|
+
from webscout.litagent import LitAgent
|
|
4
|
+
from typing import List, Dict, Optional, Tuple
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
import json
|
|
7
|
+
class YepSearch:
|
|
8
|
+
"""Yep.com search class to get search results."""
|
|
9
|
+
|
|
10
|
+
_executor: ThreadPoolExecutor = ThreadPoolExecutor()
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
timeout: int = 20,
|
|
15
|
+
proxies: Dict[str, str] | None = None,
|
|
16
|
+
verify: bool = True,
|
|
17
|
+
):
|
|
18
|
+
"""Initialize YepSearch.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
timeout: Timeout value for the HTTP client. Defaults to 20.
|
|
22
|
+
proxies: Proxy configuration for requests. Defaults to None.
|
|
23
|
+
verify: Verify SSL certificates. Defaults to True.
|
|
24
|
+
"""
|
|
25
|
+
self.base_url = "https://api.yep.com/fs/2/search"
|
|
26
|
+
self.timeout = timeout
|
|
27
|
+
self.session = cloudscraper.create_scraper()
|
|
28
|
+
self.session.headers.update({
|
|
29
|
+
"Accept": "*/*",
|
|
30
|
+
"Accept-Language": "en-US,en;q=0.9,en-IN;q=0.8",
|
|
31
|
+
"DNT": "1",
|
|
32
|
+
"Origin": "https://yep.com",
|
|
33
|
+
"Referer": "https://yep.com/",
|
|
34
|
+
"Sec-Ch-Ua": '"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"',
|
|
35
|
+
"Sec-Ch-Ua-Mobile": "?0",
|
|
36
|
+
"Sec-Ch-Ua-Platform": '"Windows"',
|
|
37
|
+
"Sec-Fetch-Dest": "empty",
|
|
38
|
+
"Sec-Fetch-Mode": "cors",
|
|
39
|
+
"Sec-Fetch-Site": "same-site",
|
|
40
|
+
"User-Agent": LitAgent().random()
|
|
41
|
+
})
|
|
42
|
+
if proxies:
|
|
43
|
+
self.session.proxies.update(proxies)
|
|
44
|
+
self.session.verify = verify
|
|
45
|
+
|
|
46
|
+
def _remove_html_tags(self, text: str) -> str:
|
|
47
|
+
"""Remove HTML tags from text using simple string manipulation.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
text: String containing HTML tags
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Clean text without HTML tags
|
|
54
|
+
"""
|
|
55
|
+
result = ""
|
|
56
|
+
in_tag = False
|
|
57
|
+
|
|
58
|
+
for char in text:
|
|
59
|
+
if char == '<':
|
|
60
|
+
in_tag = True
|
|
61
|
+
elif char == '>':
|
|
62
|
+
in_tag = False
|
|
63
|
+
elif not in_tag:
|
|
64
|
+
result += char
|
|
65
|
+
|
|
66
|
+
# Replace common HTML entities
|
|
67
|
+
replacements = {
|
|
68
|
+
' ': ' ',
|
|
69
|
+
'&': '&',
|
|
70
|
+
'<': '<',
|
|
71
|
+
'>': '>',
|
|
72
|
+
'"': '"',
|
|
73
|
+
''': "'",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for entity, replacement in replacements.items():
|
|
77
|
+
result = result.replace(entity, replacement)
|
|
78
|
+
|
|
79
|
+
return result.strip()
|
|
80
|
+
|
|
81
|
+
def format_results(self, raw_results: dict) -> List[Dict]:
|
|
82
|
+
"""Format raw API results into a consistent structure."""
|
|
83
|
+
formatted_results = []
|
|
84
|
+
|
|
85
|
+
if not raw_results or len(raw_results) < 2:
|
|
86
|
+
return formatted_results
|
|
87
|
+
|
|
88
|
+
results = raw_results[1].get('results', [])
|
|
89
|
+
|
|
90
|
+
for result in results:
|
|
91
|
+
formatted_result = {
|
|
92
|
+
"title": self._remove_html_tags(result.get("title", "")),
|
|
93
|
+
"href": result.get("url", ""),
|
|
94
|
+
"body": self._remove_html_tags(result.get("snippet", "")),
|
|
95
|
+
"source": result.get("visual_url", ""),
|
|
96
|
+
"position": len(formatted_results) + 1,
|
|
97
|
+
"type": result.get("type", "organic"),
|
|
98
|
+
"first_seen": result.get("first_seen", None)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Add sitelinks if they exist
|
|
102
|
+
if "sitelinks" in result:
|
|
103
|
+
sitelinks = []
|
|
104
|
+
if "full" in result["sitelinks"]:
|
|
105
|
+
sitelinks.extend(result["sitelinks"]["full"])
|
|
106
|
+
if "short" in result["sitelinks"]:
|
|
107
|
+
sitelinks.extend(result["sitelinks"]["short"])
|
|
108
|
+
|
|
109
|
+
if sitelinks:
|
|
110
|
+
formatted_result["sitelinks"] = [
|
|
111
|
+
{
|
|
112
|
+
"title": self._remove_html_tags(link.get("title", "")),
|
|
113
|
+
"href": link.get("url", "")
|
|
114
|
+
}
|
|
115
|
+
for link in sitelinks
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
formatted_results.append(formatted_result)
|
|
119
|
+
|
|
120
|
+
return formatted_results
|
|
121
|
+
|
|
122
|
+
def text(
|
|
123
|
+
self,
|
|
124
|
+
keywords: str,
|
|
125
|
+
region: str = "all",
|
|
126
|
+
safesearch: str = "moderate",
|
|
127
|
+
max_results: Optional[int] = None,
|
|
128
|
+
) -> List[Dict[str, str]]:
|
|
129
|
+
"""Yep.com text search.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
keywords: Search query string.
|
|
133
|
+
region: Region for search results. Defaults to "all".
|
|
134
|
+
safesearch: SafeSearch setting ("on", "moderate", "off"). Defaults to "moderate".
|
|
135
|
+
max_results: Maximum number of results to return. Defaults to None.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of dictionaries containing search results.
|
|
139
|
+
"""
|
|
140
|
+
# Convert safesearch parameter
|
|
141
|
+
safe_search_map = {
|
|
142
|
+
"on": "on",
|
|
143
|
+
"moderate": "moderate",
|
|
144
|
+
"off": "off"
|
|
145
|
+
}
|
|
146
|
+
safe_setting = safe_search_map.get(safesearch.lower(), "moderate")
|
|
147
|
+
|
|
148
|
+
params = {
|
|
149
|
+
"client": "web",
|
|
150
|
+
"gl": region,
|
|
151
|
+
"limit": str(max_results) if max_results else "10",
|
|
152
|
+
"no_correct": "false",
|
|
153
|
+
"q": keywords,
|
|
154
|
+
"safeSearch": safe_setting,
|
|
155
|
+
"type": "web"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
url = f"{self.base_url}?{urlencode(params)}"
|
|
159
|
+
try:
|
|
160
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
161
|
+
response.raise_for_status()
|
|
162
|
+
raw_results = response.json()
|
|
163
|
+
|
|
164
|
+
formatted_results = self.format_results(raw_results)
|
|
165
|
+
|
|
166
|
+
if max_results:
|
|
167
|
+
return formatted_results[:max_results]
|
|
168
|
+
return formatted_results
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise Exception(f"Yep search failed: {str(e)}")
|
|
171
|
+
|
|
172
|
+
def images(
|
|
173
|
+
self,
|
|
174
|
+
keywords: str,
|
|
175
|
+
region: str = "all",
|
|
176
|
+
safesearch: str = "moderate",
|
|
177
|
+
max_results: Optional[int] = None,
|
|
178
|
+
) -> List[Dict[str, str]]:
|
|
179
|
+
"""Yep.com image search.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
keywords: Search query string.
|
|
183
|
+
region: Region for search results. Defaults to "all".
|
|
184
|
+
safesearch: SafeSearch setting ("on", "moderate", "off"). Defaults to "moderate".
|
|
185
|
+
max_results: Maximum number of results to return. Defaults to None.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of dictionaries containing image search results with keys:
|
|
189
|
+
- title: Image title
|
|
190
|
+
- image: Full resolution image URL
|
|
191
|
+
- thumbnail: Thumbnail image URL
|
|
192
|
+
- url: Source page URL
|
|
193
|
+
- height: Image height
|
|
194
|
+
- width: Image width
|
|
195
|
+
- source: Source website domain
|
|
196
|
+
"""
|
|
197
|
+
safe_search_map = {
|
|
198
|
+
"on": "on",
|
|
199
|
+
"moderate": "moderate",
|
|
200
|
+
"off": "off"
|
|
201
|
+
}
|
|
202
|
+
safe_setting = safe_search_map.get(safesearch.lower(), "moderate")
|
|
203
|
+
|
|
204
|
+
params = {
|
|
205
|
+
"client": "web",
|
|
206
|
+
"gl": region,
|
|
207
|
+
"limit": str(max_results) if max_results else "10",
|
|
208
|
+
"no_correct": "false",
|
|
209
|
+
"q": keywords,
|
|
210
|
+
"safeSearch": safe_setting,
|
|
211
|
+
"type": "images"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
url = f"{self.base_url}?{urlencode(params)}"
|
|
215
|
+
try:
|
|
216
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
217
|
+
response.raise_for_status()
|
|
218
|
+
raw_results = response.json()
|
|
219
|
+
|
|
220
|
+
if not raw_results or len(raw_results) < 2:
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
formatted_results = []
|
|
224
|
+
results = raw_results[1].get('results', [])
|
|
225
|
+
|
|
226
|
+
for result in results:
|
|
227
|
+
if result.get("type") != "Image":
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
formatted_result = {
|
|
231
|
+
"title": self._remove_html_tags(result.get("title", "")),
|
|
232
|
+
"image": result.get("image_id", ""),
|
|
233
|
+
"thumbnail": result.get("src", ""),
|
|
234
|
+
"url": result.get("host_page", ""),
|
|
235
|
+
"height": result.get("height", 0),
|
|
236
|
+
"width": result.get("width", 0),
|
|
237
|
+
"source": result.get("visual_url", "")
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Add high-res thumbnail if available
|
|
241
|
+
if "srcset" in result:
|
|
242
|
+
formatted_result["thumbnail_hd"] = result["srcset"].split(",")[1].strip().split(" ")[0]
|
|
243
|
+
|
|
244
|
+
formatted_results.append(formatted_result)
|
|
245
|
+
|
|
246
|
+
if max_results:
|
|
247
|
+
return formatted_results[:max_results]
|
|
248
|
+
return formatted_results
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
raise Exception(f"Yep image search failed: {str(e)}")
|
|
252
|
+
|
|
253
|
+
def suggestions(
|
|
254
|
+
self,
|
|
255
|
+
query: str,
|
|
256
|
+
region: str = "all",
|
|
257
|
+
) -> List[str]:
|
|
258
|
+
"""Get search suggestions from Yep.com autocomplete API.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
query: Search query string to get suggestions for.
|
|
262
|
+
region: Region for suggestions. Defaults to "all".
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List of suggestion strings.
|
|
266
|
+
|
|
267
|
+
Example:
|
|
268
|
+
>>> yep = YepSearch()
|
|
269
|
+
>>> suggestions = yep.suggestions("ca")
|
|
270
|
+
>>> print(suggestions)
|
|
271
|
+
['capital one', 'car wash', 'carmax', 'cafe', ...]
|
|
272
|
+
"""
|
|
273
|
+
params = {
|
|
274
|
+
"query": query,
|
|
275
|
+
"type": "web",
|
|
276
|
+
"gl": region
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
url = f"https://api.yep.com/ac/?{urlencode(params)}"
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
283
|
+
response.raise_for_status()
|
|
284
|
+
data = response.json()
|
|
285
|
+
# Return suggestions list if response format is valid
|
|
286
|
+
if isinstance(data, list) and len(data) > 1 and isinstance(data[1], list):
|
|
287
|
+
return data[1]
|
|
288
|
+
return []
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
raise Exception(f"Yep suggestions failed: {str(e)}")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
yep = YepSearch()
|
|
296
|
+
r = yep.suggestions("hi", region="all")
|
|
297
|
+
print(r)
|
webscout/zeroart/__init__.py
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ZeroArt: A zero-dependency ASCII art text generator
|
|
3
|
-
|
|
4
|
-
Create awesome ASCII art text without external dependencies!
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from .base import ZeroArtFont
|
|
8
|
-
from .fonts import BlockFont, SlantFont, NeonFont, CyberFont
|
|
9
|
-
from .effects import AsciiArtEffects
|
|
10
|
-
|
|
11
|
-
def figlet_format(text, font='block'):
|
|
12
|
-
"""
|
|
13
|
-
Generate ASCII art text
|
|
14
|
-
|
|
15
|
-
:param text: Text to convert
|
|
16
|
-
:param font: Font style (default: 'block')
|
|
17
|
-
:return: ASCII art representation of text
|
|
18
|
-
"""
|
|
19
|
-
font_map = {
|
|
20
|
-
'block': BlockFont(),
|
|
21
|
-
'slant': SlantFont(),
|
|
22
|
-
'neon': NeonFont(),
|
|
23
|
-
'cyber': CyberFont()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
selected_font = font_map.get(font.lower(), BlockFont())
|
|
27
|
-
return selected_font.render(text)
|
|
28
|
-
|
|
29
|
-
def print_figlet(text, font='block'):
|
|
30
|
-
"""
|
|
31
|
-
Print ASCII art text directly
|
|
32
|
-
|
|
33
|
-
:param text: Text to convert and print
|
|
34
|
-
:param font: Font style (default: 'block')
|
|
35
|
-
"""
|
|
36
|
-
print(figlet_format(text, font))
|
|
37
|
-
|
|
38
|
-
# Expose additional effects
|
|
39
|
-
rainbow = AsciiArtEffects.rainbow_effect
|
|
40
|
-
glitch = AsciiArtEffects.glitch_effect
|
|
41
|
-
wrap_text = AsciiArtEffects.wrap_text
|
|
42
|
-
outline = AsciiArtEffects.outline_effect
|
|
43
|
-
|
|
44
|
-
__all__ = [
|
|
45
|
-
'figlet_format',
|
|
46
|
-
'print_figlet',
|
|
47
|
-
'rainbow',
|
|
48
|
-
'glitch',
|
|
49
|
-
'wrap_text',
|
|
50
|
-
'outline',
|
|
51
|
-
'BlockFont',
|
|
52
|
-
'SlantFont',
|
|
53
|
-
'NeonFont',
|
|
54
|
-
'CyberFont'
|
|
1
|
+
"""
|
|
2
|
+
ZeroArt: A zero-dependency ASCII art text generator
|
|
3
|
+
|
|
4
|
+
Create awesome ASCII art text without external dependencies!
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import ZeroArtFont
|
|
8
|
+
from .fonts import BlockFont, SlantFont, NeonFont, CyberFont
|
|
9
|
+
from .effects import AsciiArtEffects
|
|
10
|
+
|
|
11
|
+
def figlet_format(text, font='block'):
|
|
12
|
+
"""
|
|
13
|
+
Generate ASCII art text
|
|
14
|
+
|
|
15
|
+
:param text: Text to convert
|
|
16
|
+
:param font: Font style (default: 'block')
|
|
17
|
+
:return: ASCII art representation of text
|
|
18
|
+
"""
|
|
19
|
+
font_map = {
|
|
20
|
+
'block': BlockFont(),
|
|
21
|
+
'slant': SlantFont(),
|
|
22
|
+
'neon': NeonFont(),
|
|
23
|
+
'cyber': CyberFont()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
selected_font = font_map.get(font.lower(), BlockFont())
|
|
27
|
+
return selected_font.render(text)
|
|
28
|
+
|
|
29
|
+
def print_figlet(text, font='block'):
|
|
30
|
+
"""
|
|
31
|
+
Print ASCII art text directly
|
|
32
|
+
|
|
33
|
+
:param text: Text to convert and print
|
|
34
|
+
:param font: Font style (default: 'block')
|
|
35
|
+
"""
|
|
36
|
+
print(figlet_format(text, font))
|
|
37
|
+
|
|
38
|
+
# Expose additional effects
|
|
39
|
+
rainbow = AsciiArtEffects.rainbow_effect
|
|
40
|
+
glitch = AsciiArtEffects.glitch_effect
|
|
41
|
+
wrap_text = AsciiArtEffects.wrap_text
|
|
42
|
+
outline = AsciiArtEffects.outline_effect
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
'figlet_format',
|
|
46
|
+
'print_figlet',
|
|
47
|
+
'rainbow',
|
|
48
|
+
'glitch',
|
|
49
|
+
'wrap_text',
|
|
50
|
+
'outline',
|
|
51
|
+
'BlockFont',
|
|
52
|
+
'SlantFont',
|
|
53
|
+
'NeonFont',
|
|
54
|
+
'CyberFont'
|
|
55
55
|
]
|