on master help sign in
Merge branch 'master' into feature/accessibility
commita51b2b6c20c1346748c09aec051d78f6822c580c authorMathieu Brunot <mathieu.brunot@monogramm.io> dateWed 16 Oct 2019, 19:30 (7 years ago) parent4d17d4512f42d1
35 files changed+378−478
Dockerfile+3−1 dockerfiles/docker-entrypoint.sh+4 searx/engines/__init__.py+15−12 searx/engines/arxiv.py+1 searx/engines/deviantart.py+22−25 searx/engines/digg.py+12−24 searx/engines/duckduckgo.py+26−9 searx/engines/gigablast.py+4−3 searx/engines/soundcloud.py+1−1 searx/engines/startpage.py+18−8 searx/engines/wolframalpha_noapi.py+1−1 searx/engines/www1x.py+6−29 searx/exceptions.py+1 searx/plugins/https_rewrite.py+3 searx/plugins/oa_doi_rewrite.py+3 searx/plugins/tracker_url_remover.py+19−10 searx/query.py+1−1 searx/results.py+45−29 searx/search.py+61−13 searx/settings.yml+9−8 searx/static/themes/courgette/css/style.css+1−1 searx/static/themes/courgette/less/style.less+4 searx/static/themes/legacy/css/style.css+1−1 searx/static/themes/legacy/less/style.less+4 searx/templates/courgette/result_templates/key-value.html+13 searx/templates/legacy/result_templates/key-value.html+13 searx/templates/oscar/macros.html+9−1 searx/templates/oscar/result_templates/key-value.html+19 searx/templates/simple/result_templates/key-value.html+11 searx/utils.py+18−2 searx/webapp.py+5−2 tests/unit/engines/test_deviantart.py−71 tests/unit/engines/test_digg.py−85 tests/unit/engines/test_startpage.py+25−98 tests/unit/engines/test_www1x.py−43
Dockerfile+3−1
@@ -11,7 +11,9 @@ ARG TIMESTAMP_UWSGI=0
1111ARG LABEL_VCS_REF=
1212ARG LABEL_VCS_URL=
1313
14−ENV BASE_URL= \
14+ENV INSTANCE_NAME=searx \
15+ AUTOCOMPLETE= \
16+ BASE_URL= \
1517 MORTY_KEY= \
1618 MORTY_URL=
1719EXPOSE 8080
dockerfiles/docker-entrypoint.sh+4
@@ -29,6 +29,8 @@ do
2929 printf " -f Always update on the configuration files (existing files are renamed with the .old suffix)\n"
3030 printf " Without this option, new configuration files are copied with the .new suffix\n"
3131 printf "\nEnvironment variables:\n\n"
32+ printf " INSTANCE_NAME settings.yml : general.instance_name\n"
33+ printf " AUTOCOMPLETE settings.yml : search.autocomplete\n"
3234 printf " BASE_URL settings.yml : server.base_url\n"
3335 printf " MORTY_URL settings.yml : result_proxy.url\n"
3436 printf " MORTY_KEY settings.yml : result_proxy.key\n"
@@ -53,6 +55,8 @@ patch_searx_settings() {
5355
5456 # update settings.yml
5557 sed -i -e "s|base_url : False|base_url : ${BASE_URL}|g" \
58+ -e "s/instance_name : \"searx\"/instance_name : \"${INSTANCE_NAME}\"/g" \
59+ -e "s/autocomplete : \"\"/autocomplete : \"${AUTOCOMPLETE}\"/g" \
5660 -e "s/ultrasecretkey/$(openssl rand -hex 32)/g" \
5761 "${CONF}"
5862
searx/engines/__init__.py+15−12
@@ -27,7 +27,7 @@ from json import loads
2727from requests import get
2828from searx import settings
2929from searx import logger
30−from searx.utils import load_module, match_language
30+from searx.utils import load_module, match_language, get_engine_from_settings
3131
3232
3333logger = logger.getChild('engines')
@@ -53,7 +53,8 @@ engine_default_args = {'paging': False,
5353 'disabled': False,
5454 'suspend_end_time': 0,
5555 'continuous_errors': 0,
56− 'time_range_support': False}
56+ 'time_range_support': False,
57+ 'offline': False}
5758
5859
5960def load_engine(engine_data):
@@ -128,14 +129,16 @@ def load_engine(engine_data):
128129 engine.stats = {
129130 'result_count': 0,
130131 'search_count': 0,
131− 'page_load_time': 0,
132− 'page_load_count': 0,
133132 'engine_time': 0,
134133 'engine_time_count': 0,
135134 'score_count': 0,
136135 'errors': 0
137136 }
138137
138+ if not engine.offline:
139+ engine.stats['page_load_time'] = 0
140+ engine.stats['page_load_count'] = 0
141+
139142 for category_name in engine.categories:
140143 categories.setdefault(category_name, []).append(engine)
141144
@@ -173,11 +176,6 @@ def get_engines_stats():
173176 results_num = \
174177 engine.stats['result_count'] / float(engine.stats['search_count'])
175178
176− if engine.stats['page_load_count'] != 0:
177− load_times = engine.stats['page_load_time'] / float(engine.stats['page_load_count']) # noqa
178− else:
179− load_times = 0
180−
181179 if engine.stats['engine_time_count'] != 0:
182180 this_engine_time = engine.stats['engine_time'] / float(engine.stats['engine_time_count']) # noqa
183181 else:
@@ -189,14 +187,19 @@ def get_engines_stats():
189187 else:
190188 score = score_per_result = 0.0
191189
192− max_pageload = max(load_times, max_pageload)
190+ if not engine.offline:
191+ load_times = 0
192+ if engine.stats['page_load_count'] != 0:
193+ load_times = engine.stats['page_load_time'] / float(engine.stats['page_load_count']) # noqa
194+ max_pageload = max(load_times, max_pageload)
195+ pageloads.append({'avg': load_times, 'name': engine.name})
196+
193197 max_engine_times = max(this_engine_time, max_engine_times)
194198 max_results = max(results_num, max_results)
195199 max_score = max(score, max_score)
196200 max_score_per_result = max(score_per_result, max_score_per_result)
197201 max_errors = max(max_errors, engine.stats['errors'])
198202
199− pageloads.append({'avg': load_times, 'name': engine.name})
200203 engine_times.append({'avg': this_engine_time, 'name': engine.name})
201204 results.append({'avg': results_num, 'name': engine.name})
202205 scores.append({'avg': score, 'name': engine.name})
@@ -255,7 +258,7 @@ def initialize_engines(engine_list):
255258 load_engines(engine_list)
256259
257260 def engine_init(engine_name, init_fn):
258− init_fn()
261+ init_fn(get_engine_from_settings(engine_name))
259262 logger.debug('%s engine: Initialized', engine_name)
260263
261264 for engine_name, engine in engines.items():
searx/engines/arxiv.py+1
@@ -17,6 +17,7 @@ from searx.url_utils import urlencode
1717
1818
1919categories = ['science']
20+paging = True
2021
2122base_url = 'http://export.arxiv.org/api/query?search_query=all:'\
2223 + '{query}&start={offset}&max_results={number_of_results}'
searx/engines/deviantart.py+22−25
@@ -24,7 +24,7 @@ time_range_support = True
2424
2525# search-url
2626base_url = 'https://www.deviantart.com/'
27−search_url = base_url + 'browse/all/?offset={offset}&{query}'
27+search_url = base_url + 'search?page={page}&{query}'
2828time_range_url = '&order={range}'
2929
3030time_range_dict = {'day': 11,
@@ -37,9 +37,7 @@ def request(query, params):
3737 if params['time_range'] and params['time_range'] not in time_range_dict:
3838 return params
3939
40− offset = (params['pageno'] - 1) * 24
41−
42− params['url'] = search_url.format(offset=offset,
40+ params['url'] = search_url.format(page=params['pageno'],
4341 query=urlencode({'q': query}))
4442 if params['time_range'] in time_range_dict:
4543 params['url'] += time_range_url.format(range=time_range_dict[params['time_range']])
@@ -57,28 +55,27 @@ def response(resp):
5755
5856 dom = html.fromstring(resp.text)
5957
60− regex = re.compile(r'\/200H\/')
61−
6258 # parse results
63− for result in dom.xpath('.//span[@class="thumb wide"]'):
64− link = result.xpath('.//a[@class="torpedo-thumb-link"]')[0]
65− url = link.attrib.get('href')
66− title = extract_text(result.xpath('.//span[@class="title"]'))
67− thumbnail_src = link.xpath('.//img')[0].attrib.get('src')
68− img_src = regex.sub('/', thumbnail_src)
69−
70− # http to https, remove domain sharding
71− thumbnail_src = re.sub(r"https?://(th|fc)\d+.", "https://th01.", thumbnail_src)
72− thumbnail_src = re.sub(r"http://", "https://", thumbnail_src)
73−
74− url = re.sub(r"http://(.*)\.deviantart\.com/", "https://\\1.deviantart.com/", url)
75−
76− # append result
77− results.append({'url': url,
78− 'title': title,
79− 'img_src': img_src,
80− 'thumbnail_src': thumbnail_src,
81− 'template': 'images.html'})
59+ for row in dom.xpath('//div[contains(@data-hook, "content_row")]'):
60+ for result in row.xpath('./div'):
61+ link = result.xpath('.//a[@data-hook="deviation_link"]')[0]
62+ url = link.attrib.get('href')
63+ title = link.attrib.get('title')
64+ thumbnail_src = result.xpath('.//img')[0].attrib.get('src')
65+ img_src = thumbnail_src
66+
67+ # http to https, remove domain sharding
68+ thumbnail_src = re.sub(r"https?://(th|fc)\d+.", "https://th01.", thumbnail_src)
69+ thumbnail_src = re.sub(r"http://", "https://", thumbnail_src)
70+
71+ url = re.sub(r"http://(.*)\.deviantart\.com/", "https://\\1.deviantart.com/", url)
72+
73+ # append result
74+ results.append({'url': url,
75+ 'title': title,
76+ 'img_src': img_src,
77+ 'thumbnail_src': thumbnail_src,
78+ 'template': 'images.html'})
8279
8380 # return results
8481 return results
searx/engines/digg.py+12−24
@@ -15,7 +15,8 @@ import string
1515from dateutil import parser
1616from json import loads
1717from lxml import html
18−from searx.url_utils import quote_plus
18+from searx.url_utils import urlencode
19+from datetime import datetime
1920
2021# engine dependent config
2122categories = ['news', 'social media']
@@ -23,7 +24,7 @@ paging = True
2324
2425# search-url
2526base_url = 'https://digg.com/'
26−search_url = base_url + 'api/search/{query}.json?position={position}&format=html'
27+search_url = base_url + 'api/search/?{query}&from={position}&size=20&format=html'
2728
2829# specific xpath variables
2930results_xpath = '//article'
@@ -38,9 +39,9 @@ digg_cookie_chars = string.ascii_uppercase + string.ascii_lowercase +\
3839
3940# do search-request
4041def request(query, params):
41− offset = (params['pageno'] - 1) * 10
42+ offset = (params['pageno'] - 1) * 20
4243 params['url'] = search_url.format(position=offset,
43− query=quote_plus(query))
44+ query=urlencode({'q': query}))
4445 params['cookies']['frontend.auid'] = ''.join(random.choice(
4546 digg_cookie_chars) for _ in range(22))
4647 return params
@@ -52,30 +53,17 @@ def response(resp):
5253
5354 search_result = loads(resp.text)
5455
55− if 'html' not in search_result or search_result['html'] == '':
56− return results
57−
58− dom = html.fromstring(search_result['html'])
59−
6056 # parse results
61− for result in dom.xpath(results_xpath):
62− url = result.attrib.get('data-contenturl')
63− thumbnail = result.xpath('.//img')[0].attrib.get('src')
64− title = ''.join(result.xpath(title_xpath))
65− content = ''.join(result.xpath(content_xpath))
66− pubdate = result.xpath(pubdate_xpath)[0].attrib.get('datetime')
67− publishedDate = parser.parse(pubdate)
68−
69− # http to https
70− thumbnail = thumbnail.replace("http://static.digg.com", "https://static.digg.com")
57+ for result in search_result['mapped']:
7158
59+ published = datetime.strptime(result['created']['ISO'], "%Y-%m-%d %H:%M:%S")
7260 # append result
73− results.append({'url': url,
74− 'title': title,
75− 'content': content,
61+ results.append({'url': result['url'],
62+ 'title': result['title'],
63+ 'content': result['excerpt'],
7664 'template': 'videos.html',
77− 'publishedDate': publishedDate,
78− 'thumbnail': thumbnail})
65+ 'publishedDate': published,
66+ 'thumbnail': result['images']['thumbImage']})
7967
8068 # return results
8169 return results
searx/engines/duckduckgo.py+26−9
@@ -65,21 +65,36 @@ def get_region_code(lang, lang_list=[]):
6565
6666
6767def request(query, params):
68− if params['time_range'] and params['time_range'] not in time_range_dict:
68+ if params['time_range'] not in (None, 'None', '') and params['time_range'] not in time_range_dict:
6969 return params
7070
7171 offset = (params['pageno'] - 1) * 30
7272
7373 region_code = get_region_code(params['language'], supported_languages)
74− if region_code:
75− params['url'] = url.format(
76− query=urlencode({'q': query, 'kl': region_code}), offset=offset, dc_param=offset)
74+ params['url'] = 'https://duckduckgo.com/html/'
75+ if params['pageno'] > 1:
76+ params['method'] = 'POST'
77+ params['data']['q'] = query
78+ params['data']['s'] = offset
79+ params['data']['dc'] = 30
80+ params['data']['nextParams'] = ''
81+ params['data']['v'] = 'l'
82+ params['data']['o'] = 'json'
83+ params['data']['api'] = '/d.js'
84+ if params['time_range'] in time_range_dict:
85+ params['data']['df'] = time_range_dict[params['time_range']]
86+ if region_code:
87+ params['data']['kl'] = region_code
7788 else:
78− params['url'] = url.format(
79− query=urlencode({'q': query}), offset=offset, dc_param=offset)
89+ if region_code:
90+ params['url'] = url.format(
91+ query=urlencode({'q': query, 'kl': region_code}), offset=offset, dc_param=offset)
92+ else:
93+ params['url'] = url.format(
94+ query=urlencode({'q': query}), offset=offset, dc_param=offset)
8095
81− if params['time_range'] in time_range_dict:
82− params['url'] += time_range_url.format(range=time_range_dict[params['time_range']])
96+ if params['time_range'] in time_range_dict:
97+ params['url'] += time_range_url.format(range=time_range_dict[params['time_range']])
8398
8499 return params
85100
@@ -91,7 +106,9 @@ def response(resp):
91106 doc = fromstring(resp.text)
92107
93108 # parse results
94− for r in doc.xpath(result_xpath):
109+ for i, r in enumerate(doc.xpath(result_xpath)):
110+ if i >= 30:
111+ break
95112 try:
96113 res_url = r.xpath(url_xpath)[-1]
97114 except:
searx/engines/gigablast.py+4−3
@@ -35,8 +35,8 @@ search_string = 'search?{query}'\
3535 '&ff={safesearch}'\
3636 '&rxiec={rxieu}'\
3737 '&ulse={ulse}'\
38− '&rand={rxikd}' # current unix timestamp
39−
38+ '&rand={rxikd}'\
39+ '&dbez={dbez}'
4040# specific xpath variables
4141results_xpath = '//response//result'
4242url_xpath = './/url'
@@ -70,7 +70,8 @@ def request(query, params):
7070 rxieu=random.randint(1000000000, 9999999999),
7171 ulse=random.randint(100000000, 999999999),
7272 lang=language,
73− safesearch=safesearch)
73+ safesearch=safesearch,
74+ dbez=random.randint(100000000, 999999999))
7475
7576 params['url'] = base_url + search_path
7677
searx/engines/soundcloud.py+1−1
@@ -66,7 +66,7 @@ def get_client_id():
6666 return ""
6767
6868
69−def init():
69+def init(engine_settings=None):
7070 global guest_client_id
7171 # api-key
7272 guest_client_id = get_client_id()
searx/engines/startpage.py+18−8
@@ -15,6 +15,7 @@ from dateutil import parser
1515from datetime import datetime, timedelta
1616import re
1717from searx.engines.xpath import extract_text
18+from searx.languages import language_codes
1819
1920# engine dependent config
2021categories = ['general']
@@ -22,7 +23,7 @@ categories = ['general']
2223# (probably the parameter qid), require
2324# storing of qid's between mulitble search-calls
2425
25−# paging = False
26+paging = True
2627language_support = True
2728
2829# search-url
@@ -32,23 +33,32 @@ search_url = base_url + 'do/search'
3233# specific xpath variables
3334# ads xpath //div[@id="results"]/div[@id="sponsored"]//div[@class="result"]
3435# not ads: div[@class="result"] are the direct childs of div[@id="results"]
35−results_xpath = '//li[contains(@class, "search-result") and contains(@class, "search-item")]'
36−link_xpath = './/h3/a'
37−content_xpath = './p[@class="search-item__body"]'
36+results_xpath = '//div[@class="w-gl__result"]'
37+link_xpath = './/a[@class="w-gl__result-title"]'
38+content_xpath = './/p[@class="w-gl__description"]'
3839
3940
4041# do search-request
4142def request(query, params):
42− offset = (params['pageno'] - 1) * 10
4343
4444 params['url'] = search_url
4545 params['method'] = 'POST'
46− params['data'] = {'query': query,
47− 'startat': offset}
46+ params['data'] = {
47+ 'query': query,
48+ 'page': params['pageno'],
49+ 'cat': 'web',
50+ 'cmd': 'process_search',
51+ 'engine0': 'v1all',
52+ }
4853
4954 # set language if specified
5055 if params['language'] != 'all':
51− params['data']['with_language'] = ('lang_' + params['language'].split('-')[0])
56+ language = 'english'
57+ for lc, _, _, lang in language_codes:
58+ if lc == params['language']:
59+ language = lang
60+ params['data']['language'] = language
61+ params['data']['lui'] = language
5262
5363 return params
5464
searx/engines/wolframalpha_noapi.py+1−1
@@ -55,7 +55,7 @@ def obtain_token():
5555 return token
5656
5757
58−def init():
58+def init(engine_settings=None):
5959 obtain_token()
6060
6161
searx/engines/www1x.py+6−29
@@ -11,8 +11,8 @@
1111"""
1212
1313from lxml import html
14−import re
1514from searx.url_utils import urlencode, urljoin
15+from searx.engines.xpath import extract_text
1616
1717# engine dependent config
1818categories = ['images']
@@ -34,41 +34,18 @@ def request(query, params):
3434def response(resp):
3535 results = []
3636
37− # get links from result-text
38− regex = re.compile('(</a>|<a)')
39− results_parts = re.split(regex, resp.text)
40−
41− cur_element = ''
42−
43− # iterate over link parts
44− for result_part in results_parts:
37+ dom = html.fromstring(resp.text)
38+ for res in dom.xpath('//div[@class="List-item MainListing"]'):
4539 # processed start and end of link
46− if result_part == '<a':
47− cur_element = result_part
48− continue
49− elif result_part != '</a>':
50− cur_element += result_part
51− continue
52−
53− cur_element += result_part
54−
55− # fix xml-error
56− cur_element = cur_element.replace('"></a>', '"/></a>')
57−
58− dom = html.fromstring(cur_element)
59− link = dom.xpath('//a')[0]
40+ link = res.xpath('//a')[0]
6041
6142 url = urljoin(base_url, link.attrib.get('href'))
62− title = link.attrib.get('title', '')
43+ title = extract_text(link)
6344
64− thumbnail_src = urljoin(base_url, link.xpath('.//img')[0].attrib['src'])
45+ thumbnail_src = urljoin(base_url, res.xpath('.//img')[0].attrib['src'])
6546 # TODO: get image with higher resolution
6647 img_src = thumbnail_src
6748
68− # check if url is showing to a photo
69− if '/photo/' not in url:
70− continue
71−
7249 # append result
7350 results.append({'url': url,
7451 'title': title,
searx/exceptions.py+1
@@ -28,5 +28,6 @@ class SearxParameterException(SearxException):
2828 else:
2929 message = 'Invalid value "' + value + '" for parameter ' + name
3030 super(SearxParameterException, self).__init__(message)
31+ self.message = message
3132 self.parameter_name = name
3233 self.parameter_value = value
searx/plugins/https_rewrite.py+3
@@ -225,6 +225,9 @@ def https_url_rewrite(result):
225225
226226
227227def on_result(request, search, result):
228+ if 'parsed_url' not in result:
229+ return True
230+
228231 if result['parsed_url'].scheme == 'http':
229232 https_url_rewrite(result)
230233 return True
searx/plugins/oa_doi_rewrite.py+3
@@ -35,6 +35,9 @@ def get_doi_resolver(args, preference_doi_resolver):
3535
3636
3737def on_result(request, search, result):
38+ if 'parsed_url' not in result:
39+ return True
40+
3841 doi = extract_doi(result['parsed_url'])
3942 if doi and len(doi) < 50:
4043 for suffix in ('/', '.pdf', '/full', '/meta', '/abstract'):
searx/plugins/tracker_url_remover.py+19−10
@@ -17,10 +17,10 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
1717
1818from flask_babel import gettext
1919import re
20−from searx.url_utils import urlunparse
20+from searx.url_utils import urlunparse, parse_qsl, urlencode
2121
22−regexes = {re.compile(r'utm_[^&]+&?'),
23− re.compile(r'(wkey|wemail)[^&]+&?'),
22+regexes = {re.compile(r'utm_[^&]+'),
23+ re.compile(r'(wkey|wemail)[^&]*'),
2424 re.compile(r'&$')}
2525
2626name = gettext('Tracker URL remover')
@@ -30,16 +30,25 @@ preference_section = 'privacy'
3030
3131
3232def on_result(request, search, result):
33+ if 'parsed_url' not in result:
34+ return True
35+
3336 query = result['parsed_url'].query
3437
3538 if query == "":
3639 return True
37−
38− for reg in regexes:
39− query = reg.sub('', query)
40−
41− if query != result['parsed_url'].query:
42− result['parsed_url'] = result['parsed_url']._replace(query=query)
43− result['url'] = urlunparse(result['parsed_url'])
40+ parsed_query = parse_qsl(query)
41+
42+ changed = False
43+ for i, (param_name, _) in enumerate(list(parsed_query)):
44+ for reg in regexes:
45+ if reg.match(param_name):
46+ parsed_query.pop(i)
47+ changed = True
48+ break
49+
50+ if changed:
51+ result['parsed_url'] = result['parsed_url']._replace(query=urlencode(parsed_query))
52+ result['url'] = urlunparse(result['parsed_url'])
4453
4554 return True
searx/query.py+1−1
@@ -184,7 +184,7 @@ class SearchQuery(object):
184184 self.lang = lang
185185 self.safesearch = safesearch
186186 self.pageno = pageno
187− self.time_range = time_range
187+ self.time_range = None if time_range in ('', 'None', None) else time_range
188188 self.timeout_limit = timeout_limit
189189
190190 def __str__(self):
searx/results.py+45−29
@@ -197,6 +197,13 @@ class ResultContainer(object):
197197 self.infoboxes.append(infobox)
198198
199199 def _merge_result(self, result, position):
200+ if 'url' in result:
201+ self.__merge_url_result(result, position)
202+ return
203+
204+ self.__merge_result_no_url(result, position)
205+
206+ def __merge_url_result(self, result, position):
200207 result['parsed_url'] = urlparse(result['url'])
201208
202209 # if the result has no scheme, use http as default
@@ -210,51 +217,60 @@ class ResultContainer(object):
210217 if result.get('content'):
211218 result['content'] = WHITESPACE_REGEX.sub(' ', result['content'])
212219
213− # check for duplicates
214− duplicated = False
220+ duplicated = self.__find_duplicated_http_result(result)
221+ if duplicated:
222+ self.__merge_duplicated_http_result(duplicated, result, position)
223+ return
224+
225+ # if there is no duplicate found, append result
226+ result['positions'] = [position]
227+ with RLock():
228+ self._merged_results.append(result)
229+
230+ def __find_duplicated_http_result(self, result):
215231 result_template = result.get('template')
216232 for merged_result in self._merged_results:
233+ if 'parsed_url' not in merged_result:
234+ continue
217235 if compare_urls(result['parsed_url'], merged_result['parsed_url'])\
218236 and result_template == merged_result.get('template'):
219237 if result_template != 'images.html':
220238 # not an image, same template, same url : it's a duplicate
221− duplicated = merged_result
222− break
239+ return merged_result
223240 else:
224241 # it's an image
225242 # it's a duplicate if the parsed_url, template and img_src are differents
226243 if result.get('img_src', '') == merged_result.get('img_src', ''):
227− duplicated = merged_result
228− break
244+ return merged_result
245+ return None
229246
230− # merge duplicates together
231− if duplicated:
232− # using content with more text
233− if result_content_len(result.get('content', '')) >\
234− result_content_len(duplicated.get('content', '')):
235− duplicated['content'] = result['content']
247+ def __merge_duplicated_http_result(self, duplicated, result, position):
248+ # using content with more text
249+ if result_content_len(result.get('content', '')) >\
250+ result_content_len(duplicated.get('content', '')):
251+ duplicated['content'] = result['content']
236252
237− # merge all result's parameters not found in duplicate
238− for key in result.keys():
239− if not duplicated.get(key):
240− duplicated[key] = result.get(key)
253+ # merge all result's parameters not found in duplicate
254+ for key in result.keys():
255+ if not duplicated.get(key):
256+ duplicated[key] = result.get(key)
241257
242− # add the new position
243− duplicated['positions'].append(position)
258+ # add the new position
259+ duplicated['positions'].append(position)
244260
245− # add engine to list of result-engines
246− duplicated['engines'].add(result['engine'])
261+ # add engine to list of result-engines
262+ duplicated['engines'].add(result['engine'])
247263
248− # using https if possible
249− if duplicated['parsed_url'].scheme != 'https' and result['parsed_url'].scheme == 'https':
250− duplicated['url'] = result['parsed_url'].geturl()
251− duplicated['parsed_url'] = result['parsed_url']
264+ # using https if possible
265+ if duplicated['parsed_url'].scheme != 'https' and result['parsed_url'].scheme == 'https':
266+ duplicated['url'] = result['parsed_url'].geturl()
267+ duplicated['parsed_url'] = result['parsed_url']
252268
253− # if there is no duplicate found, append result
254− else:
255− result['positions'] = [position]
256− with RLock():
257− self._merged_results.append(result)
269+ def __merge_result_no_url(self, result, position):
270+ result['engines'] = set([result['engine']])
271+ result['positions'] = [position]
272+ with RLock():
273+ self._merged_results.append(result)
258274
259275 def order_results(self):
260276 for result in self._merged_results:
searx/search.py+61−13
@@ -77,7 +77,7 @@ def send_http_request(engine, request_params):
7777 return req(request_params['url'], **request_args)
7878
7979
80−def search_one_request(engine, query, request_params):
80+def search_one_http_request(engine, query, request_params):
8181 # update request parameters dependent on
8282 # search-engine (contained in engines folder)
8383 engine.request(query, request_params)
@@ -97,7 +97,53 @@ def search_one_request(engine, query, request_params):
9797 return engine.response(response)
9898
9999
100+def search_one_offline_request(engine, query, request_params):
101+ return engine.search(query, request_params)
102+
103+
100104def search_one_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit):
105+ if engines[engine_name].offline:
106+ return search_one_offline_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit) # noqa
107+ return search_one_http_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit)
108+
109+
110+def search_one_offline_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit):
111+ engine = engines[engine_name]
112+
113+ try:
114+ search_results = search_one_offline_request(engine, query, request_params)
115+
116+ if search_results:
117+ result_container.extend(engine_name, search_results)
118+
119+ engine_time = time() - start_time
120+ result_container.add_timing(engine_name, engine_time, engine_time)
121+ with threading.RLock():
122+ engine.stats['engine_time'] += engine_time
123+ engine.stats['engine_time_count'] += 1
124+
125+ except ValueError as e:
126+ record_offline_engine_stats_on_error(engine, result_container, start_time)
127+ logger.exception('engine {0} : invalid input : {1}'.format(engine_name, e))
128+ except Exception as e:
129+ record_offline_engine_stats_on_error(engine, result_container, start_time)
130+
131+ result_container.add_unresponsive_engine((
132+ engine_name,
133+ u'{0}: {1}'.format(gettext('unexpected crash'), e),
134+ ))
135+ logger.exception('engine {0} : exception : {1}'.format(engine_name, e))
136+
137+
138+def record_offline_engine_stats_on_error(engine, result_container, start_time):
139+ engine_time = time() - start_time
140+ result_container.add_timing(engine.name, engine_time, engine_time)
141+
142+ with threading.RLock():
143+ engine.stats['errors'] += 1
144+
145+
146+def search_one_http_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit):
101147 # set timeout for all HTTP requests
102148 requests_lib.set_timeout_for_thread(timeout_limit, start_time=start_time)
103149 # reset the HTTP total time
@@ -111,7 +157,7 @@ def search_one_request_safe(engine_name, query, request_params, result_container
111157
112158 try:
113159 # send requests and parse the results
114− search_results = search_one_request(engine, query, request_params)
160+ search_results = search_one_http_request(engine, query, request_params)
115161
116162 # check if the engine accepted the request
117163 if search_results is not None:
@@ -427,19 +473,21 @@ class Search(object):
427473 continue
428474
429475 # set default request parameters
430− request_params = default_request_params()
431− request_params['headers']['User-Agent'] = user_agent
432− request_params['category'] = selected_engine['category']
433− request_params['pageno'] = search_query.pageno
476+ request_params = {}
477+ if not engine.offline:
478+ request_params = default_request_params()
479+ request_params['headers']['User-Agent'] = user_agent
434480
435− if hasattr(engine, 'language') and engine.language:
436− request_params['language'] = engine.language
437− else:
438− request_params['language'] = search_query.lang
481+ if hasattr(engine, 'language') and engine.language:
482+ request_params['language'] = engine.language
483+ else:
484+ request_params['language'] = search_query.lang
439485
440− # 0 = None, 1 = Moderate, 2 = Strict
441− request_params['safesearch'] = search_query.safesearch
442− request_params['time_range'] = search_query.time_range
486+ request_params['safesearch'] = search_query.safesearch
487+ request_params['time_range'] = search_query.time_range
488+
489+ request_params['category'] = selected_engine['category']
490+ request_params['pageno'] = search_query.pageno
443491
444492 # append request to list
445493 requests.append((selected_engine['name'], search_query.query, request_params))
searx/settings.yml+9−8
@@ -161,11 +161,12 @@ engines:
161161 weight : 2
162162 disabled : True
163163
164− - name : digbt
165− engine : digbt
166− shortcut : dbt
167− timeout : 6.0
168− disabled : True
164+# cloudflare protected
165+# - name : digbt
166+# engine : digbt
167+# shortcut : dbt
168+# timeout : 6.0
169+# disabled : True
169170
170171 - name : digg
171172 engine : digg
@@ -703,9 +704,9 @@ engines:
703704 shortcut: vo
704705 categories: social media
705706 search_url : https://searchvoat.co/?t={query}
706− url_xpath : //div[@class="entry"]/p/a[contains(@class, "title")]/@href
707− title_xpath : //div[@class="entry"]/p/a[contains(@class, "title")]
708− content_xpath : //div[@class="entry"]/p/span[@class="domain"]/a/text()
707+ url_xpath : //div[@class="entry"]//p[@class="title"]/a/@href
708+ title_xpath : //div[@class="entry"]//p[@class="title"]/a/text()
709+ content_xpath : //div[@class="entry"]//span[@class="domain"]/a/text()
709710 timeout : 10.0
710711 disabled : True
711712
searx/static/themes/courgette/css/style.css+1−1
@@ -1 +1 @@
1−a,h2{color:#666}.center,html{position:relative}#categories_container>div,.top_margin a{display:inline-block}#categories,.center{text-align:center}#categories .hidden,.cache_link,.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]{-webkit-appearance:textfield}h2{text-transform:uppercase}body{font-family:sans-serif;line-height:1.5;margin:0;background:#EEE}html{min-height:100%}.title h1{font-size:7em;color:#3498DB;margin:-20px auto 0;line-height:100px;padding-bottom:20px}.center{max-width:70em;background:rgba(255,255,255,.6);padding:2em;margin:7% auto 0}.center.search{position:static;width:auto;background:0 0;margin:auto;padding-top:1.8em}@media screen and (min-width:1001px){.center:after{content:"";z-index:-1;background:url(../img/bg-body-index.jpg) no-repeat;background-size:cover;width:100%;height:100%;top:0;left:0;position:fixed}.center.search:after{content:none}}.autocompleter-choices{position:absolute;margin:0;padding:0;background:#FFF}.autocompleter-choices li{padding:.5em 1em}.autocompleter-choices li:hover{background:#3498DB;color:#FFF;cursor:pointer}.top_margin{position:absolute;bottom:-3.5em;width:100%;left:0}.top_margin a{margin-right:1em;color:#FFF;text-decoration:none}.top_margin a:focus,.top_margin a:hover{text-decoration:underline}@media screen and (max-width:1000px){.center{background:0 0}.top_margin a{color:#333}}.checkbox_container{margin-top:1.5em}.checkbox_container label{padding:.5em 1em;color:#333;cursor:pointer;font-size:.9em}.checkbox_container input[type=checkbox]:checked+label,.checkbox_container label:hover{background:#3498DB;color:#FFF}.checkbox_container input[type=checkbox]{position:absolute;top:-9999px}#categories .hidden{display:none;position:absolute;bottom:1em;left:0;text-align:center;width:100%;font-size:.9em;color:#333}#categories:hover .hidden,.right a{display:block}@media screen and (max-width:900px){#categories_container{letter-spacing:-5px}#categories_container>div{letter-spacing:normal;margin-top:1em}.checkbox_container{margin:0}.checkbox_container label{display:block;background:#CCC;padding:1em;border:1px solid #FFF}.top_margin{position:static}#categories .hidden{position:static;display:block}}@media screen and (max-width:900px) and (min-width:501px){#categories_container>div{width:31%;margin-left:2.333%}#categories_container>div:nth-child(3n+1){margin-left:0}}@media screen and (max-width:500px){#categories_container>div{width:48%;margin-left:2%;font-size:.9em}#categories_container>div:nth-child(2n+1){margin-left:0}.title h1{background:url(../img/searx-mobile.png) no-repeat;width:200px;height:39px}}#search_wrapper{position:relative}.q{padding:.5em 3em .5em 1em;width:100%;font-size:1.5em;border:0;color:#666}.cache_link,.result p{font-size:.9em}#search_submit{position:absolute;top:0;right:0;border:0;background:url(../img/search-icon.png) center center/65% auto no-repeat #3498DB;text-indent:-9999px;width:5em;height:100%;cursor:pointer}#sidebar,.right{position:fixed;width:15em;right:0;text-align:right}#search_submit:focus,#search_submit:hover{background-color:#0665A2}#sidebar{background:#3498DB;top:0;height:100%;padding:1.5em}.right{bottom:1.5em;z-index:1;padding:0 1.5em}.right a{color:#FFF;text-decoration:none}#sidebar form,#suggestions form,.row fieldset{display:inline-block}.right a:focus,.right a:hover{text-decoration:underline}#preferences{background:url(../img/preference-icon.png) right center/12% auto no-repeat;padding-right:1.8em}#search_url input{border:0;padding:.5em}#sidebar>div{margin-bottom:1em;color:#FFF}#sidebar input[type=submit]{background:#CCC;border:0;padding:.5em 1em;cursor:pointer;margin-top:.5em}#sidebar input[type=submit]:focus,#sidebar input[type=submit]:hover{color:#FFF;background-color:#0665A2}#results{padding:0 17em 0 2em}.result .content{margin:0;color:#666}.result .url{margin-top:0;color:#FF6530}.result .favicon{float:left;position:relative;top:.5em;margin-right:.5em}.definition_result{background:#CCC;padding:1em}.definition_result .result_title,.definition_result p{margin:0}.result_title{margin-bottom:0;font-weight:400}.result_title a{color:#3498DB;text-decoration:none}#answers,#suggestions span{color:#666}.result_title a:focus,.result_title a:hover{text-decoration:underline}.cache_link{color:#666}.search.center{padding-right:17em}#answers{border:2px solid #3498DB;padding:20px;text-align:center;max-width:70em;margin:0 auto 20px}#suggestions{margin-bottom:1em}#suggestions form{vertical-align:top;margin-bottom:.5em}#suggestions input[type=submit]{color:#333;padding:.5em 1em;border:0;background:#CCC;cursor:pointer}#suggestions input[type=submit]:focus,#suggestions input[type=submit]:hover{background:#3498DB;color:#FFF}#pagination{margin:1.5em 0 2em}#pagination form+form{float:right;margin-top:-2em}input[type=submit]{display:inline-block;background:#3498DB;color:#FFF;border:0;padding:.6em 1em;cursor:pointer}input[type=submit]:focus,input[type=submit]:hover{background:#0665A2}.row{max-width:60em;margin:auto}.row a{color:#3498DB}.row form{letter-spacing:-5px}.row form>*{letter-spacing:normal}.row p{margin:0}.row fieldset{width:48%;vertical-align:top}.row fieldset:last-of-type{display:block;width:auto;background:0 0;padding:0}fieldset,table tr:nth-child(odd){background:#CCC}.row fieldset:nth-child(odd){margin-right:2%}.row fieldset:nth-child(2){min-height:10.5em}@media screen and (max-width:900px){.row{margin:0 1em}.row fieldset{width:49%}.row fieldset,.row fieldset:nth-child(odd){margin-right:0}.row fieldset:first-child{width:100%;margin-right:0}.row fieldset:nth-child(even){margin-right:2%}}@media screen and (max-width:800px){.row fieldset,select{width:100%}table{font-size:.8em}#sidebar,.right{display:none}#results{padding:0 2em}.search.center{padding-right:2em}}@media screen and (max-width:400px){.row #categories_container>div{width:100%;margin-left:0}}fieldset{border:0;margin:1em 0;padding:1.5em}table{width:100%;text-align:left;border:1px solid #CCC;border-collapse:collapse}table th{background:#999;color:#FFF}table td,table th{padding:.5em 1em;border:1px solid #FFF}.engine_checkbox label{padding:.5em;background:#3498DB;color:#FFF;cursor:pointer}.engine_checkbox .deny{background:#3498DB}.engine_checkbox .allow{display:none;background:#666}.engine_checkbox input{display:none}.engine_checkbox input:checked+.allow{display:inline}.engine_checkbox input:checked+.allow+.deny{display:none}.row input[type=submit]{font-size:1em;margin:1em 0 2em}.row .right{position:static;display:inline-block}.row .right a{color:#333;width:auto;text-align:left;padding:0}.small_font{font-size:.8em}table th{padding:1em}legend{background:#EEE;padding:0 1em;position:relative}select{border:1px solid #DDD;padding:.5em .8em;font-size:1em}.highlight .hll{background-color:#ffc}.highlight{font-weight:700;background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .gr{color:red}.highlight .gh{color:navy;font-weight:700}.highlight .gi{color:#00A000}.highlight .go{color:#888}.highlight .gp{color:navy;font-weight:700}.highlight .gs{font-weight:700}.highlight .gu{color:purple;font-weight:700}.highlight .gt{color:#04D}.highlight .kc,.highlight .kd,.highlight .kn{color:green;font-weight:700}.highlight .kp{color:green}.highlight .kr{color:green;font-weight:700}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#00F;font-weight:700}.highlight .no{color:#800}.highlight .nd{color:#A2F}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#00F}.highlight .nl{color:#A0A000}.highlight .nn{color:#00F;font-weight:700}.highlight .nt{color:green;font-weight:700}.highlight .nv{color:#19177C}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#BA2121}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:green}.highlight .sr{color:#B68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}
1+a,h2{color:#666}.center,html{position:relative}#categories_container>div,.top_margin a{display:inline-block}#categories,.center{text-align:center}#categories .hidden,.cache_link,.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]{-webkit-appearance:textfield}h2{text-transform:uppercase}body{font-family:sans-serif;line-height:1.5;margin:0;background:#EEE}html{min-height:100%}.title h1{font-size:7em;color:#3498DB;margin:-20px auto 0;line-height:100px;padding-bottom:20px}.center{max-width:70em;background:rgba(255,255,255,.6);padding:2em;margin:7% auto 0}.center.search{position:static;width:auto;background:0 0;margin:auto;padding-top:1.8em}@media screen and (min-width:1001px){.center:after{content:"";z-index:-1;background:url(../img/bg-body-index.jpg) no-repeat;background-size:cover;width:100%;height:100%;top:0;left:0;position:fixed}.center.search:after{content:none}}.autocompleter-choices{position:absolute;margin:0;padding:0;background:#FFF}.autocompleter-choices li{padding:.5em 1em}.autocompleter-choices li:hover{background:#3498DB;color:#FFF;cursor:pointer}.top_margin{position:absolute;bottom:-3.5em;width:100%;left:0}.top_margin a{margin-right:1em;color:#FFF;text-decoration:none}.top_margin a:focus,.top_margin a:hover{text-decoration:underline}@media screen and (max-width:1000px){.center{background:0 0}.top_margin a{color:#333}}.checkbox_container{margin-top:1.5em}.checkbox_container label{padding:.5em 1em;color:#333;cursor:pointer;font-size:.9em}.checkbox_container input[type=checkbox]:checked+label,.checkbox_container label:hover{background:#3498DB;color:#FFF}.checkbox_container input[type=checkbox]{position:absolute;top:-9999px}#categories .hidden{display:none;position:absolute;bottom:1em;left:0;text-align:center;width:100%;font-size:.9em;color:#333}#categories:hover .hidden,.right a{display:block}@media screen and (max-width:900px){#categories_container{letter-spacing:-5px}#categories_container>div{letter-spacing:normal;margin-top:1em}.checkbox_container{margin:0}.checkbox_container label{display:block;background:#CCC;padding:1em;border:1px solid #FFF}.top_margin{position:static}#categories .hidden{position:static;display:block}}@media screen and (max-width:900px) and (min-width:501px){#categories_container>div{width:31%;margin-left:2.333%}#categories_container>div:nth-child(3n+1){margin-left:0}}@media screen and (max-width:500px){#categories_container>div{width:48%;margin-left:2%;font-size:.9em}#categories_container>div:nth-child(2n+1){margin-left:0}.title h1{background:url(../img/searx-mobile.png) no-repeat;width:200px;height:39px}}#search_wrapper{position:relative}.q{padding:.5em 3em .5em 1em;width:100%;font-size:1.5em;border:0;color:#666}.cache_link,.result p{font-size:.9em}#search_submit{position:absolute;top:0;right:0;border:0;background:url(../img/search-icon.png) center center/65% auto no-repeat #3498DB;text-indent:-9999px;width:5em;height:100%;cursor:pointer}#sidebar,.right{position:fixed;width:15em;right:0;text-align:right}#search_submit:focus,#search_submit:hover{background-color:#0665A2}#sidebar{background:#3498DB;top:0;height:100%;padding:1.5em}.right{bottom:1.5em;z-index:1;padding:0 1.5em}.right a{color:#FFF;text-decoration:none}#sidebar form,#suggestions form,.row fieldset{display:inline-block}.right a:focus,.right a:hover{text-decoration:underline}#preferences{background:url(../img/preference-icon.png) right center/12% auto no-repeat;padding-right:1.8em}#search_url input{border:0;padding:.5em}#sidebar>div{margin-bottom:1em;color:#FFF}#sidebar input[type=submit]{background:#CCC;border:0;padding:.5em 1em;cursor:pointer;margin-top:.5em}#sidebar input[type=submit]:focus,#sidebar input[type=submit]:hover{color:#FFF;background-color:#0665A2}#results{padding:0 17em 0 2em}.result .engines{text-align:right}.result .content{margin:0;color:#666}.result .url{margin-top:0;color:#FF6530}.result .favicon{float:left;position:relative;top:.5em;margin-right:.5em}.definition_result{background:#CCC;padding:1em}.definition_result .result_title,.definition_result p{margin:0}.result_title{margin-bottom:0;font-weight:400}.result_title a{color:#3498DB;text-decoration:none}#answers,#suggestions span{color:#666}.result_title a:focus,.result_title a:hover{text-decoration:underline}.cache_link{color:#666}.search.center{padding-right:17em}#answers{border:2px solid #3498DB;padding:20px;text-align:center;max-width:70em;margin:0 auto 20px}#suggestions{margin-bottom:1em}#suggestions form{vertical-align:top;margin-bottom:.5em}#suggestions input[type=submit]{color:#333;padding:.5em 1em;border:0;background:#CCC;cursor:pointer}#suggestions input[type=submit]:focus,#suggestions input[type=submit]:hover{background:#3498DB;color:#FFF}#pagination{margin:1.5em 0 2em}#pagination form+form{float:right;margin-top:-2em}input[type=submit]{display:inline-block;background:#3498DB;color:#FFF;border:0;padding:.6em 1em;cursor:pointer}input[type=submit]:focus,input[type=submit]:hover{background:#0665A2}.row{max-width:60em;margin:auto}.row a{color:#3498DB}.row form{letter-spacing:-5px}.row form>*{letter-spacing:normal}.row p{margin:0}.row fieldset{width:48%;vertical-align:top}.row fieldset:last-of-type{display:block;width:auto;background:0 0;padding:0}fieldset,table tr:nth-child(odd){background:#CCC}.row fieldset:nth-child(odd){margin-right:2%}.row fieldset:nth-child(2){min-height:10.5em}@media screen and (max-width:900px){.row{margin:0 1em}.row fieldset{width:49%}.row fieldset,.row fieldset:nth-child(odd){margin-right:0}.row fieldset:first-child{width:100%;margin-right:0}.row fieldset:nth-child(even){margin-right:2%}}@media screen and (max-width:800px){.row fieldset,select{width:100%}table{font-size:.8em}#sidebar,.right{display:none}#results{padding:0 2em}.search.center{padding-right:2em}}@media screen and (max-width:400px){.row #categories_container>div{width:100%;margin-left:0}}fieldset{border:0;margin:1em 0;padding:1.5em}table{width:100%;text-align:left;border:1px solid #CCC;border-collapse:collapse}table th{background:#999;color:#FFF}table td,table th{padding:.5em 1em;border:1px solid #FFF}.engine_checkbox label{padding:.5em;background:#3498DB;color:#FFF;cursor:pointer}.engine_checkbox .deny{background:#3498DB}.engine_checkbox .allow{display:none;background:#666}.engine_checkbox input{display:none}.engine_checkbox input:checked+.allow{display:inline}.engine_checkbox input:checked+.allow+.deny{display:none}.row input[type=submit]{font-size:1em;margin:1em 0 2em}.row .right{position:static;display:inline-block}.row .right a{color:#333;width:auto;text-align:left;padding:0}.small_font{font-size:.8em}table th{padding:1em}legend{background:#EEE;padding:0 1em;position:relative}select{border:1px solid #DDD;padding:.5em .8em;font-size:1em}.highlight .hll{background-color:#ffc}.highlight{font-weight:700;background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .gr{color:red}.highlight .gh{color:navy;font-weight:700}.highlight .gi{color:#00A000}.highlight .go{color:#888}.highlight .gp{color:navy;font-weight:700}.highlight .gs{font-weight:700}.highlight .gu{color:purple;font-weight:700}.highlight .gt{color:#04D}.highlight .kc,.highlight .kd,.highlight .kn{color:green;font-weight:700}.highlight .kp{color:green}.highlight .kr{color:green;font-weight:700}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#00F;font-weight:700}.highlight .no{color:#800}.highlight .nd{color:#A2F}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#00F}.highlight .nl{color:#A0A000}.highlight .nn{color:#00F;font-weight:700}.highlight .nt{color:green;font-weight:700}.highlight .nv{color:#19177C}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#BA2121}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:green}.highlight .sr{color:#B68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}
searx/static/themes/courgette/less/style.less+4
@@ -325,6 +325,10 @@ a {
325325 font-size: 0.9em;
326326}
327327
328+.result .engines {
329+ text-align: right;
330+}
331+
328332.result .content {
329333 margin: 0;
330334 color: #666;
searx/static/themes/legacy/css/style.css+1−1
@@ -1 +1 @@
1−.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}#categories,.highlight .lineno{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#container,.search,body,html{padding:0;margin:0}div.title h1,input[type=checkbox]{visibility:hidden}#categories,.checkbox_container label,.engine_checkbox label,.highlight .lineno{-webkit-touch-callout:none;-khtml-user-select:none}#answers input[type=submit],#infoboxes input[type=submit],#sidebar input[type=submit],#suggestions input[type=submit],.result_title a:hover,.torrent_result a:hover{text-decoration:underline}#infoboxes,.result .content,.result .url,.result h3{word-wrap:break-word}#apis,#infoboxes .infobox br,#pagination,#pagination br,.result,.result .content br.last{clear:both}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .gr{color:red}.highlight .gh{color:navy;font-weight:700}.highlight .gi{color:#00A000}.highlight .go{color:#888}.highlight .gp{color:navy;font-weight:700}.highlight .gs{font-weight:700}.highlight .gu{color:purple;font-weight:700}.highlight .gt{color:#04D}.highlight .kc,.highlight .kd,.highlight .kn{color:green;font-weight:700}.highlight .kp{color:green}.highlight .kr{color:green;font-weight:700}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#00F;font-weight:700}.highlight .no{color:#800}.highlight .nd{color:#A2F}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#00F}.highlight .nl{color:#A0A000}.highlight .nn{color:#00F;font-weight:700}.highlight .nt{color:green;font-weight:700}.highlight .nv{color:#19177C}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#BA2121}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:green}.highlight .sr{color:#B68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}html{font-family:sans-serif;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444}#container{width:100%;position:absolute;top:0}.search .checkbox_container label{font-size:.9em;border-bottom:2px solid #E8E7E6}.search .checkbox_container label:hover{border-bottom:2px solid #3498DB}.search .checkbox_container input[type=checkbox]:checked+label{border-bottom:2px solid #2980B9}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q,ul.autocompleter-choices{margin:0;border:1px solid #3498DB}.q{background:#FFF;color:#222;font-size:16px;height:28px;outline:0;padding:2px 2px 2px 8px;padding-right:0!important;width:100%;z-index:2}#search_submit{position:absolute;top:13px;right:1px;padding:0;border:0;background:url(../img/search-icon.png) no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:30px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}ul.autocompleter-choices{position:absolute;padding:0;list-style:none;border-left-color:#3498DB;border-right-color:#3498DB;border-bottom-color:#3498DB;text-align:left;font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;z-index:50;background-color:#FFF;color:#444}ul.autocompleter-choices li{position:relative;margin:-2px 0 0;padding:.2em 1.5em .2em 1em;display:block;float:none!important;cursor:pointer;font-weight:400;white-space:nowrap;font-size:1em;line-height:1.5em}ul.autocompleter-choices li.autocompleter-selected{background-color:#444;color:#FFF}ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried{color:#9FCFFF}ul.autocompleter-choices span.autocompleter-queried{display:inline;float:none;font-weight:700;margin:0;padding:0}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498DB;padding:4px 10px}a:active.hmarg,a:hover.hmarg,a:link.hmarg,a:visited.hmarg{color:#3498DB}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url(../img/searx.png) center no-repeat;width:100%;min-height:80px}input[type=submit]{padding:2px 6px;margin:2px 4px;display:inline-block;background:#3498DB;color:#FFF;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}fieldset{margin:8px;border:1px solid #3498DB}#categories{margin:0 10px;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type=checkbox]:checked+label{background:#3498DB;color:#FFF}.engine_checkbox{padding:4px}label.allow{background:#E74C3C;padding:4px 8px;color:#FFF;display:none}label.deny{background:#2ECC71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type=checkbox]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type=checkbox]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8E44AD}.result{margin:19px 0 18px;padding:0}.result_title{margin-bottom:0}.result_title a{color:#2980B9;font-weight:400;font-size:1.1em}.result_title a:visited{color:#8E44AD}.cache_link{font-size:10px!important}.result h3{font-size:1em;margin:5px 0 1px;padding:0}.result .content,.result .url,.small_font{font-size:.8em}.result .content{margin:0;padding:0;max-width:54em;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}.result .url{margin:0 0 3px;padding:0;max-width:54em;color:#C0392B}.result .published_date{font-size:.8em;color:#888;Margin:5px 20px}.result .thumbnail{width:400px}.engines{color:#888}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.image_result{display:inline-block;margin:10px;position:relative;max-height:160px}.image_result img{border:0;max-height:160px}.image_result p{margin:0;padding:0}.image_result p span a{display:none;color:#FFF}.image_result p:hover span a{display:block;position:absolute;bottom:0;right:0;padding:4px;background-color:rgba(0,0,0,.6);font-size:.7em}#categories_container,.percentage{position:relative}.torrent_result{border-left:10px solid #d3d3d3;padding-left:3px}.torrent_result p{margin:3px;font-size:.8em}.torrent_result a{color:#2980B9}.torrent_result a:visited{color:#8E44AD}.definition_result{border-left:10px solid gray;padding-left:3px}#infoboxes,#sidebar{margin:0 2px 5px 5px;padding:0 2px 2px}.percentage{width:300px}.percentage div{background:#444}table{width:100%}td{padding:0 4px}tr:hover{background:#DDD}#results{margin:auto auto 20px;padding:0;width:50em}#sidebar{position:fixed;bottom:10px;left:10px;width:14em}#answers input,#infoboxes input,#sidebar input,#suggestions input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:0 0;color:#444;cursor:pointer}#suggestions form{display:inline}#answers,#suggestions{margin-top:20px;max-width:45em}#suggestions-title{color:#888}#answers{border:2px solid #2980B9;padding:20px}#answers form,#infoboxes form{min-width:210px}#infoboxes{position:absolute;top:100px;right:20px;max-width:21em}#infoboxes .infobox{margin:10px 0;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:90%;max-heigt:12em;display:block;margin:5px;padding:5px}#infoboxes .infobox h2{margin:0}#apis,#search_url{margin-top:8px}#infoboxes .infobox table{table-layout:fixed}#infoboxes .infobox table td{vertical-align:top}#infoboxes .infobox input{font-size:1em}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url(../img/preference-icon.png) no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.github{display:none}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed!important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#answers,#suggestions{margin-top:5px}#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin:auto}#sidebar{position:static;max-width:50em;margin:0 0 2px;padding:0;float:none;border:none;width:auto}#sidebar input{border:0}#apis,#search_url{display:none}.result{border-top:1px solid #E8E7E6;margin:8px 0}.image_result,.image_result img,.result .thumbnail{max-width:98%}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:#3498DB;border:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#FFF}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:#fff;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8}
1+.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}#categories,.highlight .lineno{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#container,.search,body,html{padding:0;margin:0}div.title h1,input[type=checkbox]{visibility:hidden}#categories,.checkbox_container label,.engine_checkbox label,.highlight .lineno{-webkit-touch-callout:none;-khtml-user-select:none}#answers input[type=submit],#infoboxes input[type=submit],#sidebar input[type=submit],#suggestions input[type=submit],.result_title a:hover,.torrent_result a:hover{text-decoration:underline}#infoboxes,.result .content,.result .url,.result h3{word-wrap:break-word}#apis,#infoboxes .infobox br,#pagination,#pagination br,.result,.result .content br.last{clear:both}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .gr{color:red}.highlight .gh{color:navy;font-weight:700}.highlight .gi{color:#00A000}.highlight .go{color:#888}.highlight .gp{color:navy;font-weight:700}.highlight .gs{font-weight:700}.highlight .gu{color:purple;font-weight:700}.highlight .gt{color:#04D}.highlight .kc,.highlight .kd,.highlight .kn{color:green;font-weight:700}.highlight .kp{color:green}.highlight .kr{color:green;font-weight:700}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#00F;font-weight:700}.highlight .no{color:#800}.highlight .nd{color:#A2F}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#00F}.highlight .nl{color:#A0A000}.highlight .nn{color:#00F;font-weight:700}.highlight .nt{color:green;font-weight:700}.highlight .nv{color:#19177C}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#BA2121}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:green}.highlight .sr{color:#B68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}html{font-family:sans-serif;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444}#container{width:100%;position:absolute;top:0}.search .checkbox_container label{font-size:.9em;border-bottom:2px solid #E8E7E6}.search .checkbox_container label:hover{border-bottom:2px solid #3498DB}.search .checkbox_container input[type=checkbox]:checked+label{border-bottom:2px solid #2980B9}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q,ul.autocompleter-choices{margin:0;border:1px solid #3498DB}.q{background:#FFF;color:#222;font-size:16px;height:28px;outline:0;padding:2px 2px 2px 8px;padding-right:0!important;width:100%;z-index:2}#search_submit{position:absolute;top:13px;right:1px;padding:0;border:0;background:url(../img/search-icon.png) no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:30px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}ul.autocompleter-choices{position:absolute;padding:0;list-style:none;border-left-color:#3498DB;border-right-color:#3498DB;border-bottom-color:#3498DB;text-align:left;font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;z-index:50;background-color:#FFF;color:#444}ul.autocompleter-choices li{position:relative;margin:-2px 0 0;padding:.2em 1.5em .2em 1em;display:block;float:none!important;cursor:pointer;font-weight:400;white-space:nowrap;font-size:1em;line-height:1.5em}ul.autocompleter-choices li.autocompleter-selected{background-color:#444;color:#FFF}ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried{color:#9FCFFF}ul.autocompleter-choices span.autocompleter-queried{display:inline;float:none;font-weight:700;margin:0;padding:0}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498DB;padding:4px 10px}a:active.hmarg,a:hover.hmarg,a:link.hmarg,a:visited.hmarg{color:#3498DB}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url(../img/searx.png) center no-repeat;width:100%;min-height:80px}input[type=submit]{padding:2px 6px;margin:2px 4px;display:inline-block;background:#3498DB;color:#FFF;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}fieldset{margin:8px;border:1px solid #3498DB}#categories{margin:0 10px;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type=checkbox]:checked+label{background:#3498DB;color:#FFF}.engine_checkbox{padding:4px}label.allow{background:#E74C3C;padding:4px 8px;color:#FFF;display:none}label.deny{background:#2ECC71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type=checkbox]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type=checkbox]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8E44AD}.result{margin:19px 0 18px;padding:0}.result_title{margin-bottom:0}.result_title a{color:#2980B9;font-weight:400;font-size:1.1em}.result_title a:visited{color:#8E44AD}.cache_link{font-size:10px!important}.result h3{font-size:1em;margin:5px 0 1px;padding:0}.result .content,.result .url,.small_font{font-size:.8em}.result .content{margin:0;padding:0;max-width:54em;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}.result .url{margin:0 0 3px;padding:0;max-width:54em;color:#C0392B}.result .published_date{font-size:.8em;color:#888;Margin:5px 20px}.result .thumbnail{width:400px}.engines{color:#888}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.image_result{display:inline-block;margin:10px;position:relative;max-height:160px}.image_result img{border:0;max-height:160px}.image_result p{margin:0;padding:0}.image_result p span a{display:none;color:#FFF}.image_result p:hover span a{display:block;position:absolute;bottom:0;right:0;padding:4px;background-color:rgba(0,0,0,.6);font-size:.7em}#categories_container,.percentage{position:relative}.torrent_result{border-left:10px solid #d3d3d3;padding-left:3px}.torrent_result p{margin:3px;font-size:.8em}.torrent_result a{color:#2980B9}.torrent_result a:visited{color:#8E44AD}.definition_result{border-left:10px solid gray;padding-left:3px}.percentage{width:300px}.percentage div{background:#444}table{width:100%}.result-table{margin-bottom:10px}#infoboxes,#sidebar{margin:0 2px 5px 5px;padding:0 2px 2px}td{padding:0 4px}tr:hover{background:#DDD}#results{margin:auto auto 20px;padding:0;width:50em}#sidebar{position:fixed;bottom:10px;left:10px;width:14em}#answers input,#infoboxes input,#sidebar input,#suggestions input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:0 0;color:#444;cursor:pointer}#suggestions form{display:inline}#answers,#suggestions{margin-top:20px;max-width:45em}#suggestions-title{color:#888}#answers{border:2px solid #2980B9;padding:20px}#answers form,#infoboxes form{min-width:210px}#infoboxes{position:absolute;top:100px;right:20px;max-width:21em}#infoboxes .infobox{margin:10px 0;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:90%;max-heigt:12em;display:block;margin:5px;padding:5px}#infoboxes .infobox h2{margin:0}#apis,#search_url{margin-top:8px}#infoboxes .infobox table{table-layout:fixed}#infoboxes .infobox table td{vertical-align:top}#infoboxes .infobox input{font-size:1em}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url(../img/preference-icon.png) no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.github{display:none}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed!important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#answers,#suggestions{margin-top:5px}#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin:auto}#sidebar{position:static;max-width:50em;margin:0 0 2px;padding:0;float:none;border:none;width:auto}#sidebar input{border:0}#apis,#search_url{display:none}.result{border-top:1px solid #E8E7E6;margin:8px 0}.image_result,.image_result img,.result .thumbnail{max-width:98%}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:#3498DB;border:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#FFF}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:#fff;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8}
searx/static/themes/legacy/less/style.less+4
@@ -376,6 +376,10 @@ table {
376376 width: 100%;
377377}
378378
379+.result-table {
380+ margin-bottom: 10px;
381+}
382+
379383td {
380384 padding: 0 4px;
381385}
searx/templates/courgette/result_templates/key-value.htmlnew file+13
@@ -0,0 +1,13 @@
1+<div class="result">
2+<table>
3+ {% for key, value in result.items() %}
4+ {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %}
5+ {% continue %}
6+ {% endif %}
7+ <tr>
8+ <td><b>{{ key|upper }}</b>: {{ value|safe }}</td>
9+ </tr>
10+ {% endfor %}
11+</table>
12+<p class="engines">{{ result.engines|join(', ') }}</p>
13+</div>
searx/templates/legacy/result_templates/key-value.htmlnew file+13
@@ -0,0 +1,13 @@
1+<table class="result-table">
2+ {% for key, value in result.items() %}
3+ {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %}
4+ {% continue %}
5+ {% endif %}
6+ <tr>
7+ <td><b>{{ key|upper }}</b>: {{ value|safe }}</td>
8+ </tr>
9+ {% endfor %}
10+ <tr>
11+ <td><b>ENGINES</b>: {{ result.engines|join(', ') }}</td>
12+ </tr>
13+</table>
searx/templates/oscar/macros.html+9−1
@@ -14,7 +14,7 @@
1414
1515<!-- Draw result header -->
1616{% macro result_header(result, favicons) -%}
17−<h4 class="result_header">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result_link(result.url, result.title|safe) }}</h4>
17+<h4 class="result_header">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{% if result.url %}{{ result_link(result.url, result.title|safe) }}{% else %}{{ result.title|safe}}{% endif %}</h4>
1818{%- endmacro %}
1919
2020<!-- Draw result sub header -->
@@ -31,12 +31,16 @@
3131 {% for engine in result.engines %}
3232 <span class="label label-default">{{ engine }}</span>
3333 {% endfor %}
34+ {% if result.url %}
3435 <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small>
36+ {% endif %}
3537 {% if proxify %}
3638 <small>{{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }}</small>
3739 {% endif %}
3840</div>
41+{% if result.pretty_url %}
3942<div class="external-link">{{ result.pretty_url }}</div>
43+{% endif %}
4044{%- endmacro %}
4145
4246<!-- Draw result footer -->
@@ -45,11 +49,15 @@
4549 {% for engine in result.engines %}
4650 <span class="label label-default">{{ engine }}</span>
4751 {% endfor %}
52+ {% if result.url %}
4853 <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small>
54+ {% endif %}
4955 {% if proxify %}
5056 <small>{{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }}</small>
5157 {% endif %}
58+ {% if result.pretty_url %}
5259 <div class="external-link">{{ result.pretty_url }}</div>
60+ {% endif %}
5361{%- endmacro %}
5462
5563{% macro preferences_item_header(info, label, rtl) -%}
searx/templates/oscar/result_templates/key-value.htmlnew file+19
@@ -0,0 +1,19 @@
1+{% from 'oscar/macros.html' import result_footer, result_footer_rtl with context %}
2+<div class="panel panel-default">
3+<table class="table table-responsive table-bordered table-condensed">
4+ {% for key, value in result.items() %}
5+ {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %}
6+ {% continue %}
7+ {% endif %}
8+ <tr>
9+ <td><b>{{ key|upper }}</b>: {{ value }}</td>
10+ </tr>
11+ {% endfor %}
12+</table>
13+
14+{% if rtl %}
15+{{ result_footer_rtl(result) }}
16+{% else %}
17+{{ result_footer(result) }}
18+{% endif %}
19+</div>
searx/templates/simple/result_templates/key-value.htmlnew file+11
@@ -0,0 +1,11 @@
1+<table>
2+ {% for key, value in result.items() %}
3+ {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %}
4+ {% continue %}
5+ {% endif %}
6+ <tr>
7+ <td><b>{{ key|upper }}</b>: {{ value }}</td>
8+ </tr>
9+ {% endfor %}
10+</table>
11+<div class="engines">{% for engine in result.engines %}<span>{{ engine }}</span>{% endfor %}</div>{{- '' -}}
searx/utils.py+18−2
@@ -308,14 +308,15 @@ def int_or_zero(num):
308308
309309def is_valid_lang(lang):
310310 is_abbr = (len(lang) == 2)
311+ lang = lang.lower().decode('utf-8')
311312 if is_abbr:
312313 for l in language_codes:
313− if l[0][:2] == lang.lower():
314+ if l[0][:2] == lang:
314315 return (True, l[0][:2], l[3].lower())
315316 return False
316317 else:
317318 for l in language_codes:
318− if l[1].lower() == lang.lower():
319+ if l[1].lower() == lang or l[3].lower() == lang:
319320 return (True, l[0][:2], l[3].lower())
320321 return False
321322
@@ -434,3 +435,18 @@ def ecma_unescape(s):
434435 # "%20" becomes " ", "%F3" becomes "ó"
435436 s = ecma_unescape2_re.sub(lambda e: unichr(int(e.group(1), 16)), s)
436437 return s
438+
439+
440+def get_engine_from_settings(name):
441+ """Return engine configuration from settings.yml of a given engine name"""
442+
443+ if 'engines' not in settings:
444+ return {}
445+
446+ for engine in settings['engines']:
447+ if 'name' not in engine:
448+ continue
449+ if name == engine['name']:
450+ return engine
451+
452+ return {}
searx/webapp.py+5−2
@@ -124,6 +124,7 @@ app = Flask(
124124
125125app.jinja_env.trim_blocks = True
126126app.jinja_env.lstrip_blocks = True
127+app.jinja_env.add_extension('jinja2.ext.loopcontrols')
127128app.secret_key = settings['server']['secret_key']
128129
129130if not searx_debug \
@@ -538,14 +539,16 @@ def index():
538539 if output_format == 'html':
539540 if 'content' in result and result['content']:
540541 result['content'] = highlight_content(escape(result['content'][:1024]), search_query.query)
541− result['title'] = highlight_content(escape(result['title'] or u''), search_query.query)
542+ if 'title' in result and result['title']:
543+ result['title'] = highlight_content(escape(result['title'] or u''), search_query.query)
542544 else:
543545 if result.get('content'):
544546 result['content'] = html_to_text(result['content']).strip()
545547 # removing html content and whitespace duplications
546548 result['title'] = ' '.join(html_to_text(result['title']).strip().split())
547549
548− result['pretty_url'] = prettify_url(result['url'])
550+ if 'url' in result:
551+ result['pretty_url'] = prettify_url(result['url'])
549552
550553 # TODO, check if timezone is calculated right
551554 if 'publishedDate' in result:
tests/unit/engines/test_deviantart.py−71
@@ -22,74 +22,3 @@ class TestDeviantartEngine(SearxTestCase):
2222 dicto['time_range'] = 'year'
2323 params = deviantart.request(query, dicto)
2424 self.assertEqual({}, params['url'])
25−
26− def test_response(self):
27− self.assertRaises(AttributeError, deviantart.response, None)
28− self.assertRaises(AttributeError, deviantart.response, [])
29− self.assertRaises(AttributeError, deviantart.response, '')
30− self.assertRaises(AttributeError, deviantart.response, '[]')
31−
32− response = mock.Mock(text='<html></html>')
33− self.assertEqual(deviantart.response(response), [])
34−
35− response = mock.Mock(status_code=302)
36− self.assertEqual(deviantart.response(response), [])
37−
38− html = """
39− <div id="page-1-results" class="page-results results-page-thumb torpedo-container">
40− <span class="thumb wide" href="http://amai911.deviantart.com/art/Horse-195212845"
41− data-super-full-width="900" data-super-full-height="600">
42− <a class="torpedo-thumb-link" href="https://url.of.image">
43− <img data-sigil="torpedo-img" src="https://url.of.thumbnail" />
44− </a>
45− <span class="info"><span class="title-wrap"><span class="title">Title of image</span></span>
46− </div>
47− """
48− response = mock.Mock(text=html)
49− results = deviantart.response(response)
50− self.assertEqual(type(results), list)
51− self.assertEqual(len(results), 1)
52− self.assertEqual(results[0]['title'], 'Title of image')
53− self.assertEqual(results[0]['url'], 'https://url.of.image')
54− self.assertNotIn('content', results[0])
55− self.assertEqual(results[0]['thumbnail_src'], 'https://url.of.thumbnail')
56−
57− html = """
58− <span class="tt-fh-tc" style="width: 202px;">
59− <span class="tt-bb" style="width: 202px;">
60− </span>
61− <span class="shadow">
62− <a class="thumb" href="http://url.of.result/2nd.part.of.url"
63− title="Behoimi BE Animation Test by test-0, Jan 4,
64− 2010 in Digital Art &gt; Animation"> <i></i>
65− <img width="200" height="200" alt="Test"
66− src="http://url.of.thumbnail" data-src="http://th08.deviantart.net/test.jpg">
67− </a>
68− </span>
69− <!-- ^TTT -->
70− </span>
71− <span class="details">
72− <a href="http://test-0.deviantart.com/art/Test" class="t"
73− title="Behoimi BE Animation Test by test-0, Jan 4, 2010">
74− <span class="tt-fh-oe">Title of image</span> </a>
75− <small>
76− <span class="category">
77− <span class="age">
78− 5 years ago
79− </span>
80− in <a title="Behoimi BE Animation Test by test-0, Jan 4, 2010"
81− href="http://www.deviantart.com/browse/all/digitalart/animation/">Animation</a>
82− </span>
83− <div class="commentcount">
84− <a href="http://test-0.deviantart.com/art/Test#comments">
85− <span class="iconcommentsstats"></span>9 Comments</a>
86− </div>
87− <a class="mlt-link" href="http://www.deviantart.com/morelikethis/149167425">
88− <span class="mlt-icon"></span> <span class="mlt-text">More Like This</span> </a>
89− </span>
90− </small> <!-- TTT$ -->
91− """
92− response = mock.Mock(text=html)
93− results = deviantart.response(response)
94− self.assertEqual(type(results), list)
95− self.assertEqual(len(results), 0)
tests/unit/engines/test_digg.py−85
@@ -14,88 +14,3 @@ class TestDiggEngine(SearxTestCase):
1414 self.assertIn('url', params)
1515 self.assertIn(query, params['url'])
1616 self.assertIn('digg.com', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, digg.response, None)
20− self.assertRaises(AttributeError, digg.response, [])
21− self.assertRaises(AttributeError, digg.response, '')
22− self.assertRaises(AttributeError, digg.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(digg.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(digg.response(response), [])
29−
30− json = """
31− {
32− "status": "ok",
33− "num": 10,
34− "next_position": 20,
35− "html": "<article itemscope itemtype=\\"http://schema.org/Article\\"
36− class=\\"story-container digg-story-el hentry entry story-1sRANah col-1\\"
37− data-content-id=\\"1sRANah\\" data-contenturl=\\"http://url.of.link\\"
38− data-position=\\"0\\" data-diggs=\\"24\\" data-tweets=\\"69\\"
39− data-digg-score=\\"1190\\"> <div class=\\"story-image story-image-thumb\\">
40− <a data-position=\\"0\\" data-content-id=\\"1sRANah\\"
41− class=\\"story-link\\" href=\\"http://www.thedailybeast.com/\\"
42− target=\\"_blank\\"><img class=\\"story-image-img\\"
43− src=\\"http://url.of.image.jpeg\\" width=\\"312\\" height=\\"170\\"
44− alt=\\"\\" /> </a> </div> <div class=\\"story-content\\"><header
45− class=\\"story-header\\"> <div itemprop=\\"alternativeHeadline\\"
46− class=\\"story-kicker\\" >Kicker</div> <h2 itemprop=\\"headline\\"
47− class=\\"story-title entry-title\\"><a class=\\"story-title-link story-link\\"
48− rel=\\"bookmark\\" itemprop=\\"url\\" href=\\"http://www.thedailybeast.com/\\"
49− target=\\"_blank\\">Title of article</h2> <div class=\\"story-meta\\">
50− <div class=\\"story-score \\">
51− <div class=\\"story-score-diggscore diggscore-1sRANah\\">1190</div>
52− <div class=\\"story-score-details\\"> <div class=\\"arrow\\"></div>
53− <ul class=\\"story-score-details-list\\"> <li
54− class=\\"story-score-detail story-score-diggs\\"><span
55− class=\\"label\\">Diggs:</span> <span class=\\"count diggs-1sRANah\\">24</span>
56− </li> <li class=\\"story-score-detail story-score-twitter\\"><span
57− class=\\"label\\">Tweets:</span> <span class=\\"count tweets-1sRANah\\">69</span>
58− </li> <li class=\\"story-score-detail story-score-facebook\\"><span
59− class=\\"label\\">Facebook Shares:</span> <span
60− class=\\"count fb_shares-1sRANah\\">1097</span></li> </ul> </div> </div>
61− <span class=\\"story-meta-item story-source\\"> <a
62− itemprop=\\"publisher copyrightHolder sourceOrganization provider\\"
63− class=\\"story-meta-item-link story-source-link\\"
64− href=\\"/source/thedailybeast.com\\">The Daily Beast </a> </span>
65− <span class=\\"story-meta-item story-tag first-tag\\"> <a
66− itemprop=\\"keywords\\" rel=\\"tag\\"
67− class=\\"story-meta-item-link story-tag-link\\" href=\\"/tag/news\\">News</a>
68− </span> <abbr class=\\"published story-meta-item story-timestamp\\"
69− title=\\"2014-10-18 14:53:45\\"> <time datetime=\\"2014-10-18 14:53:45\\">18 Oct 2014</time>
70− </abbr> </div> </header> </div> <ul class=\\"story-actions\\"> <li
71− class=\\"story-action story-action-digg btn-story-action-container\\">
72− <a class=\\"target digg-1sRANah\\" href=\\"#\\">Digg</a></li> <li
73− class=\\"story-action story-action-save btn-story-action-container\\">
74− <a class=\\"target save-1sRANah\\" href=\\"#\\">Save</a></li> <li
75− class=\\"story-action story-action-share\\"><a
76− class=\\"target share-facebook\\" href=\\"https://www.facebook.com/\\">Facebook</a></li>
77− <li class=\\"story-action story-action-share\\"><a class=\\"target share-twitter\\"
78− href=\\"https://twitter.com/\\">Twitter</a></li> </ul> </article>"
79− }
80− """
81− json = json.replace('\r\n', '').replace('\n', '').replace('\r', '')
82− response = mock.Mock(text=json)
83− results = digg.response(response)
84− self.assertEqual(type(results), list)
85− self.assertEqual(len(results), 1)
86− self.assertEqual(results[0]['title'], 'Title of article')
87− self.assertEqual(results[0]['url'], 'http://url.of.link')
88− self.assertEqual(results[0]['thumbnail'], 'http://url.of.image.jpeg')
89− self.assertEqual(results[0]['content'], '')
90−
91− json = """
92− {
93− "status": "error",
94− "num": 10,
95− "next_position": 20
96− }
97− """
98− response = mock.Mock(text=json)
99− results = digg.response(response)
100− self.assertEqual(type(results), list)
101− self.assertEqual(len(results), 0)
tests/unit/engines/test_startpage.py+25−98
@@ -18,12 +18,9 @@ class TestStartpageEngine(SearxTestCase):
1818 self.assertIn('data', params)
1919 self.assertIn('query', params['data'])
2020 self.assertIn(query, params['data']['query'])
21− self.assertIn('with_language', params['data'])
22− self.assertIn('lang_fr', params['data']['with_language'])
2321
2422 dicto['language'] = 'all'
2523 params = startpage.request(query, dicto)
26− self.assertNotIn('with_language', params['data'])
2724
2825 def test_response(self):
2926 self.assertRaises(AttributeError, startpage.response, None)
@@ -35,33 +32,32 @@ class TestStartpageEngine(SearxTestCase):
3532 self.assertEqual(startpage.response(response), [])
3633
3734 html = """
38− <li class="search-result search-item">
39− <h3>
40− <a href='http://this.should.be.the.link/' id='title_2' name='title_2' >
41− This should be the title
35+<div class="w-gl__result">
36+ <a
37+ class="w-gl__result-title"
38+ href="http://this.should.be.the.link/"
39+ data-onw="1"
40+ rel="noopener noreferrer"
41+ target="_blank">
42+
43+ <h3>This should be the title</h3>
4244 </a>
43− <span id='title_stars_2' name='title_stars_2'> </span>
44− </h3>
45− <p class="search-item__body">
46− This should be the content.
47− </p>
48− <p>
49− <span class='url'>www.speed<b>test</b>.net/fr/
50− </span>
51− -
52− <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
53− class='proxy'>
54− Navigation avec Ixquick Proxy
55− </A>
56− -
57− <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
58− &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
59− &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
60− Mis en surbrillance
61− </A>
62− </p>
63− </li>
64− """
45+ <div class="w-gl__result-second-line-container">
46+ <div class="w-gl__result-url-container">
47+ <a
48+ class="w-gl__result-url"
49+ href="http://this.should.be.the.link/"
50+ rel="noopener noreferrer"
51+ target="_blank">https://www.cnbc.com/2019/10/12/dj-zedd-banned-in-china-for-liking-a-south-park-tweet.html</a>
52+ </div>
53+ <a
54+ class="w-gl__anonymous-view-url"
55+ href="https://eu-browse.startpage.com/do/proxy?ep=556b554d576b6f5054554546423167764b5445616455554d5342675441774659495246304848774f5267385453304941486b5949546c63704e33774f526b705544565647516d4a61554246304847674f4a556f6957415a4f436b455042426b6b4f7a64535a52784a56514a4f45307743446c567250445a4f4c52514e5677554e46776b4b545563704c7931554c5167465467644f42464d4f4255426f4d693152624634525741305845526c595746636b626d67494e42705743466c515252634f4267456e597a7346596b7856435134465345634f564249794b5752785643315863546769515773764a5163494c5877505246315865456f5141426b4f41774167596d6c5a4e30395758773442465251495677596c624770665a6b786344466b4151455663425249794d6a78525a55554157516f4342556766526b51314b57514e&amp;ek=4q58686o5047786n6343527259445247576p6o38&amp;ekdata=84abd523dc13cba5c65164d04d7d7263"
56+ target="_blank">Anonymous View</a>
57+ </div>
58+ <p class="w-gl__description">This should be the content.</p>
59+ </div>
60+ """ # noqa
6561 response = mock.Mock(text=html.encode('utf-8'))
6662 results = startpage.response(response)
6763 self.assertEqual(type(results), list)
@@ -69,72 +65,3 @@ class TestStartpageEngine(SearxTestCase):
6965 self.assertEqual(results[0]['title'], 'This should be the title')
7066 self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
7167 self.assertEqual(results[0]['content'], 'This should be the content.')
72−
73− html = """
74− <li class="search-result search-item">
75− <h3>
76− <a href='http://www.google.com/aclk?sa=l&ai=C' id='title_2' name='title_2' >
77− This should be the title
78− </a>
79− <span id='title_stars_2' name='title_stars_2'> </span>
80− </h3>
81− <p class="search-item__body">
82− This should be the content.
83− </p>
84− <p>
85− <span class='url'>www.speed<b>test</b>.net/fr/
86− </span>
87− -
88− <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
89− class='proxy'>
90− Navigation avec Ixquick Proxy
91− </A>
92− -
93− <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
94− &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
95− &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
96− Mis en surbrillance
97− </A>
98− </p>
99− </li>
100− <li class="search-result search-item">
101− <h3>
102− <span id='title_stars_2' name='title_stars_2'> </span>
103− </h3>
104− <p class="search-item__body">
105− This should be the content.
106− </p>
107− <p>
108− <span class='url'>www.speed<b>test</b>.net/fr/
109− </span>
110− </p>
111− </li>
112− <li class="search-result search-item">
113− <h3>
114− <a href='http://this.should.be.the.link/' id='title_2' name='title_2' >
115− This should be the title
116− </a>
117− <span id='title_stars_2' name='title_stars_2'> </span>
118− </h3>
119− <p>
120− <span class='url'>www.speed<b>test</b>.net/fr/
121− </span>
122− -
123− <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
124− class='proxy'>
125− Navigation avec Ixquick Proxy
126− </A>
127− -
128− <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
129− &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
130− &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
131− Mis en surbrillance
132− </A>
133− </p>
134− </li>
135− """
136− response = mock.Mock(text=html.encode('utf-8'))
137− results = startpage.response(response)
138− self.assertEqual(type(results), list)
139− self.assertEqual(len(results), 1)
140− self.assertEqual(results[0]['content'], '')
tests/unit/engines/test_www1x.py−43
@@ -12,46 +12,3 @@ class TestWww1xEngine(SearxTestCase):
1212 self.assertTrue('url' in params)
1313 self.assertTrue(query in params['url'])
1414 self.assertTrue('1x.com' in params['url'])
15−
16− def test_response(self):
17− self.assertRaises(AttributeError, www1x.response, None)
18− self.assertRaises(AttributeError, www1x.response, [])
19− self.assertRaises(AttributeError, www1x.response, '')
20− self.assertRaises(AttributeError, www1x.response, '[]')
21−
22− response = mock.Mock(text='<html></html>')
23− self.assertEqual(www1x.response(response), [])
24− html = """
25− <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE characters
26− [
27− <!ELEMENT characters (character*) >
28− <!ELEMENT character (#PCDATA ) >
29−
30− <!ENTITY iexcl "&#161;" >
31− <!ENTITY cent "&#162;" >
32− <!ENTITY pound "&#163;" >
33− ]
34− ><root><searchresult><![CDATA[<table border="0" cellpadding="0" cellspacing="0" width="100%">
35− <tr>
36− <td style="min-width: 220px;" valign="top">
37− <div style="font-size: 30px; margin: 0px 0px 20px 0px;">Photos</div>
38− <div>
39− <a href="/photo/123456" class="dynamiclink">
40−<img border="0" class="searchresult" src="/images/user/testimage-123456.jpg" style="width: 125px; height: 120px;">
41− </a>
42− <a title="sjoerd lammers street photography" href="/member/sjoerdlammers" class="dynamiclink">
43−<img border="0" class="searchresult" src="/images/profile/60c48b394c677d2fa4d9e7d263aabf44-square.jpg">
44− </a>
45− </div>
46− </td>
47− </table>
48− ]]></searchresult></root>
49− """
50− response = mock.Mock(text=html)
51− results = www1x.response(response)
52− self.assertEqual(type(results), list)
53− self.assertEqual(len(results), 1)
54− self.assertEqual(results[0]['url'], 'https://1x.com/photo/123456')
55− self.assertEqual(results[0]['thumbnail_src'], 'https://1x.com/images/user/testimage-123456.jpg')
56− self.assertEqual(results[0]['content'], '')
57− self.assertEqual(results[0]['template'], 'images.html')