webscout 7.4__py3-none-any.whl → 7.6__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.

Files changed (137) hide show
  1. webscout/AIauto.py +5 -53
  2. webscout/AIutel.py +8 -318
  3. webscout/DWEBS.py +460 -489
  4. webscout/Extra/YTToolkit/YTdownloader.py +14 -53
  5. webscout/Extra/YTToolkit/transcriber.py +12 -13
  6. webscout/Extra/YTToolkit/ytapi/video.py +0 -1
  7. webscout/Extra/__init__.py +0 -1
  8. webscout/Extra/autocoder/autocoder_utiles.py +0 -4
  9. webscout/Extra/autocoder/rawdog.py +13 -41
  10. webscout/Extra/gguf.py +652 -428
  11. webscout/Extra/weather.py +178 -156
  12. webscout/Extra/weather_ascii.py +70 -17
  13. webscout/Litlogger/core/logger.py +1 -2
  14. webscout/Litlogger/handlers/file.py +1 -1
  15. webscout/Litlogger/styles/formats.py +0 -2
  16. webscout/Litlogger/utils/detectors.py +0 -1
  17. webscout/Provider/AISEARCH/DeepFind.py +0 -1
  18. webscout/Provider/AISEARCH/ISou.py +1 -1
  19. webscout/Provider/AISEARCH/felo_search.py +0 -1
  20. webscout/Provider/AllenAI.py +24 -9
  21. webscout/Provider/C4ai.py +432 -0
  22. webscout/Provider/ChatGPTGratis.py +24 -56
  23. webscout/Provider/Cloudflare.py +18 -21
  24. webscout/Provider/DeepSeek.py +27 -48
  25. webscout/Provider/Deepinfra.py +129 -53
  26. webscout/Provider/Gemini.py +1 -1
  27. webscout/Provider/GithubChat.py +362 -0
  28. webscout/Provider/Glider.py +25 -8
  29. webscout/Provider/HF_space/qwen_qwen2.py +2 -2
  30. webscout/Provider/HeckAI.py +38 -5
  31. webscout/Provider/HuggingFaceChat.py +462 -0
  32. webscout/Provider/Jadve.py +20 -5
  33. webscout/Provider/Marcus.py +7 -50
  34. webscout/Provider/Netwrck.py +43 -67
  35. webscout/Provider/PI.py +4 -2
  36. webscout/Provider/Perplexitylabs.py +26 -6
  37. webscout/Provider/Phind.py +29 -3
  38. webscout/Provider/PizzaGPT.py +10 -51
  39. webscout/Provider/TTI/AiForce/async_aiforce.py +4 -37
  40. webscout/Provider/TTI/AiForce/sync_aiforce.py +41 -38
  41. webscout/Provider/TTI/FreeAIPlayground/__init__.py +9 -9
  42. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +206 -206
  43. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +192 -192
  44. webscout/Provider/TTI/MagicStudio/__init__.py +2 -0
  45. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +111 -0
  46. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +109 -0
  47. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +5 -24
  48. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +2 -22
  49. webscout/Provider/TTI/__init__.py +2 -3
  50. webscout/Provider/TTI/aiarta/__init__.py +2 -0
  51. webscout/Provider/TTI/aiarta/async_aiarta.py +482 -0
  52. webscout/Provider/TTI/aiarta/sync_aiarta.py +440 -0
  53. webscout/Provider/TTI/fastflux/__init__.py +22 -0
  54. webscout/Provider/TTI/fastflux/async_fastflux.py +257 -0
  55. webscout/Provider/TTI/fastflux/sync_fastflux.py +247 -0
  56. webscout/Provider/TTS/__init__.py +2 -2
  57. webscout/Provider/TTS/deepgram.py +12 -39
  58. webscout/Provider/TTS/elevenlabs.py +14 -40
  59. webscout/Provider/TTS/gesserit.py +11 -35
  60. webscout/Provider/TTS/murfai.py +13 -39
  61. webscout/Provider/TTS/parler.py +17 -40
  62. webscout/Provider/TTS/speechma.py +180 -0
  63. webscout/Provider/TTS/streamElements.py +17 -44
  64. webscout/Provider/TextPollinationsAI.py +39 -59
  65. webscout/Provider/Venice.py +217 -200
  66. webscout/Provider/WiseCat.py +27 -5
  67. webscout/Provider/Youchat.py +63 -36
  68. webscout/Provider/__init__.py +13 -8
  69. webscout/Provider/akashgpt.py +28 -10
  70. webscout/Provider/copilot.py +416 -0
  71. webscout/Provider/flowith.py +196 -0
  72. webscout/Provider/freeaichat.py +32 -45
  73. webscout/Provider/granite.py +17 -53
  74. webscout/Provider/koala.py +20 -5
  75. webscout/Provider/llamatutor.py +7 -47
  76. webscout/Provider/llmchat.py +36 -53
  77. webscout/Provider/multichat.py +92 -98
  78. webscout/Provider/talkai.py +1 -0
  79. webscout/Provider/turboseek.py +3 -0
  80. webscout/Provider/tutorai.py +2 -0
  81. webscout/Provider/typegpt.py +154 -64
  82. webscout/Provider/x0gpt.py +3 -1
  83. webscout/Provider/yep.py +102 -20
  84. webscout/__init__.py +3 -0
  85. webscout/cli.py +4 -40
  86. webscout/conversation.py +1 -10
  87. webscout/exceptions.py +19 -9
  88. webscout/litagent/__init__.py +2 -2
  89. webscout/litagent/agent.py +351 -20
  90. webscout/litagent/constants.py +34 -5
  91. webscout/litprinter/__init__.py +0 -3
  92. webscout/models.py +181 -0
  93. webscout/optimizers.py +1 -1
  94. webscout/prompt_manager.py +2 -8
  95. webscout/scout/core/scout.py +1 -4
  96. webscout/scout/core/search_result.py +1 -1
  97. webscout/scout/core/text_utils.py +1 -1
  98. webscout/scout/core.py +2 -5
  99. webscout/scout/element.py +1 -1
  100. webscout/scout/parsers/html_parser.py +1 -1
  101. webscout/scout/utils.py +0 -1
  102. webscout/swiftcli/__init__.py +1 -3
  103. webscout/tempid.py +1 -1
  104. webscout/update_checker.py +55 -95
  105. webscout/version.py +1 -1
  106. webscout/webscout_search_async.py +1 -2
  107. webscout/yep_search.py +297 -297
  108. webscout-7.6.dist-info/LICENSE.md +146 -0
  109. {webscout-7.4.dist-info → webscout-7.6.dist-info}/METADATA +104 -514
  110. {webscout-7.4.dist-info → webscout-7.6.dist-info}/RECORD +113 -120
  111. webscout/Extra/autollama.py +0 -231
  112. webscout/Local/__init__.py +0 -10
  113. webscout/Local/_version.py +0 -3
  114. webscout/Local/formats.py +0 -747
  115. webscout/Local/model.py +0 -1368
  116. webscout/Local/samplers.py +0 -125
  117. webscout/Local/thread.py +0 -539
  118. webscout/Local/ui.py +0 -401
  119. webscout/Local/utils.py +0 -388
  120. webscout/Provider/Amigo.py +0 -274
  121. webscout/Provider/Bing.py +0 -243
  122. webscout/Provider/DiscordRocks.py +0 -253
  123. webscout/Provider/TTI/blackbox/__init__.py +0 -4
  124. webscout/Provider/TTI/blackbox/async_blackbox.py +0 -212
  125. webscout/Provider/TTI/blackbox/sync_blackbox.py +0 -199
  126. webscout/Provider/TTI/deepinfra/__init__.py +0 -4
  127. webscout/Provider/TTI/deepinfra/async_deepinfra.py +0 -227
  128. webscout/Provider/TTI/deepinfra/sync_deepinfra.py +0 -199
  129. webscout/Provider/TTI/imgninza/__init__.py +0 -4
  130. webscout/Provider/TTI/imgninza/async_ninza.py +0 -214
  131. webscout/Provider/TTI/imgninza/sync_ninza.py +0 -209
  132. webscout/Provider/TTS/voicepod.py +0 -117
  133. webscout/Provider/dgaf.py +0 -214
  134. webscout-7.4.dist-info/LICENSE.md +0 -211
  135. {webscout-7.4.dist-info → webscout-7.6.dist-info}/WHEEL +0 -0
  136. {webscout-7.4.dist-info → webscout-7.6.dist-info}/entry_points.txt +0 -0
  137. {webscout-7.4.dist-info → webscout-7.6.dist-info}/top_level.txt +0 -0
webscout/yep_search.py CHANGED
@@ -1,297 +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
- '&nbsp;': ' ',
69
- '&amp;': '&',
70
- '&lt;': '<',
71
- '&gt;': '>',
72
- '&quot;': '"',
73
- '&apos;': "'",
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)
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
+ '&nbsp;': ' ',
69
+ '&amp;': '&',
70
+ '&lt;': '<',
71
+ '&gt;': '>',
72
+ '&quot;': '"',
73
+ '&apos;': "'",
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)