.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
autocomplete.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""This module implements functions needed for the autocompleter.
3
4"""
5# pylint: disable=use-dict-literal
6
7import json
8import html
9from urllib.parse import urlencode, quote_plus
10
11import lxml.etree
12import lxml.html
13from httpx import HTTPError
14
15from searx.extended_types import SXNG_Response
16from searx import settings
17from searx.engines import (
18 engines,
19 google,
20)
21from searx.network import get as http_get, post as http_post
22from searx.exceptions import SearxEngineResponseException
23from searx.utils import extr
24
25
26def update_kwargs(**kwargs):
27 if 'timeout' not in kwargs:
28 kwargs['timeout'] = settings['outgoing']['request_timeout']
29 kwargs['raise_for_httperror'] = True
30
31
32def get(*args, **kwargs) -> SXNG_Response:
33 update_kwargs(**kwargs)
34 return http_get(*args, **kwargs)
35
36
37def post(*args, **kwargs) -> SXNG_Response:
38 update_kwargs(**kwargs)
39 return http_post(*args, **kwargs)
40
41
42def baidu(query, _lang):
43 # baidu search autocompleter
44 base_url = "https://www.baidu.com/sugrec?"
45 response = get(base_url + urlencode({'ie': 'utf-8', 'json': 1, 'prod': 'pc', 'wd': query}))
46
47 results = []
48
49 if response.ok:
50 data = response.json()
51 if 'g' in data:
52 for item in data['g']:
53 results.append(item['q'])
54 return results
55
56
57def brave(query, _lang):
58 # brave search autocompleter
59 url = 'https://search.brave.com/api/suggest?'
60 url += urlencode({'q': query})
61 country = 'all'
62 # if lang in _brave:
63 # country = lang
64 kwargs = {'cookies': {'country': country}}
65 resp = get(url, **kwargs)
66
67 results = []
68
69 if resp.ok:
70 data = resp.json()
71 for item in data[1]:
72 results.append(item)
73 return results
74
75
76def dbpedia(query, _lang):
77 # dbpedia autocompleter, no HTTPS
78 autocomplete_url = 'https://lookup.dbpedia.org/api/search.asmx/KeywordSearch?'
79
80 response = get(autocomplete_url + urlencode(dict(QueryString=query)))
81
82 results = []
83
84 if response.ok:
85 dom = lxml.etree.fromstring(response.content)
86 results = dom.xpath('//Result/Label//text()')
87
88 return results
89
90
91def duckduckgo(query, sxng_locale):
92 """Autocomplete from DuckDuckGo. Supports DuckDuckGo's languages"""
93
94 traits = engines['duckduckgo'].traits
95 args = {
96 'q': query,
97 'kl': traits.get_region(sxng_locale, traits.all_locale),
98 }
99
100 url = 'https://duckduckgo.com/ac/?type=list&' + urlencode(args)
101 resp = get(url)
102
103 ret_val = []
104 if resp.ok:
105 j = resp.json()
106 if len(j) > 1:
107 ret_val = j[1]
108 return ret_val
109
110
111def google_complete(query, sxng_locale):
112 """Autocomplete from Google. Supports Google's languages and subdomains
113 (:py:obj:`searx.engines.google.get_google_info`) by using the async REST
114 API::
115
116 https://{subdomain}/complete/search?{args}
117
118 """
119
120 google_info = google.get_google_info({'searxng_locale': sxng_locale}, engines['google'].traits)
121
122 url = 'https://{subdomain}/complete/search?{args}'
123 args = urlencode(
124 {
125 'q': query,
126 'client': 'gws-wiz',
127 'hl': google_info['params']['hl'],
128 }
129 )
130 results = []
131 resp = get(url.format(subdomain=google_info['subdomain'], args=args))
132 if resp and resp.ok:
133 json_txt = resp.text[resp.text.find('[') : resp.text.find(']', -3) + 1]
134 data = json.loads(json_txt)
135 for item in data[0]:
136 results.append(lxml.html.fromstring(item[0]).text_content())
137 return results
138
139
140def mwmbl(query, _lang):
141 """Autocomplete from Mwmbl_."""
142
143 # mwmbl autocompleter
144 url = 'https://api.mwmbl.org/search/complete?{query}'
145
146 results = get(url.format(query=urlencode({'q': query}))).json()[1]
147
148 # results starting with `go:` are direct urls and not useful for auto completion
149 return [result for result in results if not result.startswith("go: ") and not result.startswith("search: ")]
150
151
152def naver(query, _lang):
153 # Naver search autocompleter
154 url = f"https://ac.search.naver.com/nx/ac?{urlencode({'q': query, 'r_format': 'json', 'st': 0})}"
155 response = get(url)
156
157 results = []
158
159 if response.ok:
160 data = response.json()
161 if data.get('items'):
162 for item in data['items'][0]:
163 results.append(item[0])
164 return results
165
166
167def qihu360search(query, _lang):
168 # 360Search search autocompleter
169 url = f"https://sug.so.360.cn/suggest?{urlencode({'format': 'json', 'word': query})}"
170 response = get(url)
171
172 results = []
173
174 if response.ok:
175 data = response.json()
176 if 'result' in data:
177 for item in data['result']:
178 results.append(item['word'])
179 return results
180
181
182def quark(query, _lang):
183 # Quark search autocompleter
184 url = f"https://sugs.m.sm.cn/web?{urlencode({'q': query})}"
185 response = get(url)
186
187 results = []
188
189 if response.ok:
190 data = response.json()
191 for item in data.get('r', []):
192 results.append(item['w'])
193 return results
194
195
196def seznam(query, _lang):
197 # seznam search autocompleter
198 url = 'https://suggest.seznam.cz/fulltext/cs?{query}'
199
200 resp = get(
201 url.format(
202 query=urlencode(
203 {'phrase': query, 'cursorPosition': len(query), 'format': 'json-2', 'highlight': '1', 'count': '6'}
204 )
205 )
206 )
207
208 if not resp.ok:
209 return []
210
211 data = resp.json()
212 return [
213 ''.join([part.get('text', '') for part in item.get('text', [])])
214 for item in data.get('result', [])
215 if item.get('itemType', None) == 'ItemType.TEXT'
216 ]
217
218
219def sogou(query, _lang):
220 # Sogou search autocompleter
221 base_url = "https://sor.html5.qq.com/api/getsug?"
222 response = get(base_url + urlencode({'m': 'searxng', 'key': query}))
223
224 if response.ok:
225 raw_json = extr(response.text, "[", "]", default="")
226
227 try:
228 data = json.loads(f"[{raw_json}]]")
229 return data[1]
230 except json.JSONDecodeError:
231 return []
232
233 return []
234
235
236def stract(query, _lang):
237 # stract autocompleter (beta)
238 url = f"https://stract.com/beta/api/autosuggest?q={quote_plus(query)}"
239
240 resp = post(url)
241
242 if not resp.ok:
243 return []
244
245 return [html.unescape(suggestion['raw']) for suggestion in resp.json()]
246
247
248def swisscows(query, _lang):
249 # swisscows autocompleter
250 url = 'https://swisscows.ch/api/suggest?{query}&itemsCount=5'
251
252 resp = json.loads(get(url.format(query=urlencode({'query': query}))).text)
253 return resp
254
255
256def qwant(query, sxng_locale):
257 """Autocomplete from Qwant. Supports Qwant's regions."""
258 results = []
259
260 locale = engines['qwant'].traits.get_region(sxng_locale, 'en_US')
261 url = 'https://api.qwant.com/v3/suggest?{query}'
262 resp = get(url.format(query=urlencode({'q': query, 'locale': locale, 'version': '2'})))
263
264 if resp.ok:
265 data = resp.json()
266 if data['status'] == 'success':
267 for item in data['data']['items']:
268 results.append(item['value'])
269
270 return results
271
272
273def wikipedia(query, sxng_locale):
274 """Autocomplete from Wikipedia. Supports Wikipedia's languages (aka netloc)."""
275 results = []
276 eng_traits = engines['wikipedia'].traits
277 wiki_lang = eng_traits.get_language(sxng_locale, 'en')
278 wiki_netloc = eng_traits.custom['wiki_netloc'].get(wiki_lang, 'en.wikipedia.org') # type: ignore
279
280 url = 'https://{wiki_netloc}/w/api.php?{args}'
281 args = urlencode(
282 {
283 'action': 'opensearch',
284 'format': 'json',
285 'formatversion': '2',
286 'search': query,
287 'namespace': '0',
288 'limit': '10',
289 }
290 )
291 resp = get(url.format(args=args, wiki_netloc=wiki_netloc))
292 if resp.ok:
293 data = resp.json()
294 if len(data) > 1:
295 results = data[1]
296
297 return results
298
299
300def yandex(query, _lang):
301 # yandex autocompleter
302 url = "https://suggest.yandex.com/suggest-ff.cgi?{0}"
303
304 resp = json.loads(get(url.format(urlencode(dict(part=query)))).text)
305 if len(resp) > 1:
306 return resp[1]
307 return []
308
309
310backends = {
311 '360search': qihu360search,
312 'baidu': baidu,
313 'brave': brave,
314 'dbpedia': dbpedia,
315 'duckduckgo': duckduckgo,
316 'google': google_complete,
317 'mwmbl': mwmbl,
318 'naver': naver,
319 'quark': quark,
320 'qwant': qwant,
321 'seznam': seznam,
322 'sogou': sogou,
323 'stract': stract,
324 'swisscows': swisscows,
325 'wikipedia': wikipedia,
326 'yandex': yandex,
327}
328
329
330def search_autocomplete(backend_name, query, sxng_locale):
331 backend = backends.get(backend_name)
332 if backend is None:
333 return []
334 try:
335 return backend(query, sxng_locale)
336 except (HTTPError, SearxEngineResponseException):
337 return []
search_autocomplete(backend_name, query, sxng_locale)
naver(query, _lang)
qwant(query, sxng_locale)
seznam(query, _lang)
sogou(query, _lang)
yandex(query, _lang)
wikipedia(query, sxng_locale)
quark(query, _lang)
google_complete(query, sxng_locale)
dbpedia(query, _lang)
stract(query, _lang)
SXNG_Response get(*args, **kwargs)
brave(query, _lang)
qihu360search(query, _lang)
update_kwargs(**kwargs)
swisscows(query, _lang)
duckduckgo(query, sxng_locale)
mwmbl(query, _lang)
baidu(query, _lang)
SXNG_Response post(*args, **kwargs)
::1337x
Definition 1337x.py:1