diff --git a/sources/.gitignore b/sources/.gitignore index 105f019..cd6d7e9 100644 --- a/sources/.gitignore +++ b/sources/.gitignore @@ -1,4 +1,5 @@ .coverage +coverage/ .installed.cfg engines.cfg env diff --git a/sources/.travis.yml b/sources/.travis.yml index 3bef5e5..65f8ef2 100644 --- a/sources/.travis.yml +++ b/sources/.travis.yml @@ -16,11 +16,10 @@ install: - ./manage.sh update_dev_packages - pip install coveralls script: - - ./manage.sh pep8_check - ./manage.sh styles - ./manage.sh grunt_build + - ./manage.sh tests - ./manage.sh py_test_coverage - - ./manage.sh robot_tests after_success: coveralls notifications: diff --git a/sources/AUTHORS.rst b/sources/AUTHORS.rst index 5bc6807..505b28e 100644 --- a/sources/AUTHORS.rst +++ b/sources/AUTHORS.rst @@ -1,4 +1,4 @@ -Searx was created by Adam Tauber and is maintained by Adam Tauber and Alexandre Flament. +Searx was created by Adam Tauber and is maintained by Adam Tauber, Alexandre Flament and Noémi Ványi. Major contributing authors: @@ -7,6 +7,7 @@ Major contributing authors: - Thomas Pointhuber - Alexandre Flament `@dalf `_ - @Cqoicebordel +- Noémi Ványi People who have submitted patches/translates, reported bugs, consulted features or generally made searx better: @@ -39,15 +40,21 @@ generally made searx better: - @underr - Emmanuel Benazera - @GreenLunar -- Noemi Vanyi - Kang-min Liu - Kirill Isakov - Guilhem Bonnefille - Marc Abonce Seguin - - @jibe-b - Christian Pietsch @pietsch - @Maxqia - Ashutosh Das @pyprism - YuLun Shih @imZack - Dmitry Mikhirev @mikhirev +- David A Roberts `@davidar `_ +- Jan Verbeek @blyxxyz +- Ammar Najjar @ammarnajjar +- @stepshal +- François Revol @mmuman +- marc @a01200356 +- Harry Wood @harry-wood +- Thomas Renard @threnard diff --git a/sources/CHANGELOG.rst b/sources/CHANGELOG.rst index 8907ab4..999570e 100644 --- a/sources/CHANGELOG.rst +++ b/sources/CHANGELOG.rst @@ -1,3 +1,38 @@ +0.10.0 2016.09.06 +================= + +- New engines + + - Archive.is (general) + - INA (videos) + - Scanr (science) + - Google Scholar (science) + - Crossref (science) + - Openrepos (files) + - Microsoft Academic Search Engine (science) + - Hoogle (it) + - Diggbt (files) + - Dictzone (general - dictionary) + - Translated (general - translation) +- New Plugins + + - Infinite scroll on results page + - DOAI rewrite +- Full theme redesign +- Display the number of results +- Filter searches by date range +- Instance config API endpoint +- Dependency version updates +- Socks proxy support for outgoing requests +- 404 page + + +News +~~~~ + +@kvch joined the maintainer team + + 0.9.0 2016.05.24 ================ @@ -36,6 +71,7 @@ - Multilingual autocompleter - Qwant autocompleter backend + 0.8.1 2015.12.22 ================ diff --git a/sources/README.rst b/sources/README.rst index 6563fe8..a0bb12f 100644 --- a/sources/README.rst +++ b/sources/README.rst @@ -15,7 +15,7 @@ Installation ~~~~~~~~~~~~ - clone source: - ``git clone git@github.com:asciimoo/searx.git && cd searx`` + ``git clone https://github.com/asciimoo/searx.git && cd searx`` - install dependencies: ``./manage.sh update_packages`` - edit your `settings.yml `__ diff --git a/sources/examples/basic_engine.py b/sources/examples/basic_engine.py index d786564..c7d02af 100644 --- a/sources/examples/basic_engine.py +++ b/sources/examples/basic_engine.py @@ -1,5 +1,6 @@ -categories = ['general'] # optional +categories = ['general'] # optional + def request(query, params): '''pre-request callback @@ -22,4 +23,3 @@ def response(resp): resp: requests response object ''' return [{'url': '', 'title': '', 'content': ''}] - diff --git a/sources/manage.sh b/sources/manage.sh index 0a21f0e..75ba320 100755 --- a/sources/manage.sh +++ b/sources/manage.sh @@ -1,6 +1,6 @@ #!/bin/sh -BASE_DIR=$(dirname `readlink -f $0`) +BASE_DIR=$(dirname "`readlink -f "$0"`") PYTHONPATH=$BASE_DIR SEARX_DIR="$BASE_DIR/searx" ACTION=$1 @@ -58,7 +58,8 @@ styles() { build_style themes/courgette/less/style.less themes/courgette/css/style.css build_style themes/courgette/less/style-rtl.less themes/courgette/css/style-rtl.css build_style less/bootstrap/bootstrap.less css/bootstrap.min.css - build_style themes/oscar/less/oscar/oscar.less themes/oscar/css/oscar.min.css + build_style themes/oscar/less/pointhi/oscar.less themes/oscar/css/pointhi.min.css + build_style themes/oscar/less/logicodev/oscar.less themes/oscar/css/logicodev.min.css build_style themes/pix-art/less/style.less themes/pix-art/css/style.css } diff --git a/sources/requirements-dev.txt b/sources/requirements-dev.txt index 38be888..580ef63 100644 --- a/sources/requirements-dev.txt +++ b/sources/requirements-dev.txt @@ -1,8 +1,8 @@ -babel==2.2.0 -mock==1.0.1 +babel==2.3.4 +mock==2.0.0 nose2[coverage-plugin] pep8==1.7.0 -plone.testing==4.0.15 +plone.testing==5.0.0 robotframework-selenium2library==1.7.4 robotsuite==1.7.0 transifex-client==0.11 diff --git a/sources/requirements.txt b/sources/requirements.txt index 80c08a4..029c0cf 100644 --- a/sources/requirements.txt +++ b/sources/requirements.txt @@ -1,12 +1,12 @@ -certifi==2015.11.20.1 -flask==0.10.1 -flask-babel==0.9 -lxml==3.5.0 -ndg-httpsclient==0.4.0 +certifi==2016.2.28 +flask==0.11.1 +flask-babel==0.11.1 +lxml==3.6.0 +ndg-httpsclient==0.4.1 pyasn1==0.1.9 pyasn1-modules==0.0.8 -pygments==2.0.2 +pygments==2.1.3 pyopenssl==0.15.1 -python-dateutil==2.4.2 +python-dateutil==2.5.3 pyyaml==3.11 -requests==2.9.1 +requests[socks]==2.10.0 diff --git a/sources/searx/engines/__init__.py b/sources/searx/engines/__init__.py index 6d50667..782b622 100644 --- a/sources/searx/engines/__init__.py +++ b/sources/searx/engines/__init__.py @@ -19,7 +19,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. from os.path import realpath, dirname, splitext, join import sys from imp import load_source -from flask.ext.babel import gettext +from flask_babel import gettext from operator import itemgetter from searx import settings from searx import logger @@ -42,7 +42,8 @@ engine_default_args = {'paging': False, 'shortcut': '-', 'disabled': False, 'suspend_end_time': 0, - 'continuous_errors': 0} + 'continuous_errors': 0, + 'time_range_support': False} def load_module(filename): @@ -57,7 +58,11 @@ def load_module(filename): def load_engine(engine_data): engine_name = engine_data['engine'] - engine = load_module(engine_name + '.py') + try: + engine = load_module(engine_name + '.py') + except: + logger.exception('Cannot load engine "{}"'.format(engine_name)) + return None for param_name in engine_data: if param_name == 'engine': @@ -199,4 +204,5 @@ if 'engines' not in settings or not settings['engines']: for engine_data in settings['engines']: engine = load_engine(engine_data) - engines[engine.name] = engine + if engine is not None: + engines[engine.name] = engine diff --git a/sources/searx/engines/archlinux.py b/sources/searx/engines/archlinux.py index 84e0d0f..b846934 100644 --- a/sources/searx/engines/archlinux.py +++ b/sources/searx/engines/archlinux.py @@ -34,6 +34,7 @@ def locale_to_lang_code(locale): locale = locale.split('_')[0] return locale + # wikis for some languages were moved off from the main site, we need to make # requests to correct URLs to be able to get results in those languages lang_urls = { @@ -70,6 +71,7 @@ def get_lang_urls(language): return lang_urls[language] return lang_urls['all'] + # Language names to build search requests for # those languages which are hosted on the main site. main_langs = { diff --git a/sources/searx/engines/btdigg.py b/sources/searx/engines/btdigg.py index c2b22f0..ea6baf1 100644 --- a/sources/searx/engines/btdigg.py +++ b/sources/searx/engines/btdigg.py @@ -16,6 +16,7 @@ from urllib import quote from lxml import html from operator import itemgetter from searx.engines.xpath import extract_text +from searx.utils import get_torrent_size # engine dependent config categories = ['videos', 'music', 'files'] @@ -68,20 +69,7 @@ def response(resp): leech = 0 # convert filesize to byte if possible - try: - filesize = float(filesize) - - # convert filesize to byte - if filesize_multiplier == 'TB': - filesize = int(filesize * 1024 * 1024 * 1024 * 1024) - elif filesize_multiplier == 'GB': - filesize = int(filesize * 1024 * 1024 * 1024) - elif filesize_multiplier == 'MB': - filesize = int(filesize * 1024 * 1024) - elif filesize_multiplier == 'KB': - filesize = int(filesize * 1024) - except: - filesize = None + filesize = get_torrent_size(filesize, filesize_multiplier) # convert files to int if possible if files.isdigit(): diff --git a/sources/searx/engines/currency_convert.py b/sources/searx/engines/currency_convert.py index b0ffb49..bc839cf 100644 --- a/sources/searx/engines/currency_convert.py +++ b/sources/searx/engines/currency_convert.py @@ -9,7 +9,7 @@ categories = [] url = 'https://download.finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s={query}=X' weight = 100 -parser_re = re.compile(u'.*?(\d+(?:\.\d+)?) ([^.0-9]+) (?:in|to) ([^.0-9]+)', re.I) # noqa +parser_re = re.compile(u'.*?(\\d+(?:\\.\\d+)?) ([^.0-9]+) (?:in|to) ([^.0-9]+)', re.I) # noqa db = 1 diff --git a/sources/searx/engines/deviantart.py b/sources/searx/engines/deviantart.py index 135aeb3..d893fc7 100644 --- a/sources/searx/engines/deviantart.py +++ b/sources/searx/engines/deviantart.py @@ -13,7 +13,6 @@ """ from urllib import urlencode -from urlparse import urljoin from lxml import html import re from searx.engines.xpath import extract_text @@ -21,10 +20,16 @@ from searx.engines.xpath import extract_text # engine dependent config categories = ['images'] paging = True +time_range_support = True # search-url base_url = 'https://www.deviantart.com/' search_url = base_url + 'browse/all/?offset={offset}&{query}' +time_range_url = '&order={range}' + +time_range_dict = {'day': 11, + 'week': 14, + 'month': 15} # do search-request @@ -33,6 +38,8 @@ def request(query, params): params['url'] = search_url.format(offset=offset, query=urlencode({'q': query})) + if params['time_range'] in time_range_dict: + params['url'] += time_range_url.format(range=time_range_dict[params['time_range']]) return params @@ -47,14 +54,13 @@ def response(resp): dom = html.fromstring(resp.text) - regex = re.compile('\/200H\/') + regex = re.compile(r'\/200H\/') # parse results - for result in dom.xpath('//div[contains(@class, "tt-a tt-fh")]'): - link = result.xpath('.//a[contains(@class, "thumb")]')[0] - url = urljoin(base_url, link.attrib.get('href')) - title_links = result.xpath('.//span[@class="details"]//a[contains(@class, "t")]') - title = extract_text(title_links[0]) + for result in dom.xpath('.//span[@class="thumb wide"]'): + link = result.xpath('.//a[@class="torpedo-thumb-link"]')[0] + url = link.attrib.get('href') + title = extract_text(result.xpath('.//span[@class="title"]')) thumbnail_src = link.xpath('.//img')[0].attrib.get('src') img_src = regex.sub('/', thumbnail_src) diff --git a/sources/searx/engines/dictzone.py b/sources/searx/engines/dictzone.py new file mode 100644 index 0000000..9765d5f --- /dev/null +++ b/sources/searx/engines/dictzone.py @@ -0,0 +1,69 @@ +""" + Dictzone + + @website https://dictzone.com/ + @provide-api no + @using-api no + @results HTML (using search portal) + @stable no (HTML can change) + @parse url, title, content +""" + +import re +from urlparse import urljoin +from lxml import html +from cgi import escape +from searx.utils import is_valid_lang + +categories = ['general'] +url = u'http://dictzone.com/{from_lang}-{to_lang}-dictionary/{query}' +weight = 100 + +parser_re = re.compile(u'.*?([a-z]+)-([a-z]+) ([^ ]+)$', re.I) +results_xpath = './/table[@id="r"]/tr' + + +def request(query, params): + m = parser_re.match(unicode(query, 'utf8')) + if not m: + return params + + from_lang, to_lang, query = m.groups() + + from_lang = is_valid_lang(from_lang) + to_lang = is_valid_lang(to_lang) + + if not from_lang or not to_lang: + return params + + params['url'] = url.format(from_lang=from_lang[2], + to_lang=to_lang[2], + query=query) + + return params + + +def response(resp): + results = [] + + dom = html.fromstring(resp.text) + + for k, result in enumerate(dom.xpath(results_xpath)[1:]): + try: + from_result, to_results_raw = result.xpath('./td') + except: + continue + + to_results = [] + for to_result in to_results_raw.xpath('./p/a'): + t = to_result.text_content() + if t.strip(): + to_results.append(to_result.text_content()) + + results.append({ + 'url': urljoin(resp.url, '?%d' % k), + 'title': escape(from_result.text_content()), + 'content': escape('; '.join(to_results)) + }) + + return results diff --git a/sources/searx/engines/digbt.py b/sources/searx/engines/digbt.py new file mode 100644 index 0000000..c35327e --- /dev/null +++ b/sources/searx/engines/digbt.py @@ -0,0 +1,58 @@ +""" + DigBT (Videos, Music, Files) + + @website https://digbt.org + @provide-api no + + @using-api no + @results HTML (using search portal) + @stable no (HTML can change) + @parse url, title, content, magnetlink +""" + +from urlparse import urljoin +from lxml import html +from searx.engines.xpath import extract_text +from searx.utils import get_torrent_size + +categories = ['videos', 'music', 'files'] +paging = True + +URL = 'https://digbt.org' +SEARCH_URL = URL + '/search/{query}-time-{pageno}' +FILESIZE = 3 +FILESIZE_MULTIPLIER = 4 + + +def request(query, params): + params['url'] = SEARCH_URL.format(query=query, pageno=params['pageno']) + + return params + + +def response(resp): + dom = html.fromstring(resp.content) + search_res = dom.xpath('.//td[@class="x-item"]') + + if not search_res: + return list() + + results = list() + for result in search_res: + url = urljoin(URL, result.xpath('.//a[@title]/@href')[0]) + title = result.xpath('.//a[@title]/text()')[0] + content = extract_text(result.xpath('.//div[@class="files"]')) + files_data = extract_text(result.xpath('.//div[@class="tail"]')).split() + filesize = get_torrent_size(files_data[FILESIZE], files_data[FILESIZE_MULTIPLIER]) + magnetlink = result.xpath('.//div[@class="tail"]//a[@class="title"]/@href')[0] + + results.append({'url': url, + 'title': title, + 'content': content, + 'filesize': filesize, + 'magnetlink': magnetlink, + 'seed': 'N/A', + 'leech': 'N/A', + 'template': 'torrent.html'}) + + return results diff --git a/sources/searx/engines/duckduckgo.py b/sources/searx/engines/duckduckgo.py index 373ce1b..2153492 100644 --- a/sources/searx/engines/duckduckgo.py +++ b/sources/searx/engines/duckduckgo.py @@ -11,21 +11,26 @@ @parse url, title, content @todo rewrite to api - @todo language support - (the current used site does not support language-change) """ from urllib import urlencode from lxml.html import fromstring from searx.engines.xpath import extract_text +from searx.languages import language_codes # engine dependent config categories = ['general'] paging = True language_support = True +time_range_support = True # search-url url = 'https://duckduckgo.com/html?{query}&s={offset}' +time_range_url = '&df={range}' + +time_range_dict = {'day': 'd', + 'week': 'w', + 'month': 'm'} # specific xpath variables result_xpath = '//div[@class="result results_links results_links_deep web-result "]' # noqa @@ -39,13 +44,31 @@ def request(query, params): offset = (params['pageno'] - 1) * 30 if params['language'] == 'all': - locale = 'en-us' + locale = None else: - locale = params['language'].replace('_', '-').lower() + locale = params['language'].split('_') + if len(locale) == 2: + # country code goes first + locale = locale[1].lower() + '-' + locale[0].lower() + else: + # tries to get a country code from language + locale = locale[0].lower() + lang_codes = [x[0] for x in language_codes] + for lc in lang_codes: + lc = lc.split('_') + if locale == lc[0]: + locale = lc[1].lower() + '-' + lc[0].lower() + break - params['url'] = url.format( - query=urlencode({'q': query, 'kl': locale}), - offset=offset) + if locale: + params['url'] = url.format( + query=urlencode({'q': query, 'kl': locale}), offset=offset) + else: + params['url'] = url.format( + query=urlencode({'q': query}), offset=offset) + + if params['time_range'] in time_range_dict: + params['url'] += time_range_url.format(range=time_range_dict[params['time_range']]) return params diff --git a/sources/searx/engines/filecrop.py b/sources/searx/engines/filecrop.py index 89dc776..71665bd 100644 --- a/sources/searx/engines/filecrop.py +++ b/sources/searx/engines/filecrop.py @@ -8,6 +8,7 @@ paging = True class FilecropResultParser(HTMLParser): + def __init__(self): HTMLParser.__init__(self) self.__start_processing = False diff --git a/sources/searx/engines/google.py b/sources/searx/engines/google.py index 6018ad1..ea93bc9 100644 --- a/sources/searx/engines/google.py +++ b/sources/searx/engines/google.py @@ -24,6 +24,7 @@ categories = ['general'] paging = True language_support = True use_locale_domain = True +time_range_support = True # based on https://en.wikipedia.org/wiki/List_of_Google_domains and tests default_hostname = 'www.google.com' @@ -92,6 +93,11 @@ search_url = ('https://{hostname}' + search_path + '?{query}&start={offset}&gws_rd=cr&gbv=1&lr={lang}&ei=x') +time_range_search = "&tbs=qdr:{range}" +time_range_dict = {'day': 'd', + 'week': 'w', + 'month': 'm'} + # other URLs map_hostname_start = 'maps.google.' maps_path = '/maps' @@ -179,6 +185,8 @@ def request(query, params): query=urlencode({'q': query}), hostname=google_hostname, lang=url_lang) + if params['time_range'] in time_range_dict: + params['url'] += time_range_search.format(range=time_range_dict[params['time_range']]) params['headers']['Accept-Language'] = language params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' @@ -300,9 +308,9 @@ def parse_map_detail(parsed_url, result, google_hostname): results = [] # try to parse the geoloc - m = re.search('@([0-9\.]+),([0-9\.]+),([0-9]+)', parsed_url.path) + m = re.search(r'@([0-9\.]+),([0-9\.]+),([0-9]+)', parsed_url.path) if m is None: - m = re.search('ll\=([0-9\.]+),([0-9\.]+)\&z\=([0-9]+)', parsed_url.query) + m = re.search(r'll\=([0-9\.]+),([0-9\.]+)\&z\=([0-9]+)', parsed_url.query) if m is not None: # geoloc found (ignored) diff --git a/sources/searx/engines/google_images.py b/sources/searx/engines/google_images.py index efe4681..77bdc13 100644 --- a/sources/searx/engines/google_images.py +++ b/sources/searx/engines/google_images.py @@ -11,7 +11,6 @@ """ from urllib import urlencode -from urlparse import parse_qs from json import loads from lxml import html @@ -19,24 +18,38 @@ from lxml import html categories = ['images'] paging = True safesearch = True +time_range_support = True +number_of_results = 100 search_url = 'https://www.google.com/search'\ '?{query}'\ + '&asearch=ichunk'\ + '&async=_id:rg_s,_pms:s'\ '&tbm=isch'\ - '&ijn=1'\ - '&start={offset}' + '&yv=2'\ + '&{search_options}' +time_range_attr = "qdr:{range}" +time_range_dict = {'day': 'd', + 'week': 'w', + 'month': 'm'} # do search-request def request(query, params): - offset = (params['pageno'] - 1) * 100 - params['url'] = search_url.format(query=urlencode({'q': query}), - offset=offset, - safesearch=safesearch) + search_options = { + 'ijn': params['pageno'] - 1, + 'start': (params['pageno'] - 1) * number_of_results + } + + if params['time_range'] in time_range_dict: + search_options['tbs'] = time_range_attr.format(range=time_range_dict[params['time_range']]) if safesearch and params['safesearch']: - params['url'] += '&' + urlencode({'safe': 'active'}) + search_options['safe'] = 'on' + + params['url'] = search_url.format(query=urlencode({'q': query}), + search_options=urlencode(search_options)) return params @@ -45,12 +58,17 @@ def request(query, params): def response(resp): results = [] - dom = html.fromstring(resp.text) + g_result = loads(resp.text) + + dom = html.fromstring(g_result[1][1]) # parse results for result in dom.xpath('//div[@data-ved]'): - metadata = loads(result.xpath('./div[@class="rg_meta"]/text()')[0]) + try: + metadata = loads(''.join(result.xpath('./div[@class="rg_meta"]/text()'))) + except: + continue thumbnail_src = metadata['tu'] diff --git a/sources/searx/engines/ina.py b/sources/searx/engines/ina.py new file mode 100644 index 0000000..86a3978 --- /dev/null +++ b/sources/searx/engines/ina.py @@ -0,0 +1,83 @@ +# INA (Videos) +# +# @website https://www.ina.fr/ +# @provide-api no +# +# @using-api no +# @results HTML (using search portal) +# @stable no (HTML can change) +# @parse url, title, content, publishedDate, thumbnail +# +# @todo set content-parameter with correct data +# @todo embedded (needs some md5 from video page) + +from json import loads +from urllib import urlencode +from lxml import html +from HTMLParser import HTMLParser +from searx.engines.xpath import extract_text +from dateutil import parser + +# engine dependent config +categories = ['videos'] +paging = True +page_size = 48 + +# search-url +base_url = 'https://www.ina.fr' +search_url = base_url + '/layout/set/ajax/recherche/result?autopromote=&hf={ps}&b={start}&type=Video&r=&{query}' + +# specific xpath variables +results_xpath = '//div[contains(@class,"search-results--list")]/div[@class="media"]' +url_xpath = './/a/@href' +title_xpath = './/h3[@class="h3--title media-heading"]' +thumbnail_xpath = './/img/@src' +publishedDate_xpath = './/span[@class="broadcast"]' +content_xpath = './/p[@class="media-body__summary"]' + + +# do search-request +def request(query, params): + params['url'] = search_url.format(ps=page_size, + start=params['pageno'] * page_size, + query=urlencode({'q': query})) + + return params + + +# get response from search-request +def response(resp): + results = [] + + # we get html in a JSON container... + response = loads(resp.text) + if "content" not in response: + return [] + dom = html.fromstring(response["content"]) + p = HTMLParser() + + # parse results + for result in dom.xpath(results_xpath): + videoid = result.xpath(url_xpath)[0] + url = base_url + videoid + title = p.unescape(extract_text(result.xpath(title_xpath))) + thumbnail = extract_text(result.xpath(thumbnail_xpath)[0]) + if thumbnail[0] == '/': + thumbnail = base_url + thumbnail + d = extract_text(result.xpath(publishedDate_xpath)[0]) + d = d.split('/') + # force ISO date to avoid wrong parsing + d = "%s-%s-%s" % (d[2], d[1], d[0]) + publishedDate = parser.parse(d) + content = extract_text(result.xpath(content_xpath)) + + # append result + results.append({'url': url, + 'title': title, + 'content': content, + 'template': 'videos.html', + 'publishedDate': publishedDate, + 'thumbnail': thumbnail}) + + # return results + return results diff --git a/sources/searx/engines/json_engine.py b/sources/searx/engines/json_engine.py index 5525b7f..4604c3c 100644 --- a/sources/searx/engines/json_engine.py +++ b/sources/searx/engines/json_engine.py @@ -6,7 +6,16 @@ search_url = None url_query = None content_query = None title_query = None -# suggestion_xpath = '' +suggestion_query = '' +results_query = '' + +# parameters for engines with paging support +# +# number of results on each page +# (only needed if the site requires not a page number, but an offset) +page_size = 1 +# number of the first page (usually 0 or 1) +first_page_num = 1 def iterate(iterable): @@ -69,19 +78,36 @@ def query(data, query_string): def request(query, params): query = urlencode({'q': query})[2:] - params['url'] = search_url.format(query=query) + + fp = {'query': query} + if paging and search_url.find('{pageno}') >= 0: + fp['pageno'] = (params['pageno'] - 1) * page_size + first_page_num + + params['url'] = search_url.format(**fp) params['query'] = query + return params def response(resp): results = [] - json = loads(resp.text) + if results_query: + for result in query(json, results_query)[0]: + url = query(result, url_query)[0] + title = query(result, title_query)[0] + content = query(result, content_query)[0] + results.append({'url': url, 'title': title, 'content': content}) + else: + for url, title, content in zip( + query(json, url_query), + query(json, title_query), + query(json, content_query) + ): + results.append({'url': url, 'title': title, 'content': content}) - urls = query(json, url_query) - contents = query(json, content_query) - titles = query(json, title_query) - for url, title, content in zip(urls, titles, contents): - results.append({'url': url, 'title': title, 'content': content}) + if not suggestion_query: + return results + for suggestion in query(json, suggestion_query): + results.append({'suggestion': suggestion}) return results diff --git a/sources/searx/engines/scanr_structures.py b/sources/searx/engines/scanr_structures.py new file mode 100644 index 0000000..ad78155 --- /dev/null +++ b/sources/searx/engines/scanr_structures.py @@ -0,0 +1,78 @@ +""" + ScanR Structures (Science) + + @website https://scanr.enseignementsup-recherche.gouv.fr + @provide-api yes (https://scanr.enseignementsup-recherche.gouv.fr/api/swagger-ui.html) + + @using-api yes + @results JSON + @stable yes + @parse url, title, content, img_src +""" + +from urllib import urlencode +from json import loads, dumps +from dateutil import parser +from searx.utils import html_to_text + +# engine dependent config +categories = ['science'] +paging = True +page_size = 20 + +# search-url +url = 'https://scanr.enseignementsup-recherche.gouv.fr/' +search_url = url + 'api/structures/search' + + +# do search-request +def request(query, params): + + params['url'] = search_url + params['method'] = 'POST' + params['headers']['Content-type'] = "application/json" + params['data'] = dumps({"query": query, + "searchField": "ALL", + "sortDirection": "ASC", + "sortOrder": "RELEVANCY", + "page": params['pageno'], + "pageSize": page_size}) + + return params + + +# get response from search-request +def response(resp): + results = [] + + search_res = loads(resp.text) + + # return empty array if there are no results + if search_res.get('total') < 1: + return [] + + # parse results + for result in search_res['results']: + if 'id' not in result: + continue + + # is it thumbnail or img_src?? + thumbnail = None + if 'logo' in result: + thumbnail = result['logo'] + if thumbnail[0] == '/': + thumbnail = url + thumbnail + + content = None + if 'highlights' in result: + content = result['highlights'][0]['value'] + + # append result + results.append({'url': url + 'structure/' + result['id'], + 'title': result['label'], + # 'thumbnail': thumbnail, + 'img_src': thumbnail, + 'content': html_to_text(content)}) + + # return results + return results diff --git a/sources/searx/engines/soundcloud.py b/sources/searx/engines/soundcloud.py index ac23c1e..62b03ac 100644 --- a/sources/searx/engines/soundcloud.py +++ b/sources/searx/engines/soundcloud.py @@ -57,6 +57,7 @@ def get_client_id(): logger.warning("Unable to fetch guest client_id from SoundCloud, check parser!") return "" + # api-key guest_client_id = get_client_id() diff --git a/sources/searx/engines/startpage.py b/sources/searx/engines/startpage.py index 52dd0b9..d8b702c 100644 --- a/sources/searx/engines/startpage.py +++ b/sources/searx/engines/startpage.py @@ -68,15 +68,15 @@ def response(resp): url = link.attrib.get('href') # block google-ad url's - if re.match("^http(s|)://(www\.)?google\.[a-z]+/aclk.*$", url): + if re.match(r"^http(s|)://(www\.)?google\.[a-z]+/aclk.*$", url): continue # block startpage search url's - if re.match("^http(s|)://(www\.)?startpage\.com/do/search\?.*$", url): + if re.match(r"^http(s|)://(www\.)?startpage\.com/do/search\?.*$", url): continue # block ixquick search url's - if re.match("^http(s|)://(www\.)?ixquick\.com/do/search\?.*$", url): + if re.match(r"^http(s|)://(www\.)?ixquick\.com/do/search\?.*$", url): continue title = escape(extract_text(link)) @@ -89,7 +89,7 @@ def response(resp): published_date = None # check if search result starts with something like: "2 Sep 2014 ... " - if re.match("^([1-9]|[1-2][0-9]|3[0-1]) [A-Z][a-z]{2} [0-9]{4} \.\.\. ", content): + if re.match(r"^([1-9]|[1-2][0-9]|3[0-1]) [A-Z][a-z]{2} [0-9]{4} \.\.\. ", content): date_pos = content.find('...') + 4 date_string = content[0:date_pos - 5] published_date = parser.parse(date_string, dayfirst=True) @@ -98,7 +98,7 @@ def response(resp): content = content[date_pos:] # check if search result starts with something like: "5 days ago ... " - elif re.match("^[0-9]+ days? ago \.\.\. ", content): + elif re.match(r"^[0-9]+ days? ago \.\.\. ", content): date_pos = content.find('...') + 4 date_string = content[0:date_pos - 5] diff --git a/sources/searx/engines/swisscows.py b/sources/searx/engines/swisscows.py index 864436a..1a94ed6 100644 --- a/sources/searx/engines/swisscows.py +++ b/sources/searx/engines/swisscows.py @@ -25,10 +25,10 @@ base_url = 'https://swisscows.ch/' search_string = '?{query}&page={page}' # regex -regex_json = re.compile('initialData: {"Request":(.|\n)*},\s*environment') -regex_json_remove_start = re.compile('^initialData:\s*') -regex_json_remove_end = re.compile(',\s*environment$') -regex_img_url_remove_start = re.compile('^https?://i\.swisscows\.ch/\?link=') +regex_json = re.compile(r'initialData: {"Request":(.|\n)*},\s*environment') +regex_json_remove_start = re.compile(r'^initialData:\s*') +regex_json_remove_end = re.compile(r',\s*environment$') +regex_img_url_remove_start = re.compile(r'^https?://i\.swisscows\.ch/\?link=') # do search-request diff --git a/sources/searx/engines/tokyotoshokan.py b/sources/searx/engines/tokyotoshokan.py index 17e8e21..e2990e1 100644 --- a/sources/searx/engines/tokyotoshokan.py +++ b/sources/searx/engines/tokyotoshokan.py @@ -48,7 +48,7 @@ def response(resp): return [] # regular expression for parsing torrent size strings - size_re = re.compile('Size:\s*([\d.]+)(TB|GB|MB|B)', re.IGNORECASE) + size_re = re.compile(r'Size:\s*([\d.]+)(TB|GB|MB|B)', re.IGNORECASE) # processing the results, two rows at a time for i in xrange(0, len(rows), 2): diff --git a/sources/searx/engines/translated.py b/sources/searx/engines/translated.py new file mode 100644 index 0000000..02047bc --- /dev/null +++ b/sources/searx/engines/translated.py @@ -0,0 +1,65 @@ +""" + MyMemory Translated + + @website https://mymemory.translated.net/ + @provide-api yes (https://mymemory.translated.net/doc/spec.php) + @using-api yes + @results JSON + @stable yes + @parse url, title, content +""" +import re +from cgi import escape +from searx.utils import is_valid_lang + +categories = ['general'] +url = u'http://api.mymemory.translated.net/get?q={query}&langpair={from_lang}|{to_lang}{key}' +web_url = u'http://mymemory.translated.net/en/{from_lang}/{to_lang}/{query}' +weight = 100 + +parser_re = re.compile(u'.*?([a-z]+)-([a-z]+) (.{2,})$', re.I) +api_key = '' + + +def request(query, params): + m = parser_re.match(unicode(query, 'utf8')) + if not m: + return params + + from_lang, to_lang, query = m.groups() + + from_lang = is_valid_lang(from_lang) + to_lang = is_valid_lang(to_lang) + + if not from_lang or not to_lang: + return params + + if api_key: + key_form = '&key=' + api_key + else: + key_form = '' + params['url'] = url.format(from_lang=from_lang[1], + to_lang=to_lang[1], + query=query, + key=key_form) + params['query'] = query + params['from_lang'] = from_lang + params['to_lang'] = to_lang + + return params + + +def response(resp): + results = [] + results.append({ + 'url': escape(web_url.format( + from_lang=resp.search_params['from_lang'][2], + to_lang=resp.search_params['to_lang'][2], + query=resp.search_params['query'])), + 'title': escape('[{0}-{1}] {2}'.format( + resp.search_params['from_lang'][1], + resp.search_params['to_lang'][1], + resp.search_params['query'])), + 'content': escape(resp.json()['responseData']['translatedText']) + }) + return results diff --git a/sources/searx/engines/wikidata.py b/sources/searx/engines/wikidata.py index 8aa2fcd..91040e2 100644 --- a/sources/searx/engines/wikidata.py +++ b/sources/searx/engines/wikidata.py @@ -1,56 +1,86 @@ -import json +# -*- coding: utf-8 -*- +""" + Wikidata + + @website https://wikidata.org + @provide-api yes (https://wikidata.org/w/api.php) + + @using-api partially (most things require scraping) + @results JSON, HTML + @stable no (html can change) + @parse url, infobox +""" from searx import logger from searx.poolrequests import get -from searx.utils import format_date_by_locale +from searx.engines.xpath import extract_text -from datetime import datetime -from dateutil.parser import parse as dateutil_parse +from json import loads +from lxml.html import fromstring from urllib import urlencode - logger = logger.getChild('wikidata') result_count = 1 + +# urls wikidata_host = 'https://www.wikidata.org' +url_search = wikidata_host \ + + '/wiki/Special:ItemDisambiguation?{query}' + wikidata_api = wikidata_host + '/w/api.php' -url_search = wikidata_api \ - + '?action=query&list=search&format=json'\ - + '&srnamespace=0&srprop=sectiontitle&{query}' url_detail = wikidata_api\ - + '?action=wbgetentities&format=json'\ - + '&props=labels%7Cinfo%7Csitelinks'\ - + '%7Csitelinks%2Furls%7Cdescriptions%7Cclaims'\ - + '&{query}' + + '?action=parse&format=json&{query}'\ + + '&redirects=1&prop=text%7Cdisplaytitle%7Clanglinks%7Crevid'\ + + '&disableeditsection=1&disabletidy=1&preview=1§ionpreview=1&disabletoc=1&utf8=1&formatversion=2' + url_map = 'https://www.openstreetmap.org/'\ + '?lat={latitude}&lon={longitude}&zoom={zoom}&layers=M' +url_image = 'https://commons.wikimedia.org/wiki/Special:FilePath/{filename}?width=500&height=400' + +# xpaths +wikidata_ids_xpath = '//div/ul[@class="wikibase-disambiguation"]/li/a/@title' +title_xpath = '//*[contains(@class,"wikibase-title-label")]' +description_xpath = '//div[contains(@class,"wikibase-entitytermsview-heading-description")]' +property_xpath = '//div[@id="{propertyid}"]' +label_xpath = './/div[contains(@class,"wikibase-statementgroupview-property-label")]/a' +url_xpath = './/a[contains(@class,"external free") or contains(@class, "wb-external-id")]' +wikilink_xpath = './/ul[contains(@class,"wikibase-sitelinklistview-listview")]'\ + + '/li[contains(@data-wb-siteid,"{wikiid}")]//a/@href' +property_row_xpath = './/div[contains(@class,"wikibase-statementview")]' +preferred_rank_xpath = './/span[contains(@class,"wikibase-rankselector-preferred")]' +value_xpath = './/div[contains(@class,"wikibase-statementview-mainsnak")]'\ + + '/*/div[contains(@class,"wikibase-snakview-value")]' +language_fallback_xpath = '//sup[contains(@class,"wb-language-fallback-indicator")]' +calendar_name_xpath = './/sup[contains(@class,"wb-calendar-name")]' def request(query, params): + language = params['language'].split('_')[0] + if language == 'all': + language = 'en' + params['url'] = url_search.format( - query=urlencode({'srsearch': query, - 'srlimit': result_count})) + query=urlencode({'label': query, + 'language': language})) return params def response(resp): results = [] - search_res = json.loads(resp.text) - - wikidata_ids = set() - for r in search_res.get('query', {}).get('search', {}): - wikidata_ids.add(r.get('title', '')) + html = fromstring(resp.content) + wikidata_ids = html.xpath(wikidata_ids_xpath) language = resp.search_params['language'].split('_')[0] if language == 'all': language = 'en' - url = url_detail.format(query=urlencode({'ids': '|'.join(wikidata_ids), - 'languages': language + '|en'})) - - htmlresponse = get(url) - jsonresponse = json.loads(htmlresponse.content) - for wikidata_id in wikidata_ids: - results = results + getDetail(jsonresponse, wikidata_id, language, resp.search_params['language']) + # TODO: make requests asynchronous to avoid timeout when result_count > 1 + for wikidata_id in wikidata_ids[:result_count]: + url = url_detail.format(query=urlencode({'page': wikidata_id, + 'uselang': language})) + htmlresponse = get(url) + jsonresponse = loads(htmlresponse.content) + results += getDetail(jsonresponse, wikidata_id, language, resp.search_params['language']) return results @@ -60,124 +90,206 @@ def getDetail(jsonresponse, wikidata_id, language, locale): urls = [] attributes = [] - result = jsonresponse.get('entities', {}).get(wikidata_id, {}) + title = jsonresponse.get('parse', {}).get('displaytitle', {}) + result = jsonresponse.get('parse', {}).get('text', {}) - title = result.get('labels', {}).get(language, {}).get('value', None) - if title is None: - title = result.get('labels', {}).get('en', {}).get('value', None) - if title is None: + if not title or not result: return results - description = result\ - .get('descriptions', {})\ - .get(language, {})\ - .get('value', None) + title = fromstring(title) + for elem in title.xpath(language_fallback_xpath): + elem.getparent().remove(elem) + title = extract_text(title.xpath(title_xpath)) - if description is None: - description = result\ - .get('descriptions', {})\ - .get('en', {})\ - .get('value', '') + result = fromstring(result) + for elem in result.xpath(language_fallback_xpath): + elem.getparent().remove(elem) - claims = result.get('claims', {}) - official_website = get_string(claims, 'P856', None) - if official_website is not None: - urls.append({'title': 'Official site', 'url': official_website}) - results.append({'title': title, 'url': official_website}) + description = extract_text(result.xpath(description_xpath)) + # URLS + + # official website + add_url(urls, result, 'P856', results=results) + + # wikipedia wikipedia_link_count = 0 wikipedia_link = get_wikilink(result, language + 'wiki') - wikipedia_link_count += add_url(urls, - 'Wikipedia (' + language + ')', - wikipedia_link) + if wikipedia_link: + wikipedia_link_count += 1 + urls.append({'title': 'Wikipedia (' + language + ')', + 'url': wikipedia_link}) + if language != 'en': wikipedia_en_link = get_wikilink(result, 'enwiki') - wikipedia_link_count += add_url(urls, - 'Wikipedia (en)', - wikipedia_en_link) - if wikipedia_link_count == 0: - misc_language = get_wiki_firstlanguage(result, 'wiki') - if misc_language is not None: - add_url(urls, - 'Wikipedia (' + misc_language + ')', - get_wikilink(result, misc_language + 'wiki')) + if wikipedia_en_link: + wikipedia_link_count += 1 + urls.append({'title': 'Wikipedia (en)', + 'url': wikipedia_en_link}) - if language != 'en': - add_url(urls, - 'Wiki voyage (' + language + ')', - get_wikilink(result, language + 'wikivoyage')) + # TODO: get_wiki_firstlanguage + # if wikipedia_link_count == 0: - add_url(urls, - 'Wiki voyage (en)', - get_wikilink(result, 'enwikivoyage')) + # more wikis + add_url(urls, result, default_label='Wikivoyage (' + language + ')', link_type=language + 'wikivoyage') + add_url(urls, result, default_label='Wikiquote (' + language + ')', link_type=language + 'wikiquote') + add_url(urls, result, default_label='Wikimedia Commons', link_type='commonswiki') - if language != 'en': - add_url(urls, - 'Wikiquote (' + language + ')', - get_wikilink(result, language + 'wikiquote')) + add_url(urls, result, 'P625', 'OpenStreetMap', link_type='geo') - add_url(urls, - 'Wikiquote (en)', - get_wikilink(result, 'enwikiquote')) + # musicbrainz + add_url(urls, result, 'P434', 'MusicBrainz', 'http://musicbrainz.org/artist/') + add_url(urls, result, 'P435', 'MusicBrainz', 'http://musicbrainz.org/work/') + add_url(urls, result, 'P436', 'MusicBrainz', 'http://musicbrainz.org/release-group/') + add_url(urls, result, 'P966', 'MusicBrainz', 'http://musicbrainz.org/label/') - add_url(urls, - 'Commons wiki', - get_wikilink(result, 'commonswiki')) + # IMDb + add_url(urls, result, 'P345', 'IMDb', 'https://www.imdb.com/', link_type='imdb') + # source code repository + add_url(urls, result, 'P1324') + # blog + add_url(urls, result, 'P1581') + # social media links + add_url(urls, result, 'P2397', 'YouTube', 'https://www.youtube.com/channel/') + add_url(urls, result, 'P1651', 'YouTube', 'https://www.youtube.com/watch?v=') + add_url(urls, result, 'P2002', 'Twitter', 'https://twitter.com/') + add_url(urls, result, 'P2013', 'Facebook', 'https://facebook.com/') + add_url(urls, result, 'P2003', 'Instagram', 'https://instagram.com/') - add_url(urls, - 'Location', - get_geolink(claims, 'P625', None)) + urls.append({'title': 'Wikidata', + 'url': 'https://www.wikidata.org/wiki/' + + wikidata_id + '?uselang=' + language}) - add_url(urls, - 'Wikidata', - 'https://www.wikidata.org/wiki/' - + wikidata_id + '?uselang=' + language) + # INFOBOX ATTRIBUTES (ROWS) - musicbrainz_work_id = get_string(claims, 'P435') - if musicbrainz_work_id is not None: - add_url(urls, - 'MusicBrainz', - 'http://musicbrainz.org/work/' - + musicbrainz_work_id) + # DATES + # inception date + add_attribute(attributes, result, 'P571', date=True) + # dissolution date + add_attribute(attributes, result, 'P576', date=True) + # start date + add_attribute(attributes, result, 'P580', date=True) + # end date + add_attribute(attributes, result, 'P582', date=True) + # date of birth + add_attribute(attributes, result, 'P569', date=True) + # date of death + add_attribute(attributes, result, 'P570', date=True) + # date of spacecraft launch + add_attribute(attributes, result, 'P619', date=True) + # date of spacecraft landing + add_attribute(attributes, result, 'P620', date=True) - musicbrainz_artist_id = get_string(claims, 'P434') - if musicbrainz_artist_id is not None: - add_url(urls, - 'MusicBrainz', - 'http://musicbrainz.org/artist/' - + musicbrainz_artist_id) + # nationality + add_attribute(attributes, result, 'P27') + # country of origin + add_attribute(attributes, result, 'P495') + # country + add_attribute(attributes, result, 'P17') + # headquarters + add_attribute(attributes, result, 'Q180') - musicbrainz_release_group_id = get_string(claims, 'P436') - if musicbrainz_release_group_id is not None: - add_url(urls, - 'MusicBrainz', - 'http://musicbrainz.org/release-group/' - + musicbrainz_release_group_id) + # PLACES + # capital + add_attribute(attributes, result, 'P36', trim=True) + # head of state + add_attribute(attributes, result, 'P35', trim=True) + # head of government + add_attribute(attributes, result, 'P6', trim=True) + # type of government + add_attribute(attributes, result, 'P122') + # official language + add_attribute(attributes, result, 'P37') + # population + add_attribute(attributes, result, 'P1082', trim=True) + # area + add_attribute(attributes, result, 'P2046') + # currency + add_attribute(attributes, result, 'P38', trim=True) + # heigth (building) + add_attribute(attributes, result, 'P2048') - musicbrainz_label_id = get_string(claims, 'P966') - if musicbrainz_label_id is not None: - add_url(urls, - 'MusicBrainz', - 'http://musicbrainz.org/label/' - + musicbrainz_label_id) + # MEDIA + # platform (videogames) + add_attribute(attributes, result, 'P400') + # author + add_attribute(attributes, result, 'P50') + # creator + add_attribute(attributes, result, 'P170') + # director + add_attribute(attributes, result, 'P57') + # performer + add_attribute(attributes, result, 'P175') + # developer + add_attribute(attributes, result, 'P178') + # producer + add_attribute(attributes, result, 'P162') + # manufacturer + add_attribute(attributes, result, 'P176') + # screenwriter + add_attribute(attributes, result, 'P58') + # production company + add_attribute(attributes, result, 'P272') + # record label + add_attribute(attributes, result, 'P264') + # publisher + add_attribute(attributes, result, 'P123') + # original network + add_attribute(attributes, result, 'P449') + # distributor + add_attribute(attributes, result, 'P750') + # composer + add_attribute(attributes, result, 'P86') + # publication date + add_attribute(attributes, result, 'P577', date=True) + # genre + add_attribute(attributes, result, 'P136') + # original language + add_attribute(attributes, result, 'P364') + # isbn + add_attribute(attributes, result, 'Q33057') + # software license + add_attribute(attributes, result, 'P275') + # programming language + add_attribute(attributes, result, 'P277') + # version + add_attribute(attributes, result, 'P348', trim=True) + # narrative location + add_attribute(attributes, result, 'P840') - # musicbrainz_area_id = get_string(claims, 'P982') - # P1407 MusicBrainz series ID - # P1004 MusicBrainz place ID - # P1330 MusicBrainz instrument ID - # P1407 MusicBrainz series ID + # LANGUAGES + # number of speakers + add_attribute(attributes, result, 'P1098') + # writing system + add_attribute(attributes, result, 'P282') + # regulatory body + add_attribute(attributes, result, 'P1018') + # language code + add_attribute(attributes, result, 'P218') - postal_code = get_string(claims, 'P281', None) - if postal_code is not None: - attributes.append({'label': 'Postal code(s)', 'value': postal_code}) + # OTHER + # ceo + add_attribute(attributes, result, 'P169', trim=True) + # founder + add_attribute(attributes, result, 'P112') + # legal form (company/organization) + add_attribute(attributes, result, 'P1454') + # operator + add_attribute(attributes, result, 'P137') + # crew members (tripulation) + add_attribute(attributes, result, 'P1029') + # taxon + add_attribute(attributes, result, 'P225') + # chemical formula + add_attribute(attributes, result, 'P274') + # winner (sports/contests) + add_attribute(attributes, result, 'P1346') + # number of deaths + add_attribute(attributes, result, 'P1120') + # currency code + add_attribute(attributes, result, 'P498') - date_of_birth = get_time(claims, 'P569', locale, None) - if date_of_birth is not None: - attributes.append({'label': 'Date of birth', 'value': date_of_birth}) - - date_of_death = get_time(claims, 'P570', locale, None) - if date_of_death is not None: - attributes.append({'label': 'Date of death', 'value': date_of_death}) + image = add_image(result) if len(attributes) == 0 and len(urls) == 2 and len(description) == 0: results.append({ @@ -190,6 +302,7 @@ def getDetail(jsonresponse, wikidata_id, language, locale): 'infobox': title, 'id': wikipedia_link, 'content': description, + 'img_src': image, 'attributes': attributes, 'urls': urls }) @@ -197,92 +310,151 @@ def getDetail(jsonresponse, wikidata_id, language, locale): return results -def add_url(urls, title, url): - if url is not None: - urls.append({'title': title, 'url': url}) - return 1 +# only returns first match +def add_image(result): + # P15: route map, P242: locator map, P154: logo, P18: image, P242: map, P41: flag, P2716: collage, P2910: icon + property_ids = ['P15', 'P242', 'P154', 'P18', 'P242', 'P41', 'P2716', 'P2910'] + + for property_id in property_ids: + image = result.xpath(property_xpath.replace('{propertyid}', property_id)) + if image: + image_name = image[0].xpath(value_xpath) + image_src = url_image.replace('{filename}', extract_text(image_name[0])) + return image_src + + +# setting trim will only returned high ranked rows OR the first row +def add_attribute(attributes, result, property_id, default_label=None, date=False, trim=False): + attribute = result.xpath(property_xpath.replace('{propertyid}', property_id)) + if attribute: + + if default_label: + label = default_label + else: + label = extract_text(attribute[0].xpath(label_xpath)) + label = label[0].upper() + label[1:] + + if date: + trim = True + # remove calendar name + calendar_name = attribute[0].xpath(calendar_name_xpath) + for calendar in calendar_name: + calendar.getparent().remove(calendar) + + concat_values = "" + values = [] + first_value = None + for row in attribute[0].xpath(property_row_xpath): + if not first_value or not trim or row.xpath(preferred_rank_xpath): + + value = row.xpath(value_xpath) + if not value: + continue + value = extract_text(value) + + # save first value in case no ranked row is found + if trim and not first_value: + first_value = value + else: + # to avoid duplicate values + if value not in values: + concat_values += value + ", " + values.append(value) + + if trim and not values: + attributes.append({'label': label, + 'value': first_value}) + else: + attributes.append({'label': label, + 'value': concat_values[:-2]}) + + +# requires property_id unless it's a wiki link (defined in link_type) +def add_url(urls, result, property_id=None, default_label=None, url_prefix=None, results=None, link_type=None): + links = [] + + # wiki links don't have property in wikidata page + if link_type and 'wiki' in link_type: + links.append(get_wikilink(result, link_type)) else: - return 0 + dom_element = result.xpath(property_xpath.replace('{propertyid}', property_id)) + if dom_element: + dom_element = dom_element[0] + if not default_label: + label = extract_text(dom_element.xpath(label_xpath)) + label = label[0].upper() + label[1:] + + if link_type == 'geo': + links.append(get_geolink(dom_element)) + + elif link_type == 'imdb': + links.append(get_imdblink(dom_element, url_prefix)) + + else: + url_results = dom_element.xpath(url_xpath) + for link in url_results: + if link is not None: + if url_prefix: + link = url_prefix + extract_text(link) + else: + link = extract_text(link) + links.append(link) + + # append urls + for url in links: + if url is not None: + urls.append({'title': default_label or label, + 'url': url}) + if results is not None: + results.append({'title': default_label or label, + 'url': url}) -def get_mainsnak(claims, propertyName): - propValue = claims.get(propertyName, {}) - if len(propValue) == 0: +def get_imdblink(result, url_prefix): + imdb_id = result.xpath(value_xpath) + if imdb_id: + imdb_id = extract_text(imdb_id) + id_prefix = imdb_id[:2] + if id_prefix == 'tt': + url = url_prefix + 'title/' + imdb_id + elif id_prefix == 'nm': + url = url_prefix + 'name/' + imdb_id + elif id_prefix == 'ch': + url = url_prefix + 'character/' + imdb_id + elif id_prefix == 'co': + url = url_prefix + 'company/' + imdb_id + elif id_prefix == 'ev': + url = url_prefix + 'event/' + imdb_id + else: + url = None + return url + + +def get_geolink(result): + coordinates = result.xpath(value_xpath) + if not coordinates: return None + coordinates = extract_text(coordinates[0]) + latitude, longitude = coordinates.split(',') - propValue = propValue[0].get('mainsnak', None) - return propValue - - -def get_string(claims, propertyName, defaultValue=None): - propValue = claims.get(propertyName, {}) - if len(propValue) == 0: - return defaultValue - - result = [] - for e in propValue: - mainsnak = e.get('mainsnak', {}) - - datavalue = mainsnak.get('datavalue', {}) - if datavalue is not None: - result.append(datavalue.get('value', '')) - - if len(result) == 0: - return defaultValue - else: - # TODO handle multiple urls - return result[0] - - -def get_time(claims, propertyName, locale, defaultValue=None): - propValue = claims.get(propertyName, {}) - if len(propValue) == 0: - return defaultValue - - result = [] - for e in propValue: - mainsnak = e.get('mainsnak', {}) - - datavalue = mainsnak.get('datavalue', {}) - if datavalue is not None: - value = datavalue.get('value', '') - result.append(value.get('time', '')) - - if len(result) == 0: - date_string = defaultValue - else: - date_string = ', '.join(result) - - try: - parsed_date = datetime.strptime(date_string, "+%Y-%m-%dT%H:%M:%SZ") - except: - if date_string.startswith('-'): - return date_string.split('T')[0] - try: - parsed_date = dateutil_parse(date_string, fuzzy=False, default=False) - except: - logger.debug('could not parse date %s', date_string) - return date_string.split('T')[0] - - return format_date_by_locale(parsed_date, locale) - - -def get_geolink(claims, propertyName, defaultValue=''): - mainsnak = get_mainsnak(claims, propertyName) - - if mainsnak is None: - return defaultValue - - datatype = mainsnak.get('datatype', '') - datavalue = mainsnak.get('datavalue', {}) - - if datatype != 'globe-coordinate': - return defaultValue - - value = datavalue.get('value', {}) - - precision = value.get('precision', 0.0002) + # convert to decimal + lat = int(latitude[:latitude.find(u'°')]) + if latitude.find('\'') >= 0: + lat += int(latitude[latitude.find(u'°') + 1:latitude.find('\'')] or 0) / 60.0 + if latitude.find('"') >= 0: + lat += float(latitude[latitude.find('\'') + 1:latitude.find('"')] or 0) / 3600.0 + if latitude.find('S') >= 0: + lat *= -1 + lon = int(longitude[:longitude.find(u'°')]) + if longitude.find('\'') >= 0: + lon += int(longitude[longitude.find(u'°') + 1:longitude.find('\'')] or 0) / 60.0 + if longitude.find('"') >= 0: + lon += float(longitude[longitude.find('\'') + 1:longitude.find('"')] or 0) / 3600.0 + if longitude.find('W') >= 0: + lon *= -1 + # TODO: get precision + precision = 0.0002 # there is no zoom information, deduce from precision (error prone) # samples : # 13 --> 5 @@ -298,26 +470,20 @@ def get_geolink(claims, propertyName, defaultValue=''): zoom = int(15 - precision * 8.8322 + precision * precision * 0.625447) url = url_map\ - .replace('{latitude}', str(value.get('latitude', 0)))\ - .replace('{longitude}', str(value.get('longitude', 0)))\ + .replace('{latitude}', str(lat))\ + .replace('{longitude}', str(lon))\ .replace('{zoom}', str(zoom)) return url def get_wikilink(result, wikiid): - url = result.get('sitelinks', {}).get(wikiid, {}).get('url', None) - if url is None: - return url - elif url.startswith('http://'): + url = result.xpath(wikilink_xpath.replace('{wikiid}', wikiid)) + if not url: + return None + url = url[0] + if url.startswith('http://'): url = url.replace('http://', 'https://') elif url.startswith('//'): url = 'https:' + url return url - - -def get_wiki_firstlanguage(result, wikipatternid): - for k in result.get('sitelinks', {}).keys(): - if k.endswith(wikipatternid) and len(k) == (2 + len(wikipatternid)): - return k[0:2] - return None diff --git a/sources/searx/engines/wikipedia.py b/sources/searx/engines/wikipedia.py index fed7b26..70191d2 100644 --- a/sources/searx/engines/wikipedia.py +++ b/sources/searx/engines/wikipedia.py @@ -99,9 +99,8 @@ def response(resp): return [] # link to wikipedia article - # parenthesis are not quoted to make infobox mergeable with wikidata's wikipedia_link = url_lang(resp.search_params['language']) \ - + 'wiki/' + quote(title.replace(' ', '_').encode('utf8')).replace('%28', '(').replace('%29', ')') + + 'wiki/' + quote(title.replace(' ', '_').encode('utf8')) results.append({'url': wikipedia_link, 'title': title}) diff --git a/sources/searx/engines/wolframalpha_noapi.py b/sources/searx/engines/wolframalpha_noapi.py index 59629b8..3a8180f 100644 --- a/sources/searx/engines/wolframalpha_noapi.py +++ b/sources/searx/engines/wolframalpha_noapi.py @@ -8,11 +8,9 @@ # @stable no # @parse url, infobox -from cgi import escape from json import loads from time import time from urllib import urlencode -from lxml.etree import XML from searx.poolrequests import get as http_get @@ -36,7 +34,7 @@ search_url = url + 'input/json.jsp'\ referer_url = url + 'input/?{query}' token = {'value': '', - 'last_updated': None} + 'last_updated': 0} # pods to display as image in infobox # this pods do return a plaintext, but they look better and are more useful as images diff --git a/sources/searx/engines/www500px.py b/sources/searx/engines/www500px.py index c98e194..f1bc6c5 100644 --- a/sources/searx/engines/www500px.py +++ b/sources/searx/engines/www500px.py @@ -41,7 +41,7 @@ def response(resp): results = [] dom = html.fromstring(resp.text) - regex = re.compile('3\.jpg.*$') + regex = re.compile(r'3\.jpg.*$') # parse results for result in dom.xpath('//div[@class="photo"]'): diff --git a/sources/searx/engines/xpath.py b/sources/searx/engines/xpath.py index e701c02..e5c0c5b 100644 --- a/sources/searx/engines/xpath.py +++ b/sources/searx/engines/xpath.py @@ -87,7 +87,7 @@ def request(query, params): fp = {'query': query} if paging and search_url.find('{pageno}') >= 0: - fp['pageno'] = (params['pageno'] + first_page_num - 1) * page_size + fp['pageno'] = (params['pageno'] - 1) * page_size + first_page_num params['url'] = search_url.format(**fp) params['query'] = query diff --git a/sources/searx/engines/yahoo.py b/sources/searx/engines/yahoo.py index b8b40e4..8e24a28 100644 --- a/sources/searx/engines/yahoo.py +++ b/sources/searx/engines/yahoo.py @@ -20,10 +20,12 @@ from searx.engines.xpath import extract_text, extract_url categories = ['general'] paging = True language_support = True +time_range_support = True # search-url base_url = 'https://search.yahoo.com/' search_url = 'search?{query}&b={offset}&fl=1&vl=lang_{lang}' +search_url_with_time = 'search?{query}&b={offset}&fl=1&vl=lang_{lang}&age={age}&btf={btf}&fr2=time' # specific xpath variables results_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' Sr ')]" @@ -32,6 +34,10 @@ title_xpath = './/h3/a' content_xpath = './/div[@class="compText aAbs"]' suggestion_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' AlsoTry ')]//a" +time_range_dict = {'day': ['1d', 'd'], + 'week': ['1w', 'w'], + 'month': ['1m', 'm']} + # remove yahoo-specific tracking-url def parse_url(url_string): @@ -51,18 +57,30 @@ def parse_url(url_string): return unquote(url_string[start:end]) +def _get_url(query, offset, language, time_range): + if time_range in time_range_dict: + return base_url + search_url_with_time.format(offset=offset, + query=urlencode({'p': query}), + lang=language, + age=time_range_dict[time_range][0], + btf=time_range_dict[time_range][1]) + return base_url + search_url.format(offset=offset, + query=urlencode({'p': query}), + lang=language) + + +def _get_language(params): + if params['language'] == 'all': + return 'en' + return params['language'].split('_')[0] + + # do search-request def request(query, params): offset = (params['pageno'] - 1) * 10 + 1 + language = _get_language(params) - if params['language'] == 'all': - language = 'en' - else: - language = params['language'].split('_')[0] - - params['url'] = base_url + search_url.format(offset=offset, - query=urlencode({'p': query}), - lang=language) + params['url'] = _get_url(query, offset, language, params['time_range']) # TODO required? params['cookies']['sB'] = 'fl=1&vl=lang_{lang}&sh=1&rw=new&v=1'\ diff --git a/sources/searx/engines/yahoo_news.py b/sources/searx/engines/yahoo_news.py index d4cfbed..e91c1d3 100644 --- a/sources/searx/engines/yahoo_news.py +++ b/sources/searx/engines/yahoo_news.py @@ -55,7 +55,7 @@ def request(query, params): def sanitize_url(url): if ".yahoo.com/" in url: - return re.sub(u"\;\_ylt\=.+$", "", url) + return re.sub(u"\\;\\_ylt\\=.+$", "", url) else: return url diff --git a/sources/searx/plugins/__init__.py b/sources/searx/plugins/__init__.py index efb9b06..768a510 100644 --- a/sources/searx/plugins/__init__.py +++ b/sources/searx/plugins/__init__.py @@ -19,15 +19,17 @@ from searx import logger logger = logger.getChild('plugins') -from searx.plugins import (https_rewrite, +from searx.plugins import (doai_rewrite, + https_rewrite, + infinite_scroll, open_results_on_new_tab, self_info, search_on_category_select, tracker_url_remover, vim_hotkeys) -required_attrs = (('name', str), - ('description', str), +required_attrs = (('name', (str, unicode)), + ('description', (str, unicode)), ('default_on', bool)) optional_attrs = (('js_dependencies', tuple), @@ -73,7 +75,9 @@ class PluginStore(): plugins = PluginStore() +plugins.register(doai_rewrite) plugins.register(https_rewrite) +plugins.register(infinite_scroll) plugins.register(open_results_on_new_tab) plugins.register(self_info) plugins.register(search_on_category_select) diff --git a/sources/searx/plugins/doai_rewrite.py b/sources/searx/plugins/doai_rewrite.py new file mode 100644 index 0000000..fc5998b --- /dev/null +++ b/sources/searx/plugins/doai_rewrite.py @@ -0,0 +1,31 @@ +from flask_babel import gettext +import re +from urlparse import urlparse, parse_qsl + +regex = re.compile(r'10\.\d{4,9}/[^\s]+') + +name = gettext('DOAI rewrite') +description = gettext('Avoid paywalls by redirecting to open-access versions of publications when available') +default_on = False + + +def extract_doi(url): + match = regex.search(url.path) + if match: + return match.group(0) + for _, v in parse_qsl(url.query): + match = regex.search(v) + if match: + return match.group(0) + return None + + +def on_result(request, ctx): + doi = extract_doi(ctx['result']['parsed_url']) + if doi and len(doi) < 50: + for suffix in ('/', '.pdf', '/full', '/meta', '/abstract'): + if doi.endswith(suffix): + doi = doi[:-len(suffix)] + ctx['result']['url'] = 'http://doai.io/' + doi + ctx['result']['parsed_url'] = urlparse(ctx['result']['url']) + return True diff --git a/sources/searx/plugins/https_rewrite.py b/sources/searx/plugins/https_rewrite.py index 0a58cc8..8a9fcd4 100644 --- a/sources/searx/plugins/https_rewrite.py +++ b/sources/searx/plugins/https_rewrite.py @@ -21,7 +21,7 @@ from lxml import etree from os import listdir, environ from os.path import isfile, isdir, join from searx.plugins import logger -from flask.ext.babel import gettext +from flask_babel import gettext from searx import searx_dir @@ -87,7 +87,7 @@ def load_single_https_ruleset(rules_path): # convert host-rule to valid regex host = ruleset.attrib.get('host')\ - .replace('.', '\.').replace('*', '.*') + .replace('.', r'\.').replace('*', '.*') # append to host list hosts.append(host) diff --git a/sources/searx/plugins/infinite_scroll.py b/sources/searx/plugins/infinite_scroll.py new file mode 100644 index 0000000..422a4be --- /dev/null +++ b/sources/searx/plugins/infinite_scroll.py @@ -0,0 +1,8 @@ +from flask_babel import gettext + +name = gettext('Infinite scroll') +description = gettext('Automatically load next page when scrolling to bottom of current page') +default_on = False + +js_dependencies = ('plugins/js/infinite_scroll.js',) +css_dependencies = ('plugins/css/infinite_scroll.css',) diff --git a/sources/searx/plugins/open_results_on_new_tab.py b/sources/searx/plugins/open_results_on_new_tab.py index 5ebece1..ae27ea2 100644 --- a/sources/searx/plugins/open_results_on_new_tab.py +++ b/sources/searx/plugins/open_results_on_new_tab.py @@ -14,7 +14,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2016 by Adam Tauber, ''' -from flask.ext.babel import gettext +from flask_babel import gettext name = gettext('Open result links on new browser tabs') description = gettext('Results are opened in the same window by default. ' 'This plugin overwrites the default behaviour to open links on new tabs/windows. ' diff --git a/sources/searx/plugins/search_on_category_select.py b/sources/searx/plugins/search_on_category_select.py index 53585fa..f72c63d 100644 --- a/sources/searx/plugins/search_on_category_select.py +++ b/sources/searx/plugins/search_on_category_select.py @@ -14,7 +14,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2015 by Adam Tauber, ''' -from flask.ext.babel import gettext +from flask_babel import gettext name = gettext('Search on category select') description = gettext('Perform search immediately if a category selected. ' 'Disable to select multiple categories. (JavaScript required)') diff --git a/sources/searx/plugins/self_info.py b/sources/searx/plugins/self_info.py index dc6b7cd..438274c 100644 --- a/sources/searx/plugins/self_info.py +++ b/sources/searx/plugins/self_info.py @@ -14,7 +14,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2015 by Adam Tauber, ''' -from flask.ext.babel import gettext +from flask_babel import gettext import re name = "Self Informations" description = gettext('Displays your IP if the query is "ip" and your user agent if the query contains "user agent".') @@ -29,6 +29,8 @@ p = re.compile('.*user[ -]agent.*', re.IGNORECASE) # request: flask request object # ctx: the whole local context of the pre search hook def post_search(request, ctx): + if ctx['search'].pageno > 1: + return True if ctx['search'].query == 'ip': x_forwarded_for = request.headers.getlist("X-Forwarded-For") if x_forwarded_for: diff --git a/sources/searx/plugins/tracker_url_remover.py b/sources/searx/plugins/tracker_url_remover.py index ed71c94..b909e3f 100644 --- a/sources/searx/plugins/tracker_url_remover.py +++ b/sources/searx/plugins/tracker_url_remover.py @@ -15,7 +15,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2015 by Adam Tauber, ''' -from flask.ext.babel import gettext +from flask_babel import gettext import re from urlparse import urlunparse diff --git a/sources/searx/plugins/vim_hotkeys.py b/sources/searx/plugins/vim_hotkeys.py index e537a3a..8f06f13 100644 --- a/sources/searx/plugins/vim_hotkeys.py +++ b/sources/searx/plugins/vim_hotkeys.py @@ -1,4 +1,4 @@ -from flask.ext.babel import gettext +from flask_babel import gettext name = gettext('Vim-like hotkeys') description = gettext('Navigate search results with Vim-like hotkeys ' diff --git a/sources/searx/preferences.py b/sources/searx/preferences.py index dd9133d..ed7b6f6 100644 --- a/sources/searx/preferences.py +++ b/sources/searx/preferences.py @@ -166,6 +166,7 @@ class SwitchableSetting(Setting): class EnginesSetting(SwitchableSetting): + def _post_init(self): super(EnginesSetting, self)._post_init() transformed_choices = [] @@ -191,6 +192,7 @@ class EnginesSetting(SwitchableSetting): class PluginsSetting(SwitchableSetting): + def _post_init(self): super(PluginsSetting, self)._post_init() transformed_choices = [] @@ -225,7 +227,8 @@ class Preferences(object): 'safesearch': MapSetting(settings['search']['safe_search'], map={'0': 0, '1': 1, '2': 2}), - 'theme': EnumStringSetting(settings['ui']['default_theme'], choices=themes)} + 'theme': EnumStringSetting(settings['ui']['default_theme'], choices=themes), + 'results_on_new_tab': MapSetting(False, map={'0': False, '1': True})} self.engines = EnginesSetting('engines', choices=engines) self.plugins = PluginsSetting('plugins', choices=plugins) diff --git a/sources/searx/results.py b/sources/searx/results.py index dcd966e..32832f1 100644 --- a/sources/searx/results.py +++ b/sources/searx/results.py @@ -5,7 +5,7 @@ from threading import RLock from urlparse import urlparse, unquote from searx.engines import engines -CONTENT_LEN_IGNORED_CHARS_REGEX = re.compile('[,;:!?\./\\\\ ()-_]', re.M | re.U) +CONTENT_LEN_IGNORED_CHARS_REGEX = re.compile(r'[,;:!?\./\\\\ ()-_]', re.M | re.U) WHITESPACE_REGEX = re.compile('( |\t|\n)+', re.M | re.U) @@ -18,7 +18,17 @@ def result_content_len(content): def compare_urls(url_a, url_b): - if url_a.netloc != url_b.netloc or url_a.query != url_b.query: + # ignore www. in comparison + if url_a.netloc.startswith('www.'): + host_a = url_a.netloc.replace('www.', '', 1) + else: + host_a = url_a.netloc + if url_b.netloc.startswith('www.'): + host_b = url_b.netloc.replace('www.', '', 1) + else: + host_b = url_b.netloc + + if host_a != host_b or url_a.query != url_b.query or url_a.fragment != url_b.fragment: return False # remove / from the end of the url if required @@ -33,25 +43,42 @@ def compare_urls(url_a, url_b): def merge_two_infoboxes(infobox1, infobox2): + # get engines weights + if hasattr(engines[infobox1['engine']], 'weight'): + weight1 = engines[infobox1['engine']].weight + else: + weight1 = 1 + if hasattr(engines[infobox2['engine']], 'weight'): + weight2 = engines[infobox2['engine']].weight + else: + weight2 = 1 + + if weight2 > weight1: + infobox1['engine'] = infobox2['engine'] + if 'urls' in infobox2: urls1 = infobox1.get('urls', None) if urls1 is None: urls1 = [] - infobox1['urls'] = urls1 - urlSet = set() - for url in infobox1.get('urls', []): - urlSet.add(url.get('url', None)) + for url2 in infobox2.get('urls', []): + unique_url = True + for url1 in infobox1.get('urls', []): + if compare_urls(urlparse(url1.get('url', '')), urlparse(url2.get('url', ''))): + unique_url = False + break + if unique_url: + urls1.append(url2) - for url in infobox2.get('urls', []): - if url.get('url', None) not in urlSet: - urls1.append(url) + infobox1['urls'] = urls1 if 'img_src' in infobox2: img1 = infobox1.get('img_src', None) img2 = infobox2.get('img_src') if img1 is None: infobox1['img_src'] = img2 + elif weight2 > weight1: + infobox1['img_src'] = img2 if 'attributes' in infobox2: attributes1 = infobox1.get('attributes', None) @@ -65,7 +92,8 @@ def merge_two_infoboxes(infobox1, infobox2): attributeSet.add(attribute.get('label', None)) for attribute in infobox2.get('attributes', []): - attributes1.append(attribute) + if attribute.get('label', None) not in attributeSet: + attributes1.append(attribute) if 'content' in infobox2: content1 = infobox1.get('content', None) @@ -91,15 +119,15 @@ def result_score(result): class ResultContainer(object): """docstring for ResultContainer""" + def __init__(self): super(ResultContainer, self).__init__() self.results = defaultdict(list) self._merged_results = [] self.infoboxes = [] - self._infobox_ids = {} self.suggestions = set() self.answers = set() - self.number_of_results = 0 + self._number_of_results = [] def extend(self, engine_name, results): for result in list(results): @@ -113,7 +141,7 @@ class ResultContainer(object): self._merge_infobox(result) results.remove(result) elif 'number_of_results' in result: - self.number_of_results = max(self.number_of_results, result['number_of_results']) + self._number_of_results.append(result['number_of_results']) results.remove(result) with RLock(): @@ -137,14 +165,13 @@ class ResultContainer(object): add_infobox = True infobox_id = infobox.get('id', None) if infobox_id is not None: - existingIndex = self._infobox_ids.get(infobox_id, None) - if existingIndex is not None: - merge_two_infoboxes(self.infoboxes[existingIndex], infobox) - add_infobox = False + for existingIndex in self.infoboxes: + if compare_urls(urlparse(existingIndex.get('id', '')), urlparse(infobox_id)): + merge_two_infoboxes(existingIndex, infobox) + add_infobox = False if add_infobox: self.infoboxes.append(infobox) - self._infobox_ids[infobox_id] = len(self.infoboxes) - 1 def _merge_result(self, result, position): result['parsed_url'] = urlparse(result['url']) @@ -154,11 +181,6 @@ class ResultContainer(object): result['parsed_url'] = result['parsed_url']._replace(scheme="http") result['url'] = result['parsed_url'].geturl() - result['host'] = result['parsed_url'].netloc - - if result['host'].startswith('www.'): - result['host'] = result['host'].replace('www.', '', 1) - result['engines'] = [result['engine']] # strip multiple spaces and cariage returns from content @@ -252,3 +274,9 @@ class ResultContainer(object): def results_length(self): return len(self._merged_results) + + def results_number(self): + resultnum_sum = sum(self._number_of_results) + if not resultnum_sum or not self._number_of_results: + return 0 + return resultnum_sum / len(self._number_of_results) diff --git a/sources/searx/search.py b/sources/searx/search.py index a408016..c6d17eb 100644 --- a/sources/searx/search.py +++ b/sources/searx/search.py @@ -15,14 +15,15 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2013- by Adam Tauber, ''' +import gc import threading -import searx.poolrequests as requests_lib +from thread import start_new_thread from time import time -from searx import settings +from uuid import uuid4 +import searx.poolrequests as requests_lib from searx.engines import ( categories, engines ) -from searx.languages import language_codes from searx.utils import gen_useragent from searx.query import Query from searx.results import ResultContainer @@ -56,19 +57,20 @@ def search_request_wrapper(fn, url, engine_name, **kwargs): def threaded_requests(requests): timeout_limit = max(r[2]['timeout'] for r in requests) search_start = time() + search_id = uuid4().__str__() for fn, url, request_args, engine_name in requests: request_args['timeout'] = timeout_limit th = threading.Thread( target=search_request_wrapper, args=(fn, url, engine_name), kwargs=request_args, - name='search_request', + name=search_id, ) th._engine_name = engine_name th.start() for th in threading.enumerate(): - if th.name == 'search_request': + if th.name == search_id: remaining_time = max(0.0, timeout_limit - (time() - search_start)) th.join(remaining_time) if th.isAlive(): @@ -138,6 +140,8 @@ class Search(object): self.paging = False self.pageno = 1 self.lang = 'all' + self.time_range = None + self.is_advanced = None # set blocked engines self.disabled_engines = request.preferences.engines.get_disabled() @@ -178,9 +182,10 @@ class Search(object): if len(query_obj.languages): self.lang = query_obj.languages[-1] - self.engines = query_obj.engines + self.time_range = self.request_data.get('time_range') + self.is_advanced = self.request_data.get('advanced_search') - self.categories = [] + self.engines = query_obj.engines # if engines are calculated from query, # set categories by using that informations @@ -279,6 +284,9 @@ class Search(object): if self.lang != 'all' and not engine.language_support: continue + if self.time_range and not engine.time_range_support: + continue + # set default request parameters request_params = default_request_params() request_params['headers']['User-Agent'] = user_agent @@ -293,6 +301,8 @@ class Search(object): # 0 = None, 1 = Moderate, 2 = Strict request_params['safesearch'] = request.preferences.get_value('safesearch') + request_params['time_range'] = self.time_range + request_params['advanced_search'] = self.is_advanced # update request parameters dependent on # search-engine (contained in engines folder) @@ -339,6 +349,7 @@ class Search(object): return self # send all search-request threaded_requests(requests) + start_new_thread(gc.collect, tuple()) # return results, suggestions, answers and infoboxes return self diff --git a/sources/searx/settings.yml b/sources/searx/settings.yml index 9308756..4969c4a 100644 --- a/sources/searx/settings.yml +++ b/sources/searx/settings.yml @@ -25,7 +25,7 @@ outgoing: # communication with search engines pool_maxsize : 10 # Number of simultaneous requests by host # uncomment below section if you want to use a proxy # see http://docs.python-requests.org/en/latest/user/advanced/#proxies -# SOCKS proxies are not supported : see https://github.com/kennethreitz/requests/pull/478 +# SOCKS proxies are also supported: see http://docs.python-requests.org/en/master/user/advanced/#socks # proxies : # http : http://127.0.0.1:8080 # https: http://127.0.0.1:8080 @@ -84,9 +84,15 @@ engines: disabled : True shortcut : bb - - name : btdigg - engine : btdigg - shortcut : bt + - name : crossref + engine : json_engine + paging : True + search_url : http://search.crossref.org/dois?q={query}&page={pageno} + url_query : doi + title_query : title + content_query : fullCitation + categories : science + shortcut : cr - name : currency engine : currency_convert @@ -105,6 +111,13 @@ engines: - name : ddg definitions engine : duckduckgo_definitions shortcut : ddd + weight : 2 + disabled : True + + - name : digbt + engine : digbt + shortcut : dbt + timeout : 6.0 disabled : True - name : digg @@ -127,10 +140,12 @@ engines: - name : wikidata engine : wikidata shortcut : wd + weight : 2 - name : duckduckgo engine : duckduckgo shortcut : ddg + disabled : True # api-key required: http://www.faroo.com/hp/api/api.html#key # - name : faroo @@ -200,6 +215,20 @@ engines: engine : google_news shortcut : gon + - name : google scholar + engine : xpath + paging : True + search_url : https://scholar.google.com/scholar?start={pageno}&q={query}&hl=en&as_sdt=0,5&as_vis=1 + results_xpath : //div[@class="gs_r"]/div[@class="gs_ri"] + url_xpath : .//h3/a/@href + title_xpath : .//h3/a + content_xpath : .//div[@class="gs_rs"] + suggestion_xpath : //div[@id="gs_qsuggest"]/ul/li + page_size : 10 + first_page_num : 0 + categories : science + shortcut : gos + - name : google play apps engine : xpath search_url : https://play.google.com/store/search?q={query}&c=apps @@ -254,6 +283,37 @@ engines: disabled : True shortcut : habr + - name : hoogle + engine : json_engine + paging : True + search_url : https://www.haskell.org/hoogle/?mode=json&hoogle={query}&start={pageno} + results_query : results + url_query : location + title_query : self + content_query : docs + page_size : 20 + categories : it + shortcut : ho + + - name : ina + engine : ina + shortcut : in + timeout : 6.0 + disabled : True + + - name : microsoft academic + engine : json_engine + paging : True + search_url : https://academic.microsoft.com/api/search/GetEntityResults?query=%40{query}%40&filters=&offset={pageno}&limit=8&correlationId=undefined + results_query : results + url_query : u + title_query : dn + content_query : d + page_size : 8 + first_page_num : 0 + categories : science + shortcut : ma + - name : mixcloud engine : mixcloud shortcut : mc @@ -267,6 +327,18 @@ engines: engine : openstreetmap shortcut : osm + - name : openrepos + engine : xpath + paging : True + search_url : https://openrepos.net/search/node/{query}?page={pageno} + url_xpath : //li[@class="search-result"]//h3[@class="title"]/a/@href + title_xpath : //li[@class="search-result"]//h3[@class="title"]/a + content_xpath : //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"] + categories : files + timeout : 4.0 + disabled : True + shortcut : or + - name : photon engine : photon shortcut : ph @@ -274,7 +346,8 @@ engines: - name : piratebay engine : piratebay shortcut : tpb - disabled : True + url: https://pirateproxy.red/ + timeout : 3.0 - name : qwant engine : qwant @@ -304,9 +377,10 @@ engines: timeout : 10.0 disabled : True - - name : kickass - engine : kickass - shortcut : ka + - name : scanr_structures + shortcut: scs + engine : scanr_structures + disabled : True - name : soundcloud engine : soundcloud @@ -361,11 +435,6 @@ engines: timeout : 6.0 disabled : True - - name : torrentz - engine : torrentz - timeout : 5.0 - shortcut : to - - name : twitter engine : twitter shortcut : tw @@ -426,6 +495,19 @@ engines: timeout: 6.0 categories : science + - name : dictzone + engine : dictzone + shortcut : dc + + - name : mymemory translated + engine : translated + shortcut : tl + timeout : 5.0 + disabled : True + # You can use without an API key, but you are limited to 1000 words/day + # See : http://mymemory.translated.net/doc/usagelimits.php + # api_key : '' + #The blekko technology and team have joined IBM Watson! -> https://blekko.com/ # - name : blekko images # engine : blekko_images diff --git a/sources/searx/static/plugins/css/infinite_scroll.css b/sources/searx/static/plugins/css/infinite_scroll.css new file mode 100644 index 0000000..7e0ee20 --- /dev/null +++ b/sources/searx/static/plugins/css/infinite_scroll.css @@ -0,0 +1,16 @@ +@keyframes rotate-forever { + 0% { transform: rotate(0deg) } + 100% { transform: rotate(360deg) } +} +.loading-spinner { + animation-duration: 0.75s; + animation-iteration-count: infinite; + animation-name: rotate-forever; + animation-timing-function: linear; + height: 30px; + width: 30px; + border: 8px solid #666; + border-right-color: transparent; + border-radius: 50% !important; + margin: 0 auto; +} diff --git a/sources/searx/static/plugins/js/infinite_scroll.js b/sources/searx/static/plugins/js/infinite_scroll.js new file mode 100644 index 0000000..213f74b --- /dev/null +++ b/sources/searx/static/plugins/js/infinite_scroll.js @@ -0,0 +1,18 @@ +$(document).ready(function() { + var win = $(window); + win.scroll(function() { + if ($(document).height() - win.height() == win.scrollTop()) { + var formData = $('#pagination form:last').serialize(); + if (formData) { + $('#pagination').html('
'); + $.post('/', formData, function (data) { + var body = $(data); + $('#pagination').remove(); + $('#main_results').append('
'); + $('#main_results').append(body.find('.result')); + $('#main_results').append(body.find('#pagination')); + }); + } + } + }); +}); diff --git a/sources/searx/static/plugins/js/search_on_category_select.js b/sources/searx/static/plugins/js/search_on_category_select.js index 5ecc2cd..19aeef9 100644 --- a/sources/searx/static/plugins/js/search_on_category_select.js +++ b/sources/searx/static/plugins/js/search_on_category_select.js @@ -4,13 +4,16 @@ $(document).ready(function() { $('#categories input[type="checkbox"]').each(function(i, checkbox) { $(checkbox).prop('checked', false); }); - $('#categories label').removeClass('btn-primary').removeClass('active').addClass('btn-default'); - $(this).removeClass('btn-default').addClass('btn-primary').addClass('active'); - $($(this).children()[0]).prop('checked', 'checked'); + $(document.getElementById($(this).attr("for"))).prop('checked', true); if($('#q').val()) { $('#search_form').submit(); } return false; }); + $('#time-range > option').click(function(e) { + if($('#q').val()) { + $('#search_form').submit(); + } + }); } }); diff --git a/sources/searx/static/themes/oscar/css/logicodev.min.css b/sources/searx/static/themes/oscar/css/logicodev.min.css index 4f9fffb..bd58aa5 100644 --- a/sources/searx/static/themes/oscar/css/logicodev.min.css +++ b/sources/searx/static/themes/oscar/css/logicodev.min.css @@ -1 +1 @@ -.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background:#29314d;color:#01d7d4}.navbar>li>a{padding:0;margin:0}.navbar-nav>li>a{background:#29314d;padding:0 8px;margin:0;line-height:30px}.navbar,.navbar-default{background-color:#29314d;border:none;border-top:4px solid #01d7d4;padding-top:5px;color:#f6f9fa!important;font-weight:700;font-size:1.1em;text-transform:lowercase;margin-bottom:24px;height:30px;line-height:30px;z-index:1}.navbar .navbar-nav>li>a,.navbar-default .navbar-nav>li>a{color:#f6f9fa}.navbar .navbar-brand,.navbar-default .navbar-brand{font-weight:700;color:#01d7d4;line-height:30px;padding:0 30px;margin:0}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#01d7d4;background:#29314d}.navbar-toggle{margin-top:0}*{border-radius:0!important}html{position:relative;min-height:100%;color:#29314d}body{font-family:Roboto,Helvetica,Arial,sans-serif;margin-bottom:80px;background-color:#fff}body a{color:#08c}.footer{position:absolute;bottom:0;width:100%;height:60px;text-align:center;color:#999}input[type=checkbox]:checked+.label_hide_if_checked,input[type=checkbox]:checked+.label_hide_if_not_checked+.label_hide_if_checked{display:none}input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbox]:not(:checked)+.label_hide_if_checked+.label_hide_if_not_checked{display:none}.result_header{margin-top:6px;margin-bottom:4px;font-size:16px}.result_header .favicon{margin-bottom:-3px}.result_header a{color:#29314d;text-decoration:none}.result_header a:hover{color:#08c}.result_header a:visited{color:#684898}.result_header a .highlight{background-color:#f6f9fa}.result-content{margin-top:2px;margin-bottom:0;word-wrap:break-word;color:#666;font-size:13px}.result-content .highlight{font-weight:700}.external-link,.external-link a{color:#2ecc71}.external-link a,.external-link a a{margin-right:3px}.result-default,.result-code,.result-torrent,.result-videos,.result-map{clear:both;padding:2px 4px}.result-default:hover,.result-code:hover,.result-torrent:hover,.result-videos:hover,.result-map:hover{background-color:#f6f9fa}.result-images{float:left!important;width:24%;margin:.5%}.result-images a{display:block;width:100%;height:170px;background-size:cover}.img-thumbnail{margin:5px;max-height:128px;min-height:128px}.result-videos{clear:both}.result-videos hr{margin:5px 0 15px 0}.result-videos .collapse{width:100%}.result-videos .in{margin-bottom:8px}.result-torrent{clear:both}.result-torrent b{margin-right:5px;margin-left:5px}.result-torrent .seeders{color:#2ecc71}.result-torrent .leechers{color:#f35e77}.result-map{clear:both}.result-code{clear:both}.result-code .code-fork,.result-code .code-fork a{color:#666}.suggestion_item{margin:2px 5px}.result_download{margin-right:5px}#pagination{margin-top:30px;padding-bottom:60px}.label-default{color:#a4a4a4;background:0 0}.infobox .panel-heading{background-color:#f6f9fa}.infobox .panel-heading .panel-title{font-weight:700}.infobox p{font-family:"DejaVu Serif",Georgia,Cambria,"Times New Roman",Times,serif!important;font-style:italic}.infobox .btn{background-color:#2ecc71;border:none}.infobox .btn a{color:#fff;margin:5px}.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}.infobox .infobox_part:last-child{margin-bottom:0}.search_categories,#categories{margin:10px 0 4px 0;text-transform:capitalize}.search_categories label,#categories label{border:none;box-shadow:none;font-size:13px;padding-bottom:2px;color:#a4a4a4;margin-bottom:5px}.search_categories label:hover,#categories label:hover{color:#29314d;background-color:transparent}.search_categories label:active,#categories label:active{box-shadow:none}.search_categories .active,#categories .active,.search_categories .btn-primary,#categories .btn-primary{color:#29314d;font-weight:700;border-bottom:5px solid #01d7d4;background-color:transparent}#categories{margin:0}#main-logo{margin-top:10vh;margin-bottom:25px}#main-logo>img{max-width:350px;width:80%}#q{box-shadow:none;border-right:none;border-color:#a4a4a4}#search_form .input-group-btn .btn{border-color:#a4a4a4}#search_form .input-group-btn .btn:hover{background-color:#2ecc71;color:#fff}.cursor-text{cursor:text!important}.cursor-pointer{cursor:pointer!important}pre,code{font-family:'Ubuntu Mono','Courier New','Lucida Console',monospace!important}.lineno{margin-right:5px}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#556366;font-style:italic}.highlight .err{border:1px solid #ffa92f}.highlight .k{color:#BE74D5;font-weight:700}.highlight .o{color:#d19a66}.highlight .cm{color:#556366;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#556366;font-style:italic}.highlight .cs{color:#556366;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.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{color:#BE74D5;font-weight:700}.highlight .kd{color:#BE74D5;font-weight:700}.highlight .kn{color:#BE74D5;font-weight:700}.highlight .kp{color:#be74d5}.highlight .kr{color:#BE74D5;font-weight:700}.highlight .kt{color:#d46c72}.highlight .m{color:#d19a66}.highlight .s{color:#86c372}.highlight .na{color:#7d9029}.highlight .nb{color:#be74d5}.highlight .nc{color:#61AFEF;font-weight:700}.highlight .no{color:#d19a66}.highlight .nd{color:#a2f}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#61afef}.highlight .nl{color:#a0a000}.highlight .nn{color:#61AFEF;font-weight:700}.highlight .nt{color:#BE74D5;font-weight:700}.highlight .nv{color:#dfc06f}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#d7dae0}.highlight .mf{color:#d19a66}.highlight .mh{color:#d19a66}.highlight .mi{color:#d19a66}.highlight .mo{color:#d19a66}.highlight .sb{color:#86c372}.highlight .sc{color:#86c372}.highlight .sd{color:#86C372;font-style:italic}.highlight .s2{color:#86c372}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#86c372}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:#be74d5}.highlight .sr{color:#b68}.highlight .s1{color:#86c372}.highlight .ss{color:#dfc06f}.highlight .bp{color:#be74d5}.highlight .vc{color:#dfc06f}.highlight .vg{color:#dfc06f}.highlight .vi{color:#dfc06f}.highlight .il{color:#d19a66}.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;color:#556366}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}.highlight pre{background-color:#282C34;color:#D7DAE0;border:none;margin-bottom:25px;font-size:15px;padding:20px 10px}.highlight{font-weight:700} \ No newline at end of file +.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background:#29314d;color:#01d7d4}.navbar>li>a{padding:0;margin:0}.navbar-nav>li>a{background:#29314d;padding:0 8px;margin:0;line-height:30px}.navbar,.navbar-default{background-color:#29314d;border:none;border-top:4px solid #01d7d4;padding-top:5px;color:#f6f9fa!important;font-weight:700;font-size:1.1em;text-transform:lowercase;margin-bottom:24px;height:30px;line-height:30px;z-index:10}.navbar .navbar-nav>li>a,.navbar-default .navbar-nav>li>a{color:#f6f9fa}.navbar .navbar-brand,.navbar-default .navbar-brand{font-weight:700;color:#01d7d4;line-height:30px;padding:0 30px;margin:0}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#01d7d4;background:#29314d}.navbar-toggle{margin-top:0}*{border-radius:0!important}html{position:relative;min-height:100%;color:#29314d}body{font-family:Roboto,Helvetica,Arial,sans-serif;margin-bottom:80px;background-color:#fff}body a{color:#08c}.footer{position:absolute;bottom:0;width:100%;height:60px;text-align:center;color:#999}input[type=checkbox]:checked+.label_hide_if_checked,input[type=checkbox]:checked+.label_hide_if_not_checked+.label_hide_if_checked{display:none}input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbox]:not(:checked)+.label_hide_if_checked+.label_hide_if_not_checked{display:none}.onoff-checkbox{width:15%}.onoffswitch{position:relative;width:110px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.onoffswitch-checkbox{display:none}.onoffswitch-label{display:block;overflow:hidden;cursor:pointer;border:2px solid #FFF!important;border-radius:50px!important}.onoffswitch-inner{display:block;transition:margin .3s ease-in 0s}.onoffswitch-inner:before,.onoffswitch-inner:after{display:block;float:left;width:50%;height:30px;padding:0;line-height:40px;font-size:20px;box-sizing:border-box;content:"";background-color:#EEE}.onoffswitch-switch{display:block;width:37px;background-color:#01d7d4;position:absolute;top:0;bottom:0;right:0;border:2px solid #FFF!important;border-radius:50px!important;transition:all .3s ease-in 0s}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-inner{margin-right:0}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-switch{right:71px;background-color:#A1A1A1}.result_header{margin-top:6px;margin-bottom:4px;font-size:16px}.result_header .favicon{margin-bottom:-3px}.result_header a{color:#29314d;text-decoration:none}.result_header a:hover{color:#08c}.result_header a:visited{color:#684898}.result_header a .highlight{background-color:#f6f9fa}.result-content{margin-top:2px;margin-bottom:0;word-wrap:break-word;color:#666;font-size:13px}.result-content .highlight{font-weight:700}.external-link,.external-link a{color:#2ecc71}.external-link a,.external-link a a{margin-right:3px}.result-default,.result-code,.result-torrent,.result-videos,.result-map{clear:both;padding:2px 4px}.result-default:hover,.result-code:hover,.result-torrent:hover,.result-videos:hover,.result-map:hover{background-color:#f6f9fa}.result-images{float:left!important;width:24%;margin:.5%}.result-images a{display:block;width:100%;background-size:cover}.img-thumbnail{margin:5px;max-height:128px;min-height:128px}.result-videos{clear:both}.result-videos hr{margin:5px 0 15px 0}.result-videos .collapse{width:100%}.result-videos .in{margin-bottom:8px}.result-torrent{clear:both}.result-torrent b{margin-right:5px;margin-left:5px}.result-torrent .seeders{color:#2ecc71}.result-torrent .leechers{color:#f35e77}.result-map{clear:both}.result-code{clear:both}.result-code .code-fork,.result-code .code-fork a{color:#666}.suggestion_item{margin:2px 5px}.result_download{margin-right:5px}#pagination{margin-top:30px;padding-bottom:60px}.label-default{color:#a4a4a4;background:0 0}.result .text-muted small{word-wrap:break-word}.modal-wrapper{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-wrapper{background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0 none;position:relative}.infobox .panel-heading{background-color:#f6f9fa}.infobox .panel-heading .panel-title{font-weight:700}.infobox p{font-family:"DejaVu Serif",Georgia,Cambria,"Times New Roman",Times,serif!important;font-style:italic}.infobox .btn{background-color:#2ecc71;border:none}.infobox .btn a{color:#fff;margin:5px}.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}.infobox .infobox_part:last-child{margin-bottom:0}.search_categories,#categories{text-transform:capitalize;margin-bottom:.5rem;display:flex;flex-wrap:wrap;flex-flow:row wrap;align-content:stretch}.search_categories label,#categories label,.search_categories .input-group-addon,#categories .input-group-addon{flex-grow:1;flex-basis:auto;font-size:1.2rem;font-weight:400;background-color:#fff;border:#ddd 1px solid;border-right:none;color:#666;padding-bottom:.4rem;padding-top:.4rem;text-align:center}.search_categories label:last-child,#categories label:last-child,.search_categories .input-group-addon:last-child,#categories .input-group-addon:last-child{border-right:#ddd 1px solid}.search_categories input[type=checkbox]:checked+label,#categories input[type=checkbox]:checked+label{color:#29314d;font-weight:700;border-bottom:#01d7d4 5px solid}#main-logo{margin-top:10vh;margin-bottom:25px}#main-logo>img{max-width:350px;width:80%}#q{box-shadow:none;border-right:none;border-color:#a4a4a4}#search_form .input-group-btn .btn{border-color:#a4a4a4}#search_form .input-group-btn .btn:hover{background-color:#2ecc71;color:#fff}#advanced-search-container{display:none;text-align:left;margin-bottom:1rem;clear:both}#advanced-search-container label,#advanced-search-container .input-group-addon{font-size:1.2rem;font-weight:400;background-color:#fff;border:#ddd 1px solid;border-right:none;color:#666;padding-bottom:.4rem;padding-right:.7rem;padding-left:.7rem}#advanced-search-container label:last-child,#advanced-search-container .input-group-addon:last-child{border-right:#ddd 1px solid}#advanced-search-container input[type=radio]{display:none}#advanced-search-container input[type=radio]:checked+label{color:#29314d;font-weight:700;border-bottom:#01d7d4 5px solid}#advanced-search-container select{appearance:none;-webkit-appearance:none;-moz-appearance:none;font-size:1.2rem;font-weight:400;background-color:#fff;border:#ddd 1px solid;color:#666;padding-bottom:.4rem;padding-top:.4rem;padding-left:1rem;padding-right:5rem;margin-right:.5rem;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAABFkAAARZAVnbJUkAAAAHdElNRQfgBxgLDwB20OFsAAAAbElEQVQY073OsQ3CMAAEwJMYwJGnsAehpoXJItltBkmcdZBYgIIiQoLglnz3ui+eP+bk5uneteTMZJa6OJuIqvYzSJoqwqBq8gdmTTW86/dghxAUq4xsVYT9laBYXCw93Aajh7GPEF23t4fkBYevGFTANkPRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA3LTI0VDExOjU1OjU4KzAyOjAwRFqFOQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wNy0yNFQxMToxNTowMCswMjowMP7RDgQAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC) 96% no-repeat}#check-advanced{display:none}#check-advanced:checked~#advanced-search-container{display:block}.advanced{padding:0;margin-top:.3rem;text-align:right}.advanced label,.advanced select{cursor:pointer}.cursor-text{cursor:text!important}.cursor-pointer{cursor:pointer!important}pre,code{font-family:'Ubuntu Mono','Courier New','Lucida Console',monospace!important}.lineno{margin-right:5px}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#556366;font-style:italic}.highlight .err{border:1px solid #ffa92f}.highlight .k{color:#BE74D5;font-weight:700}.highlight .o{color:#d19a66}.highlight .cm{color:#556366;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#556366;font-style:italic}.highlight .cs{color:#556366;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.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{color:#BE74D5;font-weight:700}.highlight .kd{color:#BE74D5;font-weight:700}.highlight .kn{color:#BE74D5;font-weight:700}.highlight .kp{color:#be74d5}.highlight .kr{color:#BE74D5;font-weight:700}.highlight .kt{color:#d46c72}.highlight .m{color:#d19a66}.highlight .s{color:#86c372}.highlight .na{color:#7d9029}.highlight .nb{color:#be74d5}.highlight .nc{color:#61AFEF;font-weight:700}.highlight .no{color:#d19a66}.highlight .nd{color:#a2f}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#61afef}.highlight .nl{color:#a0a000}.highlight .nn{color:#61AFEF;font-weight:700}.highlight .nt{color:#BE74D5;font-weight:700}.highlight .nv{color:#dfc06f}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#d7dae0}.highlight .mf{color:#d19a66}.highlight .mh{color:#d19a66}.highlight .mi{color:#d19a66}.highlight .mo{color:#d19a66}.highlight .sb{color:#86c372}.highlight .sc{color:#86c372}.highlight .sd{color:#86C372;font-style:italic}.highlight .s2{color:#86c372}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#86c372}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:#be74d5}.highlight .sr{color:#b68}.highlight .s1{color:#86c372}.highlight .ss{color:#dfc06f}.highlight .bp{color:#be74d5}.highlight .vc{color:#dfc06f}.highlight .vg{color:#dfc06f}.highlight .vi{color:#dfc06f}.highlight .il{color:#d19a66}.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;color:#556366}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}.highlight pre{background-color:#282C34;color:#D7DAE0;border:none;margin-bottom:25px;font-size:15px;padding:20px 10px}.highlight{font-weight:700} \ No newline at end of file diff --git a/sources/searx/static/themes/oscar/css/pointhi.min.css b/sources/searx/static/themes/oscar/css/pointhi.min.css index 29f7497..0c2472c 100644 --- a/sources/searx/static/themes/oscar/css/pointhi.min.css +++ b/sources/searx/static/themes/oscar/css/pointhi.min.css @@ -1 +1 @@ -html{position:relative;min-height:100%}body{margin-bottom:80px}.footer{position:absolute;bottom:0;width:100%;height:60px}input[type=checkbox]:checked+.label_hide_if_checked,input[type=checkbox]:checked+.label_hide_if_not_checked+.label_hide_if_checked{display:none}input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbox]:not(:checked)+.label_hide_if_checked+.label_hide_if_not_checked{display:none}.result_header{margin-bottom:5px;margin-top:20px}.result_header .favicon{margin-bottom:-3px}.result_header a{vertical-align:bottom}.result_header a .highlight{font-weight:700}.result-content{margin-top:5px;word-wrap:break-word}.result-content .highlight{font-weight:700}.result-default{clear:both}.result-images{float:left!important}.img-thumbnail{margin:5px;max-height:128px;min-height:128px}.result-videos{clear:both}.result-torrents{clear:both}.result-map{clear:both}.result-code{clear:both}.suggestion_item{margin:2px 5px}.result_download{margin-right:5px}#pagination{margin-top:30px;padding-bottom:50px}.label-default{color:#AAA;background:#FFF}.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}.infobox .infobox_part:last-child{margin-bottom:0}.search_categories{margin:10px 0;text-transform:capitalize}.cursor-text{cursor:text!important}.cursor-pointer{cursor:pointer!important}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.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{color:green;font-weight:700}.highlight .kd{color:green;font-weight:700}.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{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#ba2121}.highlight .sc{color:#ba2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{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{color:#19177c}.highlight .vg{color:#19177c}.highlight .vi{color:#19177c}.highlight .il{color:#666}.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} \ No newline at end of file +html{position:relative;min-height:100%}body{margin-bottom:80px}.footer{position:absolute;bottom:0;width:100%;height:60px}input[type=checkbox]:checked+.label_hide_if_checked,input[type=checkbox]:checked+.label_hide_if_not_checked+.label_hide_if_checked{display:none}input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbox]:not(:checked)+.label_hide_if_checked+.label_hide_if_not_checked{display:none}.onoff-checkbox{width:15%}.onoffswitch{position:relative;width:110px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.onoffswitch-checkbox{display:none}.onoffswitch-label{display:block;overflow:hidden;cursor:pointer;border:2px solid #FFF!important;border-radius:50px!important}.onoffswitch-inner{display:block;transition:margin .3s ease-in 0s}.onoffswitch-inner:before,.onoffswitch-inner:after{display:block;float:left;width:50%;height:30px;padding:0;line-height:40px;font-size:20px;box-sizing:border-box;content:"";background-color:#EEE}.onoffswitch-switch{display:block;width:37px;background-color:#0C0;position:absolute;top:0;bottom:0;right:0;border:2px solid #FFF!important;border-radius:50px!important;transition:all .3s ease-in 0s}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-inner{margin-right:0}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-switch{right:71px;background-color:#A1A1A1}.result_header{margin-bottom:5px;margin-top:20px}.result_header .favicon{margin-bottom:-3px}.result_header a{vertical-align:bottom}.result_header a .highlight{font-weight:700}.result-content{margin-top:5px;word-wrap:break-word}.result-content .highlight{font-weight:700}.result-default{clear:both}.result-images{float:left!important}.img-thumbnail{margin:5px;max-height:128px;min-height:128px}.result-videos{clear:both}.result-torrents{clear:both}.result-map{clear:both}.result-code{clear:both}.suggestion_item{margin:2px 5px}.result_download{margin-right:5px}#pagination{margin-top:30px;padding-bottom:50px}.label-default{color:#AAA;background:#FFF}.result .text-muted small{word-wrap:break-word}.modal-wrapper{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-wrapper{background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0 none;position:relative}.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}.infobox .infobox_part:last-child{margin-bottom:0}.search_categories,#categories{text-transform:capitalize;margin-bottom:1.5rem;margin-top:1.5rem;display:flex;flex-wrap:wrap;align-content:stretch}.search_categories label,#categories label,.search_categories .input-group-addon,#categories .input-group-addon{flex-grow:1;flex-basis:auto;font-size:1.3rem;font-weight:400;background-color:#fff;border:#DDD 1px solid;border-right:none;color:#333;padding-bottom:.8rem;padding-top:.8rem;text-align:center}.search_categories label:last-child,#categories label:last-child,.search_categories .input-group-addon:last-child,#categories .input-group-addon:last-child{border-right:#DDD 1px solid}.search_categories input[type=checkbox]:checked+label,#categories input[type=checkbox]:checked+label{color:#000;font-weight:700;background-color:#EEE}#advanced-search-container{display:none;text-align:center;margin-bottom:1rem;clear:both}#advanced-search-container label,#advanced-search-container .input-group-addon{font-size:1.3rem;font-weight:400;background-color:#fff;border:#DDD 1px solid;border-right:none;color:#333;padding-bottom:.8rem;padding-left:1.2rem;padding-right:1.2rem}#advanced-search-container label:last-child,#advanced-search-container .input-group-addon:last-child{border-right:#DDD 1px solid}#advanced-search-container input[type=radio]{display:none}#advanced-search-container input[type=radio]:checked+label{color:#000;font-weight:700;background-color:#EEE}#check-advanced{display:none}#check-advanced:checked~#advanced-search-container{display:block}.advanced{padding:0;margin-top:.3rem;text-align:right}.advanced label,.advanced select{cursor:pointer}.cursor-text{cursor:text!important}.cursor-pointer{cursor:pointer!important}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.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{color:green;font-weight:700}.highlight .kd{color:green;font-weight:700}.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{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#ba2121}.highlight .sc{color:#ba2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{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{color:#19177c}.highlight .vg{color:#19177c}.highlight .vi{color:#19177c}.highlight .il{color:#666}.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} \ No newline at end of file diff --git a/sources/searx/static/themes/oscar/js/searx.min.js b/sources/searx/static/themes/oscar/js/searx.min.js index 8a1055d..9bc4aae 100644 --- a/sources/searx/static/themes/oscar/js/searx.min.js +++ b/sources/searx/static/themes/oscar/js/searx.min.js @@ -1,2 +1,2 @@ -/*! oscar/searx.min.js | 09-01-2015 | https://github.com/asciimoo/searx */ -requirejs.config({baseUrl:"./static/themes/oscar/js",paths:{app:"../app"}}),searx.autocompleter&&(searx.searchResults=new Bloodhound({datumTokenizer:Bloodhound.tokenizers.obj.whitespace("value"),queryTokenizer:Bloodhound.tokenizers.whitespace,remote:"./autocompleter?q=%QUERY"}),searx.searchResults.initialize()),$(document).ready(function(){searx.autocompleter&&$("#q").typeahead(null,{name:"search-results",displayKey:function(a){return a},source:searx.searchResults.ttAdapter()})}),$(document).ready(function(){$("#q.autofocus").focus(),$(".select-all-on-click").click(function(){$(this).select()}),$(".btn-collapse").click(function(){var a=$(this).data("btn-text-collapsed"),b=$(this).data("btn-text-not-collapsed");""!==a&&""!==b&&(new_html=$(this).hasClass("collapsed")?$(this).html().replace(a,b):$(this).html().replace(b,a),$(this).html(new_html))}),$(".btn-toggle .btn").click(function(){var a="btn-"+$(this).data("btn-class"),b=$(this).data("btn-label-default"),c=$(this).data("btn-label-toggled");""!==c&&(new_html=$(this).hasClass("btn-default")?$(this).html().replace(b,c):$(this).html().replace(c,b),$(this).html(new_html)),$(this).toggleClass(a),$(this).toggleClass("btn-default")}),$(".media-loader").click(function(){var a=$(this).data("target"),b=$(a+" > iframe"),c=b.attr("src");(void 0===c||c===!1)&&b.attr("src",b.data("src"))}),$(".btn-sm").dblclick(function(){var a="btn-"+$(this).data("btn-class");$(this).hasClass("btn-default")?($(".btn-sm > input").attr("checked","checked"),$(".btn-sm > input").prop("checked",!0),$(".btn-sm").addClass(a),$(".btn-sm").addClass("active"),$(".btn-sm").removeClass("btn-default")):($(".btn-sm > input").attr("checked",""),$(".btn-sm > input").removeAttr("checked"),$(".btn-sm > input").checked=!1,$(".btn-sm").removeClass(a),$(".btn-sm").removeClass("active"),$(".btn-sm").addClass("btn-default"))})}),$(document).ready(function(){$(".searx_overpass_request").on("click",function(a){var b="https://overpass-api.de/api/interpreter?data=",c=b+"[out:json][timeout:25];(",d=");out meta;",e=$(this).data("osm-id"),f=$(this).data("osm-type"),g=$(this).data("result-table"),h="#"+$(this).data("result-table-loadicon"),i=["addr:city","addr:country","addr:housenumber","addr:postcode","addr:street"];if(e&&f&&g){g="#"+g;var j=null;switch(f){case"node":j=c+"node("+e+");"+d;break;case"way":j=c+"way("+e+");"+d;break;case"relation":j=c+"relation("+e+");"+d}if(j){$.ajax(j).done(function(a){if(a&&a.elements&&a.elements[0]){var b=a.elements[0],c=$(g).html();for(var d in b.tags)if(null===b.tags.name||-1==i.indexOf(d)){switch(c+=""+d+"",d){case"phone":case"fax":c+=''+b.tags[d]+"";break;case"email":c+=''+b.tags[d]+"";break;case"website":case"url":c+=''+b.tags[d]+"";break;case"wikidata":c+=''+b.tags[d]+"";break;case"wikipedia":if(-1!=b.tags[d].indexOf(":")){c+=''+b.tags[d]+"";break}default:c+=b.tags[d]}c+=""}$(g).html(c),$(g).removeClass("hidden"),$(h).addClass("hidden")}}).fail(function(){$(h).html($(h).html()+'

could not load data!

')})}}$(this).off(a)}),$(".searx_init_map").on("click",function(a){var b=$(this).data("leaflet-target"),c=$(this).data("map-lon"),d=$(this).data("map-lat"),e=$(this).data("map-zoom"),f=$(this).data("map-boundingbox"),g=$(this).data("map-geojson");require(["leaflet-0.7.3.min"],function(){f&&(southWest=L.latLng(f[0],f[2]),northEast=L.latLng(f[1],f[3]),map_bounds=L.latLngBounds(southWest,northEast)),L.Icon.Default.imagePath="./static/themes/oscar/img/map";{var a=L.map(b),h="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",i='Map data © OpenStreetMap contributors',j=new L.TileLayer(h,{minZoom:1,maxZoom:19,attribution:i}),k="http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg",l='Map data © OpenStreetMap contributors | Tiles Courtesy of MapQuest ',m=new L.TileLayer(k,{minZoom:1,maxZoom:18,subdomains:"1234",attribution:l}),n="http://otile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg",o='Map data © OpenStreetMap contributors | Tiles Courtesy of MapQuest | Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency';new L.TileLayer(n,{minZoom:1,maxZoom:11,subdomains:"1234",attribution:o})}map_bounds?setTimeout(function(){a.fitBounds(map_bounds,{maxZoom:17})},0):c&&d&&(e?a.setView(new L.LatLng(d,c),e):a.setView(new L.LatLng(d,c),8)),a.addLayer(m);var p={"OSM Mapnik":j,MapQuest:m};L.control.layers(p).addTo(a),g&&L.geoJson(g).addTo(a)}),$(this).off(a)})}); \ No newline at end of file +/*! oscar/searx.min.js | 25-07-2016 | https://github.com/asciimoo/searx */ +requirejs.config({baseUrl:"./static/themes/oscar/js",paths:{app:"../app"}}),searx.autocompleter&&(searx.searchResults=new Bloodhound({datumTokenizer:Bloodhound.tokenizers.obj.whitespace("value"),queryTokenizer:Bloodhound.tokenizers.whitespace,remote:"./autocompleter?q=%QUERY"}),searx.searchResults.initialize()),$(document).ready(function(){searx.autocompleter&&$("#q").typeahead(null,{name:"search-results",displayKey:function(a){return a},source:searx.searchResults.ttAdapter()})}),$(document).ready(function(){$("#q.autofocus").focus(),$(".select-all-on-click").click(function(){$(this).select()}),$(".btn-collapse").click(function(){var a=$(this).data("btn-text-collapsed"),b=$(this).data("btn-text-not-collapsed");""!==a&&""!==b&&($(this).hasClass("collapsed")?new_html=$(this).html().replace(a,b):new_html=$(this).html().replace(b,a),$(this).html(new_html))}),$(".btn-toggle .btn").click(function(){var a="btn-"+$(this).data("btn-class"),b=$(this).data("btn-label-default"),c=$(this).data("btn-label-toggled");""!==c&&($(this).hasClass("btn-default")?new_html=$(this).html().replace(b,c):new_html=$(this).html().replace(c,b),$(this).html(new_html)),$(this).toggleClass(a),$(this).toggleClass("btn-default")}),$(".media-loader").click(function(){var a=$(this).data("target"),b=$(a+" > iframe"),c=b.attr("src");void 0!==c&&c!==!1||b.attr("src",b.data("src"))}),$(".btn-sm").dblclick(function(){var a="btn-"+$(this).data("btn-class");$(this).hasClass("btn-default")?($(".btn-sm > input").attr("checked","checked"),$(".btn-sm > input").prop("checked",!0),$(".btn-sm").addClass(a),$(".btn-sm").addClass("active"),$(".btn-sm").removeClass("btn-default")):($(".btn-sm > input").attr("checked",""),$(".btn-sm > input").removeAttr("checked"),$(".btn-sm > input").checked=!1,$(".btn-sm").removeClass(a),$(".btn-sm").removeClass("active"),$(".btn-sm").addClass("btn-default"))})}),$(document).ready(function(){$(".searx_overpass_request").on("click",function(a){var b="https://overpass-api.de/api/interpreter?data=",c=b+"[out:json][timeout:25];(",d=");out meta;",e=$(this).data("osm-id"),f=$(this).data("osm-type"),g=$(this).data("result-table"),h="#"+$(this).data("result-table-loadicon"),i=["addr:city","addr:country","addr:housenumber","addr:postcode","addr:street"];if(e&&f&&g){g="#"+g;var j=null;switch(f){case"node":j=c+"node("+e+");"+d;break;case"way":j=c+"way("+e+");"+d;break;case"relation":j=c+"relation("+e+");"+d}if(j){$.ajax(j).done(function(a){if(a&&a.elements&&a.elements[0]){var b=a.elements[0],c=$(g).html();for(var d in b.tags)if(null===b.tags.name||i.indexOf(d)==-1){switch(c+=""+d+"",d){case"phone":case"fax":c+=''+b.tags[d]+"";break;case"email":c+=''+b.tags[d]+"";break;case"website":case"url":c+=''+b.tags[d]+"";break;case"wikidata":c+=''+b.tags[d]+"";break;case"wikipedia":if(b.tags[d].indexOf(":")!=-1){c+=''+b.tags[d]+"";break}default:c+=b.tags[d]}c+=""}$(g).html(c),$(g).removeClass("hidden"),$(h).addClass("hidden")}}).fail(function(){$(h).html($(h).html()+'

could not load data!

')})}}$(this).off(a)}),$(".searx_init_map").on("click",function(a){var b=$(this).data("leaflet-target"),c=$(this).data("map-lon"),d=$(this).data("map-lat"),e=$(this).data("map-zoom"),f=$(this).data("map-boundingbox"),g=$(this).data("map-geojson");require(["leaflet-0.7.3.min"],function(a){f&&(southWest=L.latLng(f[0],f[2]),northEast=L.latLng(f[1],f[3]),map_bounds=L.latLngBounds(southWest,northEast)),L.Icon.Default.imagePath="./static/themes/oscar/img/map";var h=L.map(b),i="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",j='Map data © OpenStreetMap contributors',k=new L.TileLayer(i,{minZoom:1,maxZoom:19,attribution:j}),l="https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png",m='Wikimedia maps beta | Maps data © OpenStreetMap contributors';new L.TileLayer(l,{minZoom:1,maxZoom:19,attribution:m});map_bounds?setTimeout(function(){h.fitBounds(map_bounds,{maxZoom:17})},0):c&&d&&(e?h.setView(new L.LatLng(d,c),e):h.setView(new L.LatLng(d,c),8)),h.addLayer(k);var n={"OSM Mapnik":k};L.control.layers(n).addTo(h),g&&L.geoJson(g).addTo(h)}),$(this).off(a)})}); \ No newline at end of file diff --git a/sources/searx/static/themes/oscar/js/searx_src/leaflet_map.js b/sources/searx/static/themes/oscar/js/searx_src/leaflet_map.js index cbcbe15..4be46ac 100644 --- a/sources/searx/static/themes/oscar/js/searx_src/leaflet_map.js +++ b/sources/searx/static/themes/oscar/js/searx_src/leaflet_map.js @@ -122,17 +122,13 @@ $(document).ready(function(){ var map = L.map(leaflet_target); // create the tile layer with correct attribution - var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; - var osmMapnikAttrib='Map data © OpenStreetMap contributors'; - var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib}); - - var osmMapquestUrl='http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg'; - var osmMapquestAttrib='Map data © OpenStreetMap contributors | Tiles Courtesy of MapQuest '; - var osmMapquest = new L.TileLayer(osmMapquestUrl, {minZoom: 1, maxZoom: 18, subdomains: '1234', attribution: osmMapquestAttrib}); - - var osmMapquestOpenAerialUrl='http://otile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg'; - var osmMapquestOpenAerialAttrib='Map data © OpenStreetMap contributors | Tiles Courtesy of MapQuest | Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency'; - var osmMapquestOpenAerial = new L.TileLayer(osmMapquestOpenAerialUrl, {minZoom: 1, maxZoom: 11, subdomains: '1234', attribution: osmMapquestOpenAerialAttrib}); + var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var osmMapnikAttrib='Map data © OpenStreetMap contributors'; + var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib}); + + var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png'; + var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © OpenStreetMap contributors'; + var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib}); // init map view if(map_bounds) { @@ -149,12 +145,11 @@ $(document).ready(function(){ map.setView(new L.LatLng(map_lat, map_lon),8); } - map.addLayer(osmMapquest); + map.addLayer(osmMapnik); var baseLayers = { - "OSM Mapnik": osmMapnik, - "MapQuest": osmMapquest/*, - "MapQuest Open Aerial": osmMapquestOpenAerial*/ + "OSM Mapnik": osmMapnik/*, + "OSM Wikimedia": osmWikimedia*/ }; L.control.layers(baseLayers).addTo(map); diff --git a/sources/searx/static/themes/oscar/less/logicodev/advanced.less b/sources/searx/static/themes/oscar/less/logicodev/advanced.less new file mode 100644 index 0000000..3b2cd3c --- /dev/null +++ b/sources/searx/static/themes/oscar/less/logicodev/advanced.less @@ -0,0 +1,72 @@ +#advanced-search-container { + display: none; + text-align: left; + margin-bottom: 1rem; + clear: both; + + label, .input-group-addon { + font-size: 1.2rem; + font-weight:normal; + background-color: white; + border: @mild-gray 1px solid; + border-right: none; + color: @dark-gray; + padding-bottom: 0.4rem; + padding-right: 0.7rem; + padding-left: 0.7rem; + } + + label:last-child, .input-group-addon:last-child { + border-right: @mild-gray 1px solid; + } + + input[type="radio"] { + display: none; + } + + input[type="radio"]:checked + label{ + color: @black; + font-weight: bold; + border-bottom: @light-green 5px solid; + } + select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + font-size: 1.2rem; + font-weight:normal; + background-color: white; + border: @mild-gray 1px solid; + color: @dark-gray; + padding-bottom: 0.4rem; + padding-top: 0.4rem; + padding-left: 1rem; + padding-right: 5rem; + margin-right: 0.5rem; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAABGdBTUEAALGPC/xhBQAAACBjSFJN +AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZ +cwAABFkAAARZAVnbJUkAAAAHdElNRQfgBxgLDwB20OFsAAAAbElEQVQY073OsQ3CMAAEwJMYwJGn +sAehpoXJItltBkmcdZBYgIIiQoLglnz3ui+eP+bk5uneteTMZJa6OJuIqvYzSJoqwqBq8gdmTTW8 +6/dghxAUq4xsVYT9laBYXCw93Aajh7GPEF23t4fkBYevGFTANkPRAAAAJXRFWHRkYXRlOmNyZWF0 +ZQAyMDE2LTA3LTI0VDExOjU1OjU4KzAyOjAwRFqFOQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0w +Ny0yNFQxMToxNTowMCswMjowMP7RDgQAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb +7jwaAAAAAElFTkSuQmCC) 96% no-repeat; + } +} + +#check-advanced { + display: none; +} + +#check-advanced:checked ~ #advanced-search-container { + display: block; +} + +.advanced { + padding: 0; + margin-top: 0.3rem; + text-align: right; + label, select { + cursor: pointer; + } +} diff --git a/sources/searx/static/themes/oscar/less/logicodev/navbar.less b/sources/searx/static/themes/oscar/less/logicodev/navbar.less index 493c9dc..2426210 100644 --- a/sources/searx/static/themes/oscar/less/logicodev/navbar.less +++ b/sources/searx/static/themes/oscar/less/logicodev/navbar.less @@ -39,7 +39,7 @@ padding: 0 30px; margin: 0; } - z-index: 1; + z-index: 10; } // Hover color diff --git a/sources/searx/static/themes/oscar/less/logicodev/onoff.less b/sources/searx/static/themes/oscar/less/logicodev/onoff.less new file mode 100644 index 0000000..f471892 --- /dev/null +++ b/sources/searx/static/themes/oscar/less/logicodev/onoff.less @@ -0,0 +1,57 @@ +.onoff-checkbox { + width:15%; +} +.onoffswitch { + position: relative; + width: 110px; + -webkit-user-select:none; + -moz-user-select:none; + -ms-user-select: none; +} +.onoffswitch-checkbox { + display: none; +} +.onoffswitch-label { + display: block; + overflow: hidden; + cursor: pointer; + border: 2px solid #FFFFFF !important; + border-radius: 50px !important; +} +.onoffswitch-inner { + display: block; + transition: margin 0.3s ease-in 0s; +} + +.onoffswitch-inner:before, .onoffswitch-inner:after { + display: block; + float: left; + width: 50%; + height: 30px; + padding: 0; + line-height: 40px; + font-size: 20px; + box-sizing: border-box; + content: ""; + background-color: #EEEEEE; +} + +.onoffswitch-switch { + display: block; + width: 37px; + background-color: @light-green; + position: absolute; + top: 0; + bottom: 0; + right: 0px; + border: 2px solid #FFFFFF !important; + border-radius: 50px !important; + transition: all 0.3s ease-in 0s; +} +.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { + margin-right: 0; +} +.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { + right: 71px; + background-color: #A1A1A1; +} diff --git a/sources/searx/static/themes/oscar/less/logicodev/oscar.less b/sources/searx/static/themes/oscar/less/logicodev/oscar.less index fba596c..55181cb 100644 --- a/sources/searx/static/themes/oscar/less/logicodev/oscar.less +++ b/sources/searx/static/themes/oscar/less/logicodev/oscar.less @@ -6,12 +6,16 @@ @import "checkbox.less"; +@import "onoff.less"; + @import "results.less"; @import "infobox.less"; @import "search.less"; +@import "advanced.less"; + @import "cursor.less"; @import "code.less"; diff --git a/sources/searx/static/themes/oscar/less/logicodev/results.less b/sources/searx/static/themes/oscar/less/logicodev/results.less index 37a5a76..9e57da7 100644 --- a/sources/searx/static/themes/oscar/less/logicodev/results.less +++ b/sources/searx/static/themes/oscar/less/logicodev/results.less @@ -6,7 +6,7 @@ .favicon { margin-bottom:-3px; } - + a { color: @black; text-decoration: none; @@ -18,7 +18,7 @@ &:visited{ color: @violet; } - + .highlight { background-color: @dim-gray; // Chrome hack: bold is different size than normal @@ -64,10 +64,9 @@ float: left !important; width: 24%; margin: .5%; - a{ + a { display: block; width: 100%; - height: 170px; background-size: cover; } } @@ -148,3 +147,21 @@ color: @gray; background: transparent; } + +.result .text-muted small { + word-wrap: break-word; +} + +.modal-wrapper { + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); +} + +.modal-wrapper { + background-clip: padding-box; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + outline: 0 none; + position: relative; +} diff --git a/sources/searx/static/themes/oscar/less/logicodev/search.less b/sources/searx/static/themes/oscar/less/logicodev/search.less index 1bb71a7..4ebfe88 100644 --- a/sources/searx/static/themes/oscar/less/logicodev/search.less +++ b/sources/searx/static/themes/oscar/less/logicodev/search.less @@ -1,36 +1,33 @@ .search_categories, #categories { - margin: 10px 0 4px 0; text-transform: capitalize; - - label{ - border: none; - box-shadow: none; - font-size: 13px; - padding-bottom: 2px; - color: @gray; - margin-bottom: 5px; + margin-bottom: 0.5rem; + display: flex; + flex-wrap: wrap; + flex-flow: row wrap; + align-content: stretch; - &:hover{ - color: @black; - background-color: transparent; - } - - &:active{ - box-shadow: none; - } + label, .input-group-addon { + flex-grow: 1; + flex-basis: auto; + font-size: 1.2rem; + font-weight: normal; + background-color: white; + border: @mild-gray 1px solid; + border-right: none; + color: @dark-gray; + padding-bottom: 0.4rem; + padding-top: 0.4rem; + text-align: center; + } + label:last-child, .input-group-addon:last-child { + border-right: @mild-gray 1px solid; } - .active, .btn-primary{ + input[type="checkbox"]:checked + label { color: @black; - font-weight: 700; - border-bottom: 5px solid @light-green; - background-color: transparent; + font-weight: bold; + border-bottom: @light-green 5px solid; } - -} - -#categories{ - margin: 0; } #main-logo{ diff --git a/sources/searx/static/themes/oscar/less/logicodev/variables.less b/sources/searx/static/themes/oscar/less/logicodev/variables.less index 3ca05e7..32d5bb9 100644 --- a/sources/searx/static/themes/oscar/less/logicodev/variables.less +++ b/sources/searx/static/themes/oscar/less/logicodev/variables.less @@ -2,7 +2,9 @@ @gray: #A4A4A4; @dim-gray: #F6F9FA; @dark-gray: #666; -@blue: #0088CC; +@middle-gray: #F5F5F5; +@mild-gray: #DDD; +@blue: #0088CC; @red: #F35E77; @violet: #684898; @green: #2ecc71; diff --git a/sources/searx/static/themes/oscar/less/pointhi/advanced.less b/sources/searx/static/themes/oscar/less/pointhi/advanced.less new file mode 100644 index 0000000..23bfdb0 --- /dev/null +++ b/sources/searx/static/themes/oscar/less/pointhi/advanced.less @@ -0,0 +1,49 @@ +#advanced-search-container { + display: none; + text-align: center; + margin-bottom: 1rem; + clear: both; + + label, .input-group-addon { + font-size: 1.3rem; + font-weight:normal; + background-color: white; + border: #DDD 1px solid; + border-right: none; + color: #333; + padding-bottom: 0.8rem; + padding-left: 1.2rem; + padding-right: 1.2rem; + } + + label:last-child, .input-group-addon:last-child { + border-right: #DDD 1px solid; + } + + input[type="radio"] { + display: none; + } + + input[type="radio"]:checked + label { + color: black; + font-weight: bold; + background-color: #EEE; + } +} + +#check-advanced { + display: none; +} + +#check-advanced:checked ~ #advanced-search-container { + display: block; +} + +.advanced { + padding: 0; + margin-top: 0.3rem; + text-align: right; + label, select { + cursor: pointer; + } +} diff --git a/sources/searx/static/themes/oscar/less/pointhi/onoff.less b/sources/searx/static/themes/oscar/less/pointhi/onoff.less new file mode 100644 index 0000000..72b289a --- /dev/null +++ b/sources/searx/static/themes/oscar/less/pointhi/onoff.less @@ -0,0 +1,57 @@ +.onoff-checkbox { + width:15%; +} +.onoffswitch { + position: relative; + width: 110px; + -webkit-user-select:none; + -moz-user-select:none; + -ms-user-select: none; +} +.onoffswitch-checkbox { + display: none; +} +.onoffswitch-label { + display: block; + overflow: hidden; + cursor: pointer; + border: 2px solid #FFFFFF !important; + border-radius: 50px !important; +} +.onoffswitch-inner { + display: block; + transition: margin 0.3s ease-in 0s; +} + +.onoffswitch-inner:before, .onoffswitch-inner:after { + display: block; + float: left; + width: 50%; + height: 30px; + padding: 0; + line-height: 40px; + font-size: 20px; + box-sizing: border-box; + content: ""; + background-color: #EEEEEE; +} + +.onoffswitch-switch { + display: block; + width: 37px; + background-color: #00CC00; + position: absolute; + top: 0; + bottom: 0; + right: 0px; + border: 2px solid #FFFFFF !important; + border-radius: 50px !important; + transition: all 0.3s ease-in 0s; +} +.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { + margin-right: 0; +} +.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { + right: 71px; + background-color: #A1A1A1; +} diff --git a/sources/searx/static/themes/oscar/less/pointhi/oscar.less b/sources/searx/static/themes/oscar/less/pointhi/oscar.less index 6a84785..ef69ace 100644 --- a/sources/searx/static/themes/oscar/less/pointhi/oscar.less +++ b/sources/searx/static/themes/oscar/less/pointhi/oscar.less @@ -2,12 +2,16 @@ @import "checkbox.less"; +@import "onoff.less"; + @import "results.less"; @import "infobox.less"; @import "search.less"; +@import "advanced.less"; + @import "cursor.less"; @import "code.less"; diff --git a/sources/searx/static/themes/oscar/less/pointhi/results.less b/sources/searx/static/themes/oscar/less/pointhi/results.less index b3d8700..beea353 100644 --- a/sources/searx/static/themes/oscar/less/pointhi/results.less +++ b/sources/searx/static/themes/oscar/less/pointhi/results.less @@ -6,10 +6,10 @@ .favicon { margin-bottom:-3px; } - + a { vertical-align: bottom; - + .highlight { font-weight:bold; } @@ -81,3 +81,21 @@ color: #AAA; background: #FFF; } + +.result .text-muted small { + word-wrap: break-word; +} + +.modal-wrapper { + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); +} + +.modal-wrapper { + background-clip: padding-box; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + outline: 0 none; + position: relative; +} diff --git a/sources/searx/static/themes/oscar/less/pointhi/search.less b/sources/searx/static/themes/oscar/less/pointhi/search.less index f95ab50..cea6799 100644 --- a/sources/searx/static/themes/oscar/less/pointhi/search.less +++ b/sources/searx/static/themes/oscar/less/pointhi/search.less @@ -1,4 +1,32 @@ -.search_categories { - margin:10px 0; - text-transform: capitalize; +.search_categories, #categories { + text-transform: capitalize; + margin-bottom: 1.5rem; + margin-top: 1.5rem; + display: flex; + flex-wrap: wrap; + align-content: stretch; + + label, .input-group-addon { + flex-grow: 1; + flex-basis: auto; + font-size: 1.3rem; + font-weight: normal; + background-color: white; + border: #DDD 1px solid; + border-right: none; + color: #333; + padding-bottom: 0.8rem; + padding-top: 0.8rem; + text-align: center; + } + + label:last-child, .input-group-addon:last-child { + border-right: #DDD 1px solid; + } + + input[type="checkbox"]:checked + label{ + color: black; + font-weight: bold; + background-color: #EEE; + } } diff --git a/sources/searx/templates/courgette/404.html b/sources/searx/templates/courgette/404.html new file mode 100644 index 0000000..77f1287 --- /dev/null +++ b/sources/searx/templates/courgette/404.html @@ -0,0 +1,9 @@ +{% extends "courgette/base.html" %} +{% block content %} +
+

{{ _('Page not found') }}

+ {% autoescape false %} +

{{ _('Go to %(search_page)s.', search_page='{}'.decode('utf-8').format(url_for('index'), _('search page'))) }}

+ {% endautoescape %} +
+{% endblock %} diff --git a/sources/searx/templates/courgette/about.html b/sources/searx/templates/courgette/about.html index 2945e1f..3855d46 100644 --- a/sources/searx/templates/courgette/about.html +++ b/sources/searx/templates/courgette/about.html @@ -6,20 +6,20 @@

Searx is a metasearch engine, aggregating the results of other search engines while not storing information about its users.

-

Why use Searx?

+

Why use searx?

    -
  • Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • -
  • Searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • -
  • Searx is free software, the code is 100% open and you can help to make it better. See more on github
  • +
  • searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • +
  • searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • +
  • searx is free software, the code is 100% open and you can help to make it better. See more on github

If you do care about privacy, want to be a conscious user, or otherwise believe - in digital freedom, make Searx your default search engine or run it on your own server

+ in digital freedom, make searx your default search engine or run it on your own server

Technical details - How does it work?

Searx is a metasearch engine, inspired by the seeks project.
-It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, Searx uses the search bar to perform GET requests.
+It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, searx uses the search bar to perform GET requests.
Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.

diff --git a/sources/searx/templates/courgette/base.html b/sources/searx/templates/courgette/base.html index 276fae8..b2c70a3 100644 --- a/sources/searx/templates/courgette/base.html +++ b/sources/searx/templates/courgette/base.html @@ -2,7 +2,7 @@ - + diff --git a/sources/searx/templates/courgette/result_templates/code.html b/sources/searx/templates/courgette/result_templates/code.html index 726f305..953617e 100644 --- a/sources/searx/templates/courgette/result_templates/code.html +++ b/sources/searx/templates/courgette/result_templates/code.html @@ -1,8 +1,8 @@
-

{% if result['favicon'] %}{{result['favicon']}}{% endif %}{{ result.title|safe }}

+

{% if result['favicon'] %}{{result['favicon']}}{% endif %}{{ result.title|safe }}

{% if result.publishedDate %}{{ result.publishedDate }}{% endif %}

{% if result.img_src %}{% endif %}{% if result.content %}{{ result.content|safe }}
{% endif %}

- {% if result.repository %}

{{ result.repository }}

{% endif %} + {% if result.repository %}

{{ result.repository }}

{% endif %}
{{ result.codelines|code_highlighter(result.code_language)|safe }}
diff --git a/sources/searx/templates/courgette/result_templates/default.html b/sources/searx/templates/courgette/result_templates/default.html index 585ecf3..5f2ead6 100644 --- a/sources/searx/templates/courgette/result_templates/default.html +++ b/sources/searx/templates/courgette/result_templates/default.html @@ -5,7 +5,7 @@ {% endif %}
-

{{ result.title|safe }}

+

{{ result.title|safe }}

{% if result.publishedDate %}{{ result.publishedDate }}{% endif %}

{% if result.content %}{{ result.content|safe }}
{% endif %}

{{ result.pretty_url }}‎

diff --git a/sources/searx/templates/courgette/result_templates/images.html b/sources/searx/templates/courgette/result_templates/images.html index 87fc774..49acb3b 100644 --- a/sources/searx/templates/courgette/result_templates/images.html +++ b/sources/searx/templates/courgette/result_templates/images.html @@ -1,6 +1,6 @@ diff --git a/sources/searx/templates/courgette/result_templates/map.html b/sources/searx/templates/courgette/result_templates/map.html index 585ecf3..5f2ead6 100644 --- a/sources/searx/templates/courgette/result_templates/map.html +++ b/sources/searx/templates/courgette/result_templates/map.html @@ -5,7 +5,7 @@ {% endif %}
-

{{ result.title|safe }}

+

{{ result.title|safe }}

{% if result.publishedDate %}{{ result.publishedDate }}{% endif %}

{% if result.content %}{{ result.content|safe }}
{% endif %}

{{ result.pretty_url }}‎

diff --git a/sources/searx/templates/courgette/result_templates/torrent.html b/sources/searx/templates/courgette/result_templates/torrent.html index 33b5742..2fd8395 100644 --- a/sources/searx/templates/courgette/result_templates/torrent.html +++ b/sources/searx/templates/courgette/result_templates/torrent.html @@ -2,12 +2,12 @@ {% if "icon_"~result.engine~".ico" in favicons %} {{result.engine}} {% endif %} -

{{ result.title|safe }}

+

{{ result.title|safe }}

{% if result.content %}{{ result.content|safe }}
{% endif %} {{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}
{% if result.magnetlink %}{{ _('magnet link') }}{% endif %} - {% if result.torrentfile %}{{ _('torrent file') }}{% endif %} + {% if result.torrentfile %}{{ _('torrent file') }}{% endif %}

{{ result.pretty_url }}‎

diff --git a/sources/searx/templates/courgette/result_templates/videos.html b/sources/searx/templates/courgette/result_templates/videos.html index ceed8b2..b3e19e0 100644 --- a/sources/searx/templates/courgette/result_templates/videos.html +++ b/sources/searx/templates/courgette/result_templates/videos.html @@ -3,8 +3,8 @@ {{result.engine}} {% endif %} -

{{ result.title|safe }}

+

{{ result.title|safe }}

{% if result.publishedDate %}{{ result.publishedDate }}
{% endif %} - {{ result.title|striptags }} + {{ result.title|striptags }}

{{ result.pretty_url }}‎

diff --git a/sources/searx/templates/default/404.html b/sources/searx/templates/default/404.html new file mode 100644 index 0000000..1d88f86 --- /dev/null +++ b/sources/searx/templates/default/404.html @@ -0,0 +1,9 @@ +{% extends "default/base.html" %} +{% block content %} +
+

{{ _('Page not found') }}

+ {% autoescape false %} +

{{ _('Go to %(search_page)s.', search_page='{}'.decode('utf-8').format(url_for('index'), _('search page'))) }}

+ {% endautoescape %} +
+{% endblock %} diff --git a/sources/searx/templates/default/about.html b/sources/searx/templates/default/about.html index 1b5fc34..f21a6f2 100644 --- a/sources/searx/templates/default/about.html +++ b/sources/searx/templates/default/about.html @@ -6,20 +6,20 @@

Searx is a metasearch engine, aggregating the results of other search engines while not storing information about its users.

-

Why use Searx?

+

Why use searx?

    -
  • Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • -
  • Searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • -
  • Searx is free software, the code is 100% open and you can help to make it better. See more on github
  • +
  • searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • +
  • searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • +
  • searx is free software, the code is 100% open and you can help to make it better. See more on github

If you do care about privacy, want to be a conscious user, or otherwise believe - in digital freedom, make Searx your default search engine or run it on your own server

+ in digital freedom, make searx your default search engine or run it on your own server

Technical details - How does it work?

Searx is a metasearch engine, inspired by the seeks project.
-It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, if Searx used from the search bar it performs GET requests.
+It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, if searx used from the search bar it performs GET requests.
Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.

diff --git a/sources/searx/templates/default/base.html b/sources/searx/templates/default/base.html index 143bdb8..a2c38fe 100644 --- a/sources/searx/templates/default/base.html +++ b/sources/searx/templates/default/base.html @@ -2,7 +2,7 @@ - + diff --git a/sources/searx/templates/default/infobox.html b/sources/searx/templates/default/infobox.html index 178a27e..4dd25fa 100644 --- a/sources/searx/templates/default/infobox.html +++ b/sources/searx/templates/default/infobox.html @@ -1,18 +1,18 @@
-

{{ infobox.infobox }}

+

{{ infobox.infobox }}

{% if infobox.img_src %}{{ infobox.infobox|striptags }}{% endif %} -

{{ infobox.entity }}

-

{{ infobox.content | safe }}

+

{{ infobox.entity }}

+

{{ infobox.content | safe }}

{% if infobox.attributes %}
{% for attribute in infobox.attributes %} - + {% if attribute.image %} {% else %} - + {% endif %} {% endfor %} @@ -24,7 +24,7 @@
@@ -34,7 +34,7 @@
{% for topic in infobox.relatedTopics %}
-

{{ topic.name }}

+

{{ topic.name }}

{% for suggestion in topic.suggestions %}
diff --git a/sources/searx/templates/default/preferences.html b/sources/searx/templates/default/preferences.html index a47dba4..1de9d22 100644 --- a/sources/searx/templates/default/preferences.html +++ b/sources/searx/templates/default/preferences.html @@ -80,6 +80,15 @@

+
+ {{ _('Results on new tabs') }} +

+ +

+
{{ _('Currently used search engines') }} diff --git a/sources/searx/templates/default/result_templates/code.html b/sources/searx/templates/default/result_templates/code.html index ad1d97e..9e3ed20 100644 --- a/sources/searx/templates/default/result_templates/code.html +++ b/sources/searx/templates/default/result_templates/code.html @@ -1,9 +1,9 @@
-

{% if result['favicon'] %}{{result['favicon']}}{% endif %}{{ result.title|safe }}

-

{{ result.pretty_url }}‎ {{ _('cached') }}

+

{% if result['favicon'] %}{{result['favicon']}}{% endif %}{{ result.title|safe }}

+

{{ result.pretty_url }}‎ {{ _('cached') }}

{% if result.publishedDate %}

{{ result.publishedDate }}

{% endif %}

{% if result.img_src %}{% endif %}{% if result.content %}{{ result.content|safe }}
{% endif %}

- {% if result.repository %}

{{ result.repository }}

{% endif %} + {% if result.repository %}

{{ result.repository }}

{% endif %}
{{ result.codelines|code_highlighter(result.code_language)|safe }} diff --git a/sources/searx/templates/default/result_templates/default.html b/sources/searx/templates/default/result_templates/default.html index 89091e2..da09117 100644 --- a/sources/searx/templates/default/result_templates/default.html +++ b/sources/searx/templates/default/result_templates/default.html @@ -1,6 +1,6 @@
-

{% if "icon_"~result.engine~".ico" in favicons %}{{result.engine}}{% endif %}{{ result.title|safe }}

-

{{ result.pretty_url }}‎ {{ _('cached') }} +

{% if "icon_"~result.engine~".ico" in favicons %}{{result.engine}}{% endif %}{{ result.title|safe }}

+

{{ result.pretty_url }}‎ {{ _('cached') }} {% if result.publishedDate %}{{ result.publishedDate }}{% endif %}

{% if result.img_src %}{% endif %}{% if result.content %}{{ result.content|safe }}
{% endif %}

diff --git a/sources/searx/templates/default/result_templates/images.html b/sources/searx/templates/default/result_templates/images.html index d85f841..00f62ab 100644 --- a/sources/searx/templates/default/result_templates/images.html +++ b/sources/searx/templates/default/result_templates/images.html @@ -1,6 +1,6 @@ diff --git a/sources/searx/templates/default/result_templates/map.html b/sources/searx/templates/default/result_templates/map.html index d413742..0200e0f 100644 --- a/sources/searx/templates/default/result_templates/map.html +++ b/sources/searx/templates/default/result_templates/map.html @@ -5,8 +5,8 @@ {% endif %}
-

{{ result.title|safe }}

-

{{ result.pretty_url }}‎ {{ _('cached') }} +

{{ result.title|safe }}

+

{{ result.pretty_url }}‎ {{ _('cached') }} {% if result.publishedDate %}{{ result.publishedDate }}{% endif %}

{% if result.img_src %}{% endif %}{% if result.content %}{{ result.content|safe }}
{% endif %}

diff --git a/sources/searx/templates/default/result_templates/torrent.html b/sources/searx/templates/default/result_templates/torrent.html index 4b2522a..935af4f 100644 --- a/sources/searx/templates/default/result_templates/torrent.html +++ b/sources/searx/templates/default/result_templates/torrent.html @@ -2,12 +2,12 @@ {% if "icon_"~result.engine~".ico" in favicons %} {{result.engine}} {% endif %} -

{{ result.title|safe }}

+

{{ result.title|safe }}

{{ result.pretty_url }}‎

{% if result.content %}

{{ result.content|safe }}

{% endif %}

{% if result.magnetlink %}{{ _('magnet link') }}{% endif %} - {% if result.torrentfile %}{{ _('torrent file') }}{% endif %} - + {% if result.torrentfile %}{{ _('torrent file') }}{% endif %} - {{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}

diff --git a/sources/searx/templates/default/result_templates/videos.html b/sources/searx/templates/default/result_templates/videos.html index 5a377b7..727f44c 100644 --- a/sources/searx/templates/default/result_templates/videos.html +++ b/sources/searx/templates/default/result_templates/videos.html @@ -1,6 +1,6 @@
-

{% if "icon_"~result.engine~".ico" in favicons %}{{result.engine}}{% endif %}{{ result.title|safe }}

+

{% if "icon_"~result.engine~".ico" in favicons %}{{result.engine}}{% endif %}{{ result.title|safe }}

{% if result.publishedDate %}{{ result.publishedDate }}
{% endif %} - {{ result.title|striptags }} + {{ result.title|striptags }}

{{ result.url }}‎

diff --git a/sources/searx/templates/oscar/404.html b/sources/searx/templates/oscar/404.html new file mode 100644 index 0000000..11d7895 --- /dev/null +++ b/sources/searx/templates/oscar/404.html @@ -0,0 +1,9 @@ +{% extends "oscar/base.html" %} +{% block content %} +
+

{{ _('Page not found') }}

+ {% autoescape false %} +

{{ _('Go to %(search_page)s.', search_page='{}'.decode('utf-8').format(url_for('index'), _('search page'))) }}

+ {% endautoescape %} +
+{% endblock %} diff --git a/sources/searx/templates/oscar/about.html b/sources/searx/templates/oscar/about.html index e1f3782..d42b783 100644 --- a/sources/searx/templates/oscar/about.html +++ b/sources/searx/templates/oscar/about.html @@ -7,27 +7,27 @@

Searx is a metasearch engine, aggregating the results of other search engines while not storing information about its users.

-

Why use Searx?

+

Why use searx?

    -
  • Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • -
  • Searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • -
  • Searx is free software, the code is 100% open and you can help to make it better. See more on github
  • +
  • searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • +
  • searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • +
  • searx is free software, the code is 100% open and you can help to make it better. See more on github

If you do care about privacy, want to be a conscious user, or otherwise believe - in digital freedom, make Searx your default search engine or run it on your own server

+ in digital freedom, make searx your default search engine or run it on your own server

Technical details - How does it work?

Searx is a metasearch engine, inspired by the seeks project.
-It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, Searx uses the search bar to perform GET requests.
+It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, searx uses the search bar to perform GET requests.
Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.

How can I make it my own?

Searx appreciates your concern regarding logs, so take the code and run it yourself!
Add your Searx to this list to help other people reclaim their privacy and make the Internet freer! -
The more decentralized the Internet, is the more freedom we have!

+
The more decentralized the Internet is, the more freedom we have!

More about searx

diff --git a/sources/searx/templates/oscar/advanced.html b/sources/searx/templates/oscar/advanced.html new file mode 100644 index 0000000..2c694cf --- /dev/null +++ b/sources/searx/templates/oscar/advanced.html @@ -0,0 +1,9 @@ + + +
+ {% include 'oscar/categories.html' %} + {% include 'oscar/time-range.html' %} +
diff --git a/sources/searx/templates/oscar/base.html b/sources/searx/templates/oscar/base.html index 649d91f..d3170d6 100644 --- a/sources/searx/templates/oscar/base.html +++ b/sources/searx/templates/oscar/base.html @@ -2,7 +2,7 @@ - + @@ -90,8 +90,5 @@ {% for script in scripts %} {% endfor %} - diff --git a/sources/searx/templates/oscar/categories.html b/sources/searx/templates/oscar/categories.html index 834cffc..241d262 100644 --- a/sources/searx/templates/oscar/categories.html +++ b/sources/searx/templates/oscar/categories.html @@ -1,42 +1,14 @@ - -
{{ attribute.label }}{{ attribute.label }}{{ attribute.image.alt }}{{ attribute.value }}{{ attribute.value }}
{% for attribute in infobox.attributes %} - + {% if attribute.image %} {% else %} - + {% endif %} {% endfor %} @@ -24,11 +24,12 @@ {% if infobox.urls %}
+ {% for url in infobox.urls %} -

{{ url.title }}

+

{{ result_link(url.url, url.title) }}

{% endfor %} +
{% endif %} - diff --git a/sources/searx/templates/oscar/macros.html b/sources/searx/templates/oscar/macros.html index a826b0e..06881db 100644 --- a/sources/searx/templates/oscar/macros.html +++ b/sources/searx/templates/oscar/macros.html @@ -9,16 +9,20 @@ {{ favicon }} {%- endmacro %} +{%- macro result_link(url, title, classes='') -%} +{{ title }} +{%- endmacro -%} + -{% macro result_header(result, favicons) -%} -

{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result.title|safe }}

+{% macro result_header(result, favicons) -%} +

{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result_link(result.url, result.title|safe) }}

{%- endmacro %} {% macro result_sub_header(result) -%} {% if result.publishedDate %}{% endif %} - {% if result.magnetlink %}{{ icon('magnet') }} {{ _('magnet link') }}{% endif %} - {% if result.torrentfile %}{{ icon('download-alt') }} {{ _('torrent file') }}{% endif %} + {% if result.magnetlink %} • {{ result_link(result.magnetlink, icon('magnet') + _('magnet link'), "magnetlink") }}{% endif %} + {% if result.torrentfile %} • {{ result_link(result.torrentfile, icon('download-alt') + _('torrent file'), "torrentfile") }}{% endif %} {%- endmacro %} @@ -28,7 +32,7 @@ {% for engine in result.engines %} {{ engine }} {% endfor %} - {{ icon('link') }} {{ _('cached') }} + {{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}
{{ result.pretty_url }}
{%- endmacro %} @@ -39,7 +43,7 @@ {% for engine in result.engines %} {{ engine }} {% endfor %} - {{ icon('link') }} {{ _('cached') }} + {{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}
{{ result.pretty_url }}
{%- endmacro %} @@ -68,9 +72,11 @@ {%- endmacro %} {% macro checkbox_toggle(id, blocked) -%} -
- - - +
+ +
{%- endmacro %} diff --git a/sources/searx/templates/oscar/preferences.html b/sources/searx/templates/oscar/preferences.html index 18308bd..283b7ba 100644 --- a/sources/searx/templates/oscar/preferences.html +++ b/sources/searx/templates/oscar/preferences.html @@ -36,7 +36,7 @@ {% else %} -
+
{% include 'oscar/categories.html' %}
{% endif %} @@ -117,6 +117,15 @@ {{ preferences_item_footer(_('Choose style for this theme'), _('Style'), rtl) }} + + {% set label = _('Results on new tabs') %} + {% set info = _('Open result links on new browser tabs') %} + {{ preferences_item_header(info, label, rtl) }} + + {{ preferences_item_footer(info, label, rtl) }}
@@ -164,7 +173,9 @@ {% if not search_engine.private %} {% if not rtl %} - + @@ -176,7 +187,9 @@ - + {% endif %} {% endif %} @@ -203,7 +216,9 @@
{{ _(plugin.description) }}
+
{{ checkbox_toggle('plugin_' + plugin.id, plugin.id not in allowed_plugins) }} +
diff --git a/sources/searx/templates/oscar/result_templates/code.html b/sources/searx/templates/oscar/result_templates/code.html index 582a214..ba74d03 100644 --- a/sources/searx/templates/oscar/result_templates/code.html +++ b/sources/searx/templates/oscar/result_templates/code.html @@ -5,7 +5,7 @@ {% if result.content %}

{{ result.content|safe }}

{% endif %} -{% if result.repository %}

{{ icon('file') }} {{ result.repository }}

{% endif %} +{% if result.repository %}

{{ icon('file') }} {{ result.repository }}

{% endif %}
{{ result.codelines|code_highlighter(result.code_language)|safe }} diff --git a/sources/searx/templates/oscar/result_templates/default.html b/sources/searx/templates/oscar/result_templates/default.html index f283693..3ed0f31 100644 --- a/sources/searx/templates/oscar/result_templates/default.html +++ b/sources/searx/templates/oscar/result_templates/default.html @@ -1,4 +1,4 @@ -{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %} +{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon with context %} {{ result_header(result, favicons) }} {{ result_sub_header(result) }} diff --git a/sources/searx/templates/oscar/result_templates/images.html b/sources/searx/templates/oscar/result_templates/images.html index 1bfff0a..812749c 100644 --- a/sources/searx/templates/oscar/result_templates/images.html +++ b/sources/searx/templates/oscar/result_templates/images.html @@ -1,12 +1,12 @@ {% from 'oscar/macros.html' import draw_favicon %} - + {{ result.title|striptags }} @@ -118,7 +122,7 @@
- 1 %}&pageno={{ pageno }}{% endif %}" readonly> + 1 %}&pageno={{ pageno }}{% endif %}{% if time_range %}&time_range={{ time_range }}{% endif %}" readonly>
@@ -130,6 +134,7 @@ {% for category in selected_categories %}{% endfor %} + {% endfor %} diff --git a/sources/searx/templates/oscar/search.html b/sources/searx/templates/oscar/search.html index e48c80f..6a3b2d2 100644 --- a/sources/searx/templates/oscar/search.html +++ b/sources/searx/templates/oscar/search.html @@ -6,7 +6,7 @@
-
- {% include 'oscar/categories.html' %} -
+
+ {% include 'oscar/advanced.html' %} +
diff --git a/sources/searx/templates/oscar/search_full.html b/sources/searx/templates/oscar/search_full.html index 26dae41..6fdae40 100644 --- a/sources/searx/templates/oscar/search_full.html +++ b/sources/searx/templates/oscar/search_full.html @@ -11,11 +11,8 @@ +
+ {% include 'oscar/advanced.html' %} +
- -
-
- {% include 'oscar/categories.html' %} -
-
diff --git a/sources/searx/templates/oscar/time-range.html b/sources/searx/templates/oscar/time-range.html new file mode 100644 index 0000000..4a13c4f --- /dev/null +++ b/sources/searx/templates/oscar/time-range.html @@ -0,0 +1,14 @@ + diff --git a/sources/searx/templates/pix-art/404.html b/sources/searx/templates/pix-art/404.html new file mode 100644 index 0000000..592e861 --- /dev/null +++ b/sources/searx/templates/pix-art/404.html @@ -0,0 +1,9 @@ +{% extends "pix-art/base.html" %} +{% block content %} +
+

{{ _('Page not found') }}

+ {% autoescape false %} +

{{ _('Go to %(search_page)s.', search_page='{}'.decode('utf-8').format(url_for('index'), _('search page'))) }}

+ {% endautoescape %} +
+{% endblock %} diff --git a/sources/searx/templates/pix-art/about.html b/sources/searx/templates/pix-art/about.html index cb4b351..6484b85 100644 --- a/sources/searx/templates/pix-art/about.html +++ b/sources/searx/templates/pix-art/about.html @@ -5,20 +5,20 @@

Searx is a metasearch engine, aggregating the results of other search engines while not storing information about its users.

-

Why use Searx?

+

Why use searx?

    -
  • Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • -
  • Searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • -
  • Searx is free software, the code is 100% open and you can help to make it better. See more on github
  • +
  • searx may not offer you as personalised results as Google, but it doesn't generate a profile about you
  • +
  • searx doesn't care about what you search for, never shares anything with a third party, and it can't be used to compromise you
  • +
  • searx is free software, the code is 100% open and you can help to make it better. See more on github

If you do care about privacy, want to be a conscious user, or otherwise believe - in digital freedom, make Searx your default search engine or run it on your own server

+ in digital freedom, make searx your default search engine or run it on your own server

Technical details - How does it work?

Searx is a metasearch engine, inspired by the seeks project.
-It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, if Searx used from the search bar it performs GET requests.
+It provides basic privacy by mixing your queries with searches on other platforms without storing search data. Queries are made using a POST request on every browser (except chrome*). Therefore they show up in neither our logs, nor your url history. In case of Chrome* users there is an exception, if searx used from the search bar it performs GET requests.
Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.

diff --git a/sources/searx/templates/pix-art/base.html b/sources/searx/templates/pix-art/base.html index 578180c..6af8823 100644 --- a/sources/searx/templates/pix-art/base.html +++ b/sources/searx/templates/pix-art/base.html @@ -2,7 +2,7 @@ - + diff --git a/sources/searx/templates/pix-art/index.html b/sources/searx/templates/pix-art/index.html index d398cc8..a0c61f9 100644 --- a/sources/searx/templates/pix-art/index.html +++ b/sources/searx/templates/pix-art/index.html @@ -1,7 +1,7 @@ {% extends "pix-art/base.html" %} {% block content %}
-

Searx Logo

+

searx Logo

{% include 'pix-art/search.html' %}

{{ _('about') }} diff --git a/sources/searx/templates/pix-art/results.html b/sources/searx/templates/pix-art/results.html index 9385b60..f7d0e20 100644 --- a/sources/searx/templates/pix-art/results.html +++ b/sources/searx/templates/pix-art/results.html @@ -8,7 +8,7 @@ {% block title %}{{ q }} - {% endblock %} {% block meta %}{% endblock %} {% block content %} -

+
{{ attribute.label }}{{ attribute.label }}{{ attribute.image.alt }}{{ attribute.value }}{{ attribute.value }}
{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }} + {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }} + {{ search_engine.name }} {{ shortcuts[search_engine.name] }} {{ shortcuts[search_engine.name] }} {{ search_engine.name }}{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }} + {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }} +
+ +
+
+ The Big Bang Theory + 4 hours ago +
+
+
    +
  • The Big Bang Theory 2.9 GB
  • +
  • ....
  • +
+
+
+ Files: 1 Size: 2.9 GB Downloads: 1 Updated: 4 hours ago +     + + magnet-link + +     +
+
+ """ + response = mock.Mock(content=html) + results = digbt.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['title'], 'The Big Bang Theory') + self.assertEqual(results[0]['url'], 'https://digbt.org/The-Big-Bang-Theory-d2.html') + self.assertEqual(results[0]['content'], 'The Big Bang Theory 2.9 GB ....') + self.assertEqual(results[0]['filesize'], 3113851289) + self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:a&dn=The+Big+Bang+Theory') diff --git a/sources/tests/unit/engines/test_duckduckgo.py b/sources/tests/unit/engines/test_duckduckgo.py index 8f99dc9..cb866d3 100644 --- a/sources/tests/unit/engines/test_duckduckgo.py +++ b/sources/tests/unit/engines/test_duckduckgo.py @@ -11,16 +11,13 @@ class TestDuckduckgoEngine(SearxTestCase): query = 'test_query' dicto = defaultdict(dict) dicto['pageno'] = 1 - dicto['language'] = 'fr_FR' + dicto['language'] = 'de_CH' + dicto['time_range'] = '' params = duckduckgo.request(query, dicto) self.assertIn('url', params) self.assertIn(query, params['url']) self.assertIn('duckduckgo.com', params['url']) - self.assertIn('fr-fr', params['url']) - - dicto['language'] = 'all' - params = duckduckgo.request(query, dicto) - self.assertIn('en-us', params['url']) + self.assertIn('ch-de', params['url']) def test_response(self): self.assertRaises(AttributeError, duckduckgo.response, None) diff --git a/sources/tests/unit/engines/test_flickr.py b/sources/tests/unit/engines/test_flickr.py index 8b39e92..2d7472a 100644 --- a/sources/tests/unit/engines/test_flickr.py +++ b/sources/tests/unit/engines/test_flickr.py @@ -27,7 +27,7 @@ class TestFlickrEngine(SearxTestCase): response = mock.Mock(text='{"data": []}') self.assertEqual(flickr.response(response), []) - json = """ + json = r""" { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032", "photo": [ { "id": "15751017054", "owner": "66847915@N08", @@ -55,7 +55,7 @@ class TestFlickrEngine(SearxTestCase): self.assertTrue('Owner' in results[0]['content']) self.assertTrue('Description' in results[0]['content']) - json = """ + json = r""" { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032", "photo": [ { "id": "15751017054", "owner": "66847915@N08", @@ -79,7 +79,7 @@ class TestFlickrEngine(SearxTestCase): self.assertTrue('Owner' in results[0]['content']) self.assertTrue('Description' in results[0]['content']) - json = """ + json = r""" { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032", "photo": [ { "id": "15751017054", "owner": "66847915@N08", @@ -103,7 +103,7 @@ class TestFlickrEngine(SearxTestCase): self.assertTrue('Owner' in results[0]['content']) self.assertTrue('Description' in results[0]['content']) - json = """ + json = r""" { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032", "photo": [ { "id": "15751017054", "owner": "66847915@N08", @@ -130,7 +130,7 @@ class TestFlickrEngine(SearxTestCase): self.assertEqual(type(results), list) self.assertEqual(len(results), 0) - json = """ + json = r""" {"toto":[ {"id":200,"name":"Artist Name", "link":"http:\/\/www.flickr.com\/artist\/1217","type":"artist"} diff --git a/sources/tests/unit/engines/test_flickr_noapi.py b/sources/tests/unit/engines/test_flickr_noapi.py index 3b337a2..42f38f9 100644 --- a/sources/tests/unit/engines/test_flickr_noapi.py +++ b/sources/tests/unit/engines/test_flickr_noapi.py @@ -316,7 +316,7 @@ class TestFlickrNoapiEngine(SearxTestCase): self.assertEqual(len(results), 0) # garbage test - json = """ + json = r""" {"toto":[ {"id":200,"name":"Artist Name", "link":"http:\/\/www.flickr.com\/artist\/1217","type":"artist"} diff --git a/sources/tests/unit/engines/test_google.py b/sources/tests/unit/engines/test_google.py index 37a83ca..8e73e2a 100644 --- a/sources/tests/unit/engines/test_google.py +++ b/sources/tests/unit/engines/test_google.py @@ -19,6 +19,7 @@ class TestGoogleEngine(SearxTestCase): dicto = defaultdict(dict) dicto['pageno'] = 1 dicto['language'] = 'fr_FR' + dicto['time_range'] = '' params = google.request(query, dicto) self.assertIn('url', params) self.assertIn(query, params['url']) diff --git a/sources/tests/unit/engines/test_google_images.py b/sources/tests/unit/engines/test_google_images.py index 5f184e0..493741c 100644 --- a/sources/tests/unit/engines/test_google_images.py +++ b/sources/tests/unit/engines/test_google_images.py @@ -11,10 +11,10 @@ class TestGoogleImagesEngine(SearxTestCase): dicto = defaultdict(dict) dicto['pageno'] = 1 dicto['safesearch'] = 1 + dicto['time_range'] = '' params = google_images.request(query, dicto) self.assertIn('url', params) self.assertIn(query, params['url']) - self.assertIn('safe=active', params['url']) dicto['safesearch'] = 0 params = google_images.request(query, dicto) @@ -26,33 +26,17 @@ class TestGoogleImagesEngine(SearxTestCase): self.assertRaises(AttributeError, google_images.response, '') self.assertRaises(AttributeError, google_images.response, '[]') - response = mock.Mock(text='
') - self.assertEqual(google_images.response(response), []) - - html = """ -
- -
-
- Image result for south -
-
504 × 598 - clker.com -
-
-
-
- {"id":"bQWQ9wz9loJmjM:","isu":"clker.com","ity":"png","md":"/search?tbs\u003dsbi:AMhZZit7u1mHyop9pQisu-5idR-8W_1Itvwc3afChmsjQYPx_1yYMzBvUZgtkcGoojqekKZ-6n_1rjX9ySH0OWA_1eO5OijFY6BBDw_1GApr6xxb1bXJcBcj-DiguMoXWW7cZSG7MRQbwnI5SoDZNXcv_1xGszy886I7NVb_1oRKSliTHtzqbXAxhvYreM","msu":"/search?q\u003dsouth\u0026biw\u003d1364\u0026bih\u003d235\u0026tbm\u003disch\u0026tbs\u003dsimg:CAQSEgltBZD3DP2WgiG-U42R4G0RFw","oh":598,"os":"13KB","ow":504,"pt":"South Arrow Clip Art at Clker.com - vector clip art online ...","rid":"vlONkeBtERfDuM","s":"Download this image as:","sc":1,"si":"/search?q\u003dsouth\u0026biw\u003d1364\u0026bih\u003d235\u0026tbm\u003disch\u0026tbs\u003dsimg:CAESEgltBZD3DP2WgiG-U42R4G0RFw","th":245,"tu":"https://thumbnail.url/","tw":206,"ru":"a","ou":"b"} -
-
-
-
- """ # noqa + html = r""" +["rg_s",["dom","\u003Cstyle\u003E.rg_kn,.rg_s{}.rg_bx{display:-moz-inline-box;display:inline-block;margin-top:0;margin-right:12px;margin-bottom:12px;margin-left:0;overflow:hidden;position:relative;vertical-align:top;z-index:1}.rg_meta{display:none}.rg_l{display:inline-block;height:100%;position:absolute;text-decoration:none;width:100%}.rg_l:focus{outline:0}.rg_i{border:0;color:rgba(0,0,0,0);display:block;-webkit-touch-callout:none;}.rg_an,.rg_anbg,.rg_ilm,.rg_ilmbg{right:0;bottom:0;box-sizing:border-box;-moz-box-sizing:border-box;color:#fff;font:normal 11px arial,sans-serif;line-height:100%;white-space:nowrap;width:100%}.rg_anbg,.rg_ilmbg{background:rgba(51,51,51,0.8);margin-left:0;padding:2px 4px;position:absolute}.rg_ilmn{bottom:0;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rg_ilm{display:none}#rg_s.rg_kn .rg_l:focus .rg_ilm{display:block}.rg_kn .rg_bx:hover .rg_ilm,.rg_bx:hover .rg_anbg{display:none}.rg_bx:hover .rg_ilm,.rg_anbg,.rg_kn .rg_bx:hover .rg_anbg{display:block}\u003C\/style\u003E\u003Cdiv eid=\"qlKuV-T3BoqksAHMnaroAw\" id=\"isr_scm_0\" style=\"display:none\"\u003E\u003C\/div\u003E\u003Cdiv data-cei=\"qlKuV-T3BoqksAHMnaroAw\" class=\"rg_add_chunk\"\u003E\u003C!--m--\u003E\u003Cdiv class=\"rg_di rg_bx rg_el ivg-i\" data-ved=\"0ahUKEwjk9PCm-7zOAhUKEiwKHcyOCj0QMwgCKAAwAA\"\u003E\u003Ca jsaction=\"fire.ivg_o;mouseover:str.hmov;mouseout:str.hmou\" class=\"rg_l\" style=\"background:rgb(170,205,240)\"\u003E\u003Cimg data-sz=\"f\" name=\"5eykIeMjmCk7xM:\" src=\"https:\/\/encrypted-tbn0.gstatic.com\/images?q=tbn\" class=\"rg_i rg_ic\" alt=\"Image result for south\" jsaction=\"load:str.tbn\" onload=\"google.aft\u0026\u0026google.aft(this)\"\u003E\u003Cdiv class=\"_aOd rg_ilm\"\u003E\u003Cdiv class=\"rg_ilmbg\"\u003E\u003Cspan class=\"rg_ilmn\"\u003E 566\u0026nbsp;\u0026#215;\u0026nbsp;365 - en.wikipedia.org \u003C\/span\u003E\u003C\/div\u003E\u003C\/div\u003E\u003C\/a\u003E\u003Cdiv class=\"rg_meta\"\u003E{\"id\":\"5eykIeMjmCk7xM:\",\"isu\":\"en.wikipedia.org\",\"itg\":false,\"ity\":\"png\",\"oh\":365,\"ou\":\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/e\/e4\/Us_south_census.png\",\"ow\":566,\"pt\":\"Southern United States - Wikipedia, the free encyclopedia\",\"rid\":\"cErfE02-v-VcAM\",\"ru\":\"https:\/\/en.wikipedia.org\/wiki\/Southern_United_States\",\"s\":\"The Southern United States as defined by the United States Census Bureau.\",\"sc\":1,\"th\":180,\"tu\":\"https:\/\/encrypted-tbn0.gstatic.com\/images?q\\u003dtbn\",\"tw\":280}\u003C\/div\u003E\u003C\/div\u003E\u003C!--n--\u003E\u003C!--m--\u003E\u003Cdiv class=\"rg_di rg_bx rg_el ivg-i\" data-ved=\"0ahUKEwjk9PCm-7zOAhUKEiwKHcyOCj0QMwgDKAEwAQ\"\u003E\u003Ca jsaction=\"fire.ivg_o;mouseover:str.hmov;mouseout:str.hmou\" class=\"rg_l\" style=\"background:rgb(249,252,249)\"\u003E\u003Cimg data-sz=\"f\" name=\"eRjGCc0cFyVkKM:\" src=\"https:\/\/encrypted-tbn2.gstatic.com\/images?q=tbn:ANd9GcSI7SZlbDwdMCgGXzJkpwgdn9uL41xUJ1IiIcKs0qW43_Yp0EhEsg\" class=\"rg_i rg_ic\" alt=\"Image result for south\" jsaction=\"load:str.tbn\" onload=\"google.aft\u0026\u0026google.aft(this)\"\u003E\u003Cdiv class=\"_aOd rg_ilm\"\u003E\u003Cdiv class=\"rg_ilmbg\"\u003E\u003Cspan class=\"rg_ilmn\"\u003E 2000\u0026nbsp;\u0026#215;\u0026nbsp;1002 - commons.wikimedia.org \u003C\/span\u003E\u003C\/div\u003E\u003C\/div\u003E\u003C\/a\u003E\u003Cdiv class=\"rg_meta\"\u003E{\"id\":\"eRjGCc0cFyVkKM:\",\"isu\":\"commons.wikimedia.org\",\"itg\":false,\"ity\":\"png\",\"oh\":1002,\"ou\":\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/thumb\/8\/84\/South_plate.svg\/2000px-South_plate.svg.png\",\"ow\":2000,\"pt\":\"File:South plate.svg - Wikimedia Commons\",\"rid\":\"F8TVsT2GBLb6RM\",\"ru\":\"https:\/\/commons.wikimedia.org\/wiki\/File:South_plate.svg\",\"s\":\"This image rendered as PNG in other widths: 200px, 500px, 1000px, 2000px.\",\"sc\":1,\"th\":159,\"tu\":\"https:\/\/encrypted-tbn2.gstatic.com\/images?q\\u003dtbn:ANd9GcSI7SZlbDwdMCgGXzJkpwgdn9uL41xUJ1IiIcKs0qW43_Yp0EhEsg\",\"tw\":317}\u003C\/div\u003E\u003C\/div\u003E\u003C!--n--\u003E\u003C\/div\u003E"]]""" # noqa response = mock.Mock(text=html) results = google_images.response(response) self.assertEqual(type(results), list) - self.assertEqual(len(results), 1) - self.assertEqual(results[0]['title'], u'South Arrow Clip Art at Clker.com - vector clip art online ...') - self.assertEqual(results[0]['url'], 'a') - self.assertEqual(results[0]['thumbnail_src'], 'https://thumbnail.url/') - self.assertEqual(results[0]['img_src'], 'b') - self.assertEqual(results[0]['content'], 'Download this image as:') + self.assertEqual(len(results), 2) + self.assertEqual(results[0]['title'], u'Southern United States - Wikipedia, the free encyclopedia') + self.assertEqual(results[0]['url'], 'https://en.wikipedia.org/wiki/Southern_United_States') + self.assertEqual(results[0]['img_src'], + 'https://upload.wikimedia.org/wikipedia/commons/e/e4/Us_south_census.png') + self.assertEqual(results[0]['content'], + 'The Southern United States as defined by the United States Census Bureau.') + self.assertEqual(results[0]['thumbnail_src'], + 'https://encrypted-tbn0.gstatic.com/images?q=tbn') diff --git a/sources/tests/unit/engines/test_ina.py b/sources/tests/unit/engines/test_ina.py new file mode 100644 index 0000000..109a959 --- /dev/null +++ b/sources/tests/unit/engines/test_ina.py @@ -0,0 +1,64 @@ +from collections import defaultdict +import mock +from searx.engines import ina +from searx.testing import SearxTestCase + + +class TestInaEngine(SearxTestCase): + + def test_request(self): + query = 'test_query' + dicto = defaultdict(dict) + dicto['pageno'] = 0 + params = ina.request(query, dicto) + self.assertTrue('url' in params) + self.assertTrue(query in params['url']) + self.assertTrue('ina.fr' in params['url']) + + def test_response(self): + self.assertRaises(AttributeError, ina.response, None) + self.assertRaises(AttributeError, ina.response, []) + self.assertRaises(AttributeError, ina.response, '') + self.assertRaises(AttributeError, ina.response, '[]') + + response = mock.Mock(text='{}') + self.assertEqual(ina.response(response), []) + + response = mock.Mock(text='{"data": []}') + self.assertEqual(ina.response(response), []) + + json = """ + {"content":"\\t
\\n\\t\\n\ + \\n
\\n\ +
\\n\ + \\t\\t\\t\\t\\n\ + \\"Conf\\u00e9rence\\n\ + \\t\\t\\t\\t\\t<\\/a>\\n\ + \\t\\t\\t\\t\\t
\\n\\t\\t\\t\\t\\t\\t

\\n\ + \\t\\t\\t\\t\\t\\t\\t\ + Conf\\u00e9rence de presse du G\\u00e9n\\u00e9ral de Gaulle <\\/a>\\n\ + <\\/h3>\\n\ +
\\n27\\/11\\/1967<\\/span>\\n\ + 29321 vues<\\/span>\\n\ + 01h 33m 07s<\\/span>\\n\ + <\\/div>\\n\ +

VERSION INTEGRALE DE LA CONFERENCE DE PRESSE DU GENERAL DE GAULLE . \ + - PA le Pr\\u00e9sident DE GAULLE : il ouvre les bras et s'assied. DP journalis...<\\/p>\\n\ + <\\/div>\\n<\\/div>\\n" + } + """ + response = mock.Mock(text=json) + results = ina.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['title'], u'Conf\xe9rence de presse du G\xe9n\xe9ral de Gaulle') + self.assertEqual(results[0]['url'], + 'https://www.ina.fr/video/CAF89035682/conference-de-presse-du-general-de-gaulle-video.html') + self.assertEqual(results[0]['content'], + u"VERSION INTEGRALE DE LA CONFERENCE DE PRESSE DU GENERAL DE GAULLE ." + u" - PA le Pr\u00e9sident DE GAULLE : il ouvre les bras et s'assied. DP journalis...") diff --git a/sources/tests/unit/engines/test_mediawiki.py b/sources/tests/unit/engines/test_mediawiki.py index 63f7da6..b863727 100644 --- a/sources/tests/unit/engines/test_mediawiki.py +++ b/sources/tests/unit/engines/test_mediawiki.py @@ -118,7 +118,7 @@ class TestMediawikiEngine(SearxTestCase): self.assertEqual(type(results), list) self.assertEqual(len(results), 0) - json = """ + json = r""" {"toto":[ {"id":200,"name":"Artist Name", "link":"http:\/\/www.mediawiki.com\/artist\/1217","type":"artist"} diff --git a/sources/tests/unit/engines/test_mixcloud.py b/sources/tests/unit/engines/test_mixcloud.py index a2ea47c..9c79a47 100644 --- a/sources/tests/unit/engines/test_mixcloud.py +++ b/sources/tests/unit/engines/test_mixcloud.py @@ -55,7 +55,7 @@ class TestMixcloudEngine(SearxTestCase): self.assertEqual(results[0]['content'], 'User') self.assertTrue('http://www.mixcloud.com/user/this-is-the-url/' in results[0]['embedded']) - json = """ + json = r""" {"toto":[ {"id":200,"name":"Artist Name", "link":"http:\/\/www.mixcloud.com\/artist\/1217","type":"artist"} diff --git a/sources/tests/unit/engines/test_scanr_structures.py b/sources/tests/unit/engines/test_scanr_structures.py new file mode 100644 index 0000000..a7b9e91 --- /dev/null +++ b/sources/tests/unit/engines/test_scanr_structures.py @@ -0,0 +1,175 @@ +from collections import defaultdict +import mock +from searx.engines import scanr_structures +from searx.testing import SearxTestCase + + +class TestScanrStructuresEngine(SearxTestCase): + + def test_request(self): + query = 'test_query' + dicto = defaultdict(dict) + dicto['pageno'] = 1 + params = scanr_structures.request(query, dicto) + self.assertIn('url', params) + self.assertIn(query, params['data']) + self.assertIn('scanr.enseignementsup-recherche.gouv.fr', params['url']) + + def test_response(self): + self.assertRaises(AttributeError, scanr_structures.response, None) + self.assertRaises(AttributeError, scanr_structures.response, []) + self.assertRaises(AttributeError, scanr_structures.response, '') + self.assertRaises(AttributeError, scanr_structures.response, '[]') + + response = mock.Mock(text='{}') + self.assertEqual(scanr_structures.response(response), []) + + response = mock.Mock(text='{"data": []}') + self.assertEqual(scanr_structures.response(response), []) + + json = u""" + { + "request": + { + "query":"test_query", + "page":1, + "pageSize":20, + "sortOrder":"RELEVANCY", + "sortDirection":"ASC", + "searchField":"ALL", + "from":0 + }, + "total":2471, + "results":[ + { + "id":"200711886U", + "label":"Laboratoire d'Informatique de Grenoble", + "kind":"RNSR", + "publicEntity":true, + "address":{"city":"Grenoble","departement":"38"}, + "logo":"/static/logos/200711886U.png", + "acronym":"LIG", + "type":{"code":"UR","label":"Unit\xe9 de recherche"}, + "level":2, + "institutions":[ + { + "id":"193819125", + "label":"Grenoble INP", + "acronym":"IPG", + "code":"UMR 5217" + }, + { + "id":"130021397", + "label":"Universit\xe9 de Grenoble Alpes", + "acronym":"UGA", + "code":"UMR 5217" + }, + { + "id":"180089013", + "label":"Centre national de la recherche scientifique", + "acronym":"CNRS", + "code":"UMR 5217" + }, + { + "id":"180089047", + "label":"Institut national de recherche en informatique et en automatique", + "acronym":"Inria", + "code":"UMR 5217" + } + ], + "highlights":[ + { + "type":"projects", + "value":"linguicielles d\xe9velopp\xe9s jusqu'ici par le GETALP\ + du LIG en tant que prototypes op\xe9rationnels.\ +\\r\\nDans le contexte" + }, + { + "type":"acronym", + "value":"LIG" + }, + { + "type":"websiteContents", + "value":"S\xe9lection\\nListe structures\\nD\xe9tail\\n\ + Accueil\\n200711886U : LIG\ + Laboratoire d'Informatique de Grenoble Unit\xe9 de recherche"}, + { + "type":"publications", + "value":"de noms. Nous avons d'abord d\xe9velopp\xe9 LOOV \ + (pour Lig Overlaid OCR in Vid\xe9o), \ + un outil d'extraction des" + } + ] + }, + { + "id":"199511665F", + "label":"Laboratoire Bordelais de Recherche en Informatique", + "kind":"RNSR", + "publicEntity":true, + "address":{"city":"Talence","departement":"33"}, + "logo":"/static/logos/199511665F.png", + "acronym":"LaBRI", + "type":{"code":"UR","label":"Unit\xe9 de recherche"}, + "level":2, + "institutions":[ + { + "id":"130006356", + "label":"Institut polytechnique de Bordeaux", + "acronym":"IPB", + "code":"UMR 5800" + }, + { + "id":"130018351", + "label":"Universit\xe9 de Bordeaux", + "acronym":null, + "code":"UMR 5800" + }, + { + "id":"180089013", + "label":"Centre national de la recherche scientifique", + "acronym":"CNRS", + "code":"UMR 5800" + }, + { + "id":"180089047", + "label":"Institut national de recherche en informatique et en automatique", + "acronym":"Inria", + "code":"UMR 5800" + } + ], + "highlights":[ + { + "type":"websiteContents", + "value":"Samia Kerdjoudj\\n2016-07-05\\nDouble-exponential\ + and triple-exponential bounds for\ + choosability problems parameterized" + }, + { + "type":"publications", + "value":"de cam\xe9ras install\xe9es dans les lieux publiques \ + a tripl\xe9 en 2009, passant de 20 000 \ + \xe0 60 000. Malgr\xe9 le" + } + ] + } + ] + } + """ + response = mock.Mock(text=json) + results = scanr_structures.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 2) + self.assertEqual(results[0]['title'], u"Laboratoire d'Informatique de Grenoble") + self.assertEqual(results[0]['url'], 'https://scanr.enseignementsup-recherche.gouv.fr/structure/200711886U') + self.assertEqual(results[0]['content'], + u"linguicielles d\xe9velopp\xe9s jusqu'ici par le GETALP " + u"du LIG en tant que prototypes " + u"op\xe9rationnels. Dans le contexte") + self.assertEqual(results[1]['img_src'], + 'https://scanr.enseignementsup-recherche.gouv.fr//static/logos/199511665F.png') + self.assertEqual(results[1]['content'], + "Samia Kerdjoudj 2016-07-05 Double-exponential and" + " triple-exponential bounds for " + "choosability problems parameterized") + self.assertEqual(results[1]['url'], 'https://scanr.enseignementsup-recherche.gouv.fr/structure/199511665F') + self.assertEqual(results[1]['title'], u"Laboratoire Bordelais de Recherche en Informatique") diff --git a/sources/tests/unit/engines/test_searchcode_code.py b/sources/tests/unit/engines/test_searchcode_code.py index c0ac202..955aea1 100644 --- a/sources/tests/unit/engines/test_searchcode_code.py +++ b/sources/tests/unit/engines/test_searchcode_code.py @@ -63,7 +63,7 @@ class TestSearchcodeCodeEngine(SearxTestCase): self.assertEqual(results[0]['repository'], 'https://repo') self.assertEqual(results[0]['code_language'], 'cpp') - json = """ + json = r""" {"toto":[ {"id":200,"name":"Artist Name", "link":"http:\/\/www.searchcode_code.com\/artist\/1217","type":"artist"} diff --git a/sources/tests/unit/engines/test_searchcode_doc.py b/sources/tests/unit/engines/test_searchcode_doc.py index b9dcf38..7228613 100644 --- a/sources/tests/unit/engines/test_searchcode_doc.py +++ b/sources/tests/unit/engines/test_searchcode_doc.py @@ -61,7 +61,7 @@ class TestSearchcodeDocEngine(SearxTestCase): self.assertIn('test', results[0]['content']) self.assertIn('Description', results[0]['content']) - json = """ + json = r""" {"toto":[ {"id":200,"name":"Artist Name", "link":"http:\/\/www.searchcode_doc.com\/artist\/1217","type":"artist"} diff --git a/sources/tests/unit/engines/test_wikidata.py b/sources/tests/unit/engines/test_wikidata.py new file mode 100644 index 0000000..ec5f52e --- /dev/null +++ b/sources/tests/unit/engines/test_wikidata.py @@ -0,0 +1,504 @@ +# -*- coding: utf-8 -*- +from json import loads +from lxml.html import fromstring +from collections import defaultdict +import mock +from searx.engines import wikidata +from searx.testing import SearxTestCase + + +class TestWikidataEngine(SearxTestCase): + + def test_request(self): + query = 'test_query' + dicto = defaultdict(dict) + dicto['language'] = 'all' + params = wikidata.request(query, dicto) + self.assertIn('url', params) + self.assertIn(query, params['url']) + self.assertIn('wikidata.org', params['url']) + self.assertIn('en', params['url']) + + dicto['language'] = 'es_ES' + params = wikidata.request(query, dicto) + self.assertIn(query, params['url']) + self.assertIn('es', params['url']) + + # successful cases are not tested here to avoid sending additional requests + def test_response(self): + self.assertRaises(AttributeError, wikidata.response, None) + self.assertRaises(AttributeError, wikidata.response, []) + self.assertRaises(AttributeError, wikidata.response, '') + self.assertRaises(AttributeError, wikidata.response, '[]') + + response = mock.Mock(content='', search_params={"language": "all"}) + self.assertEqual(wikidata.response(response), []) + + def test_getDetail(self): + response = {} + results = wikidata.getDetail(response, "Q123", "en", "en-US") + self.assertEqual(results, []) + + title_html = '

Test
' + html = """ +
+ """ + response = {"parse": {"displaytitle": title_html, "text": html}} + + results = wikidata.getDetail(response, "Q123", "en", "en-US") + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['url'], 'https://en.wikipedia.org/wiki/Test') + + title_html = """ +
+
+ Test + English +
+
+ """ + html = """ +
+
+ Description + English +
+ +
+ +
+
+ """ + response = {"parse": {"displaytitle": title_html, "text": html}} + + results = wikidata.getDetail(response, "Q123", "yua", "yua_MX") + self.assertEqual(len(results), 2) + self.assertEqual(results[0]['title'], 'Official website') + self.assertEqual(results[0]['url'], 'https://officialsite.com') + + self.assertEqual(results[1]['infobox'], 'Test') + self.assertEqual(results[1]['id'], None) + self.assertEqual(results[1]['content'], 'Description') + self.assertEqual(results[1]['attributes'], []) + self.assertEqual(results[1]['urls'][0]['title'], 'Official website') + self.assertEqual(results[1]['urls'][0]['url'], 'https://officialsite.com') + self.assertEqual(results[1]['urls'][1]['title'], 'Wikipedia (en)') + self.assertEqual(results[1]['urls'][1]['url'], 'https://en.wikipedia.org/wiki/Test') + + def test_add_image(self): + image_src = wikidata.add_image(fromstring("
")) + self.assertEqual(image_src, None) + + html = u""" +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ """ + html_etree = fromstring(html) + + image_src = wikidata.add_image(html_etree) + self.assertEqual(image_src, + "https://commons.wikimedia.org/wiki/Special:FilePath/image.png?width=500&height=400") + + html = u""" +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ """ + html_etree = fromstring(html) + + image_src = wikidata.add_image(html_etree) + self.assertEqual(image_src, + "https://commons.wikimedia.org/wiki/Special:FilePath/logo.png?width=500&height=400") + + def test_add_attribute(self): + html = u""" +
+
+ +
+
+
+ +
+ +
+
+
+
+ """ + attributes = [] + html_etree = fromstring(html) + + wikidata.add_attribute(attributes, html_etree, "Fail") + self.assertEqual(attributes, []) + + wikidata.add_attribute(attributes, html_etree, "P27") + self.assertEqual(len(attributes), 1) + self.assertEqual(attributes[0]["label"], "Country of citizenship") + self.assertEqual(attributes[0]["value"], "United Kingdom") + + html = u""" +
+
+ +
+
+
+ +
+
+
+
+ 27 January 1832 + + Gregorian + +
+
+
+
+
+
+
+ """ + attributes = [] + html_etree = fromstring(html) + wikidata.add_attribute(attributes, html_etree, "P569", date=True) + self.assertEqual(len(attributes), 1) + self.assertEqual(attributes[0]["label"], "Date of birth") + self.assertEqual(attributes[0]["value"], "27 January 1832") + + html = u""" +
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ """ + attributes = [] + html_etree = fromstring(html) + wikidata.add_attribute(attributes, html_etree, "P6") + self.assertEqual(len(attributes), 1) + self.assertEqual(attributes[0]["label"], "Head of government") + self.assertEqual(attributes[0]["value"], "Old Prime Minister, Actual Prime Minister") + + attributes = [] + html_etree = fromstring(html) + wikidata.add_attribute(attributes, html_etree, "P6", trim=True) + self.assertEqual(len(attributes), 1) + self.assertEqual(attributes[0]["value"], "Actual Prime Minister") + + def test_add_url(self): + html = u""" +
+ +
+ """ + urls = [] + html_etree = fromstring(html) + wikidata.add_url(urls, html_etree, 'P856') + self.assertEquals(len(urls), 1) + self.assertIn({'title': 'Official website', 'url': 'https://searx.me/'}, urls) + urls = [] + results = [] + wikidata.add_url(urls, html_etree, 'P856', 'custom label', results=results) + self.assertEquals(len(urls), 1) + self.assertEquals(len(results), 1) + self.assertIn({'title': 'custom label', 'url': 'https://searx.me/'}, urls) + self.assertIn({'title': 'custom label', 'url': 'https://searx.me/'}, results) + + html = u""" + + """ + urls = [] + html_etree = fromstring(html) + wikidata.add_url(urls, html_etree, 'P856') + self.assertEquals(len(urls), 2) + self.assertIn({'title': 'Official website', 'url': 'http://www.worldofwarcraft.com'}, urls) + self.assertIn({'title': 'Official website', 'url': 'http://eu.battle.net/wow/en/'}, urls) + + def test_get_imdblink(self): + html = u""" +
+
+ +
+
+ """ + html_etree = fromstring(html) + imdblink = wikidata.get_imdblink(html_etree, 'https://www.imdb.com/') + + html = u""" +
+
+ +
+
+ """ + html_etree = fromstring(html) + imdblink = wikidata.get_imdblink(html_etree, 'https://www.imdb.com/') + self.assertIn('https://www.imdb.com/name/nm4915994', imdblink) + + def test_get_geolink(self): + html = u""" +
+
+
+
+ 60°N, 40°E +
+
+
+
+ """ + html_etree = fromstring(html) + geolink = wikidata.get_geolink(html_etree) + self.assertIn('https://www.openstreetmap.org/', geolink) + self.assertIn('lat=60&lon=40', geolink) + + html = u""" +
+
+
+
+ 34°35'59"S, 58°22'55"W +
+
+
+
+ """ + html_etree = fromstring(html) + geolink = wikidata.get_geolink(html_etree) + self.assertIn('https://www.openstreetmap.org/', geolink) + self.assertIn('lat=-34.59', geolink) + self.assertIn('lon=-58.38', geolink) + + def test_get_wikilink(self): + html = """ +
+
+ +
+
+ +
+
+ """ + html_etree = fromstring(html) + wikilink = wikidata.get_wikilink(html_etree, 'nowiki') + self.assertEqual(wikilink, None) + wikilink = wikidata.get_wikilink(html_etree, 'enwiki') + self.assertEqual(wikilink, 'https://en.wikipedia.org/wiki/Test') + wikilink = wikidata.get_wikilink(html_etree, 'arwiki') + self.assertEqual(wikilink, 'https://ar.wikipedia.org/wiki/Test') + wikilink = wikidata.get_wikilink(html_etree, 'enwikiquote') + self.assertEqual(wikilink, 'https://en.wikiquote.org/wiki/Test') diff --git a/sources/tests/unit/engines/test_wolframalpha_noapi.py b/sources/tests/unit/engines/test_wolframalpha_noapi.py index 068c1be..a8f7347 100644 --- a/sources/tests/unit/engines/test_wolframalpha_noapi.py +++ b/sources/tests/unit/engines/test_wolframalpha_noapi.py @@ -28,7 +28,7 @@ class TestWolframAlphaNoAPIEngine(SearxTestCase): request = Request(headers={'Referer': referer_url}) # test failure - json = ''' + json = r''' {"queryresult" : { "success" : false, "error" : false, @@ -42,7 +42,7 @@ class TestWolframAlphaNoAPIEngine(SearxTestCase): self.assertEqual(wolframalpha_noapi.response(response), []) # test basic case - json = ''' + json = r''' {"queryresult" : { "success" : true, "error" : false, @@ -143,7 +143,7 @@ class TestWolframAlphaNoAPIEngine(SearxTestCase): self.assertEqual('Wolfram|Alpha', results[1]['title']) # test calc - json = """ + json = r""" {"queryresult" : { "success" : true, "error" : false, diff --git a/sources/tests/unit/engines/test_yahoo.py b/sources/tests/unit/engines/test_yahoo.py index 11ef9db..1226f92 100644 --- a/sources/tests/unit/engines/test_yahoo.py +++ b/sources/tests/unit/engines/test_yahoo.py @@ -28,6 +28,7 @@ class TestYahooEngine(SearxTestCase): query = 'test_query' dicto = defaultdict(dict) dicto['pageno'] = 1 + dicto['time_range'] = '' dicto['language'] = 'fr_FR' params = yahoo.request(query, dicto) self.assertIn('url', params) diff --git a/sources/tests/unit/test_plugins.py b/sources/tests/unit/test_plugins.py index 98d39ec..b8a8980 100644 --- a/sources/tests/unit/test_plugins.py +++ b/sources/tests/unit/test_plugins.py @@ -52,23 +52,39 @@ class SelfIPTest(SearxTestCase): request = Mock(user_plugins=store.plugins, remote_addr='127.0.0.1') request.headers.getlist.return_value = [] - ctx = get_search_mock(query='ip') + ctx = get_search_mock(query='ip', pageno=1) store.call('post_search', request, ctx) self.assertTrue('127.0.0.1' in ctx['search'].result_container.answers) + ctx = get_search_mock(query='ip', pageno=2) + store.call('post_search', request, ctx) + self.assertFalse('127.0.0.1' in ctx['search'].result_container.answers) + # User agent test request = Mock(user_plugins=store.plugins, user_agent='Mock') request.headers.getlist.return_value = [] - ctx = get_search_mock(query='user-agent') + ctx = get_search_mock(query='user-agent', pageno=1) store.call('post_search', request, ctx) self.assertTrue('Mock' in ctx['search'].result_container.answers) - ctx = get_search_mock(query='user-agent') + ctx = get_search_mock(query='user-agent', pageno=2) + store.call('post_search', request, ctx) + self.assertFalse('Mock' in ctx['search'].result_container.answers) + + ctx = get_search_mock(query='user-agent', pageno=1) store.call('post_search', request, ctx) self.assertTrue('Mock' in ctx['search'].result_container.answers) - ctx = get_search_mock(query='What is my User-Agent?') + ctx = get_search_mock(query='user-agent', pageno=2) + store.call('post_search', request, ctx) + self.assertFalse('Mock' in ctx['search'].result_container.answers) + + ctx = get_search_mock(query='What is my User-Agent?', pageno=1) store.call('post_search', request, ctx) self.assertTrue('Mock' in ctx['search'].result_container.answers) + + ctx = get_search_mock(query='What is my User-Agent?', pageno=2) + store.call('post_search', request, ctx) + self.assertFalse('Mock' in ctx['search'].result_container.answers) diff --git a/sources/tests/unit/test_preferences.py b/sources/tests/unit/test_preferences.py index e418c0a..c173508 100644 --- a/sources/tests/unit/test_preferences.py +++ b/sources/tests/unit/test_preferences.py @@ -4,6 +4,7 @@ from searx.testing import SearxTestCase class PluginStub(object): + def __init__(self, id, default_on): self.id = id self.default_on = default_on @@ -11,6 +12,7 @@ class PluginStub(object): class TestSettings(SearxTestCase): # map settings + def test_map_setting_invalid_initialization(self): with self.assertRaises(MissingArgumentException): setting = MapSetting(3, wrong_argument={'0': 0}) diff --git a/sources/tests/unit/test_webapp.py b/sources/tests/unit/test_webapp.py index 5697017..1762d66 100644 --- a/sources/tests/unit/test_webapp.py +++ b/sources/tests/unit/test_webapp.py @@ -38,6 +38,7 @@ class ViewsTestCase(SearxTestCase): suggestions=set(), infoboxes=[], results=self.test_results, + results_number=lambda: 3, results_length=lambda: len(self.test_results)) webapp.Search.search = search_mock @@ -95,7 +96,7 @@ class ViewsTestCase(SearxTestCase): ) self.assertIn( - '2', + '3', result.data ) diff --git a/sources/utils/fabfile.py b/sources/utils/fabfile.py index 4b356c5..559e2ab 100644 --- a/sources/utils/fabfile.py +++ b/sources/utils/fabfile.py @@ -89,7 +89,7 @@ def init(): sudo('git clone https://github.com/asciimoo/searx') sudo('chown -R {user}:{user} {searx_dir}'.format(user=current_user, searx_dir=searx_dir)) - put(StringIO(uwsgi_file), searx_dir+'/uwsgi.ini') + put(StringIO(uwsgi_file), searx_dir + '/uwsgi.ini') sudo('ln -s {0}/uwsgi.ini /etc/uwsgi/apps-enabled/searx.ini'.format(searx_dir)) run('virtualenv {0}'.format(searx_ve_dir)) diff --git a/sources/utils/fetch_currencies.py b/sources/utils/fetch_currencies.py index cb055dd..716b505 100644 --- a/sources/utils/fetch_currencies.py +++ b/sources/utils/fetch_currencies.py @@ -5,31 +5,31 @@ import unicodedata import string from urllib import urlencode from requests import get - + languages = {'de', 'en', 'es', 'fr', 'hu', 'it', 'nl', 'jp'} - + url_template = 'https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&{query}&props=labels%7Cdatatype%7Cclaims%7Caliases&languages=' + '|'.join(languages) -url_wmflabs_template = 'http://wdq.wmflabs.org/api?q=' -url_wikidata_search_template='http://www.wikidata.org/w/api.php?action=query&list=search&format=json&srnamespace=0&srprop=sectiontitle&{query}' - -wmflabs_queries = [ - 'CLAIM[31:8142]', # all devise +url_wmflabs_template = 'http://wdq.wmflabs.org/api?q=' +url_wikidata_search_template = 'http://www.wikidata.org/w/api.php?action=query&list=search&format=json&srnamespace=0&srprop=sectiontitle&{query}' + +wmflabs_queries = [ + 'CLAIM[31:8142]', # all devise ] - + db = { - 'iso4217' : { - }, - 'names' : { - } + 'iso4217': { + }, + 'names': { + } } def remove_accents(data): return unicodedata.normalize('NFKD', data).lower() - + def normalize_name(name): - return re.sub(' +',' ', remove_accents(name.lower()).replace('-', ' ')) + return re.sub(' +', ' ', remove_accents(name.lower()).replace('-', ' ')) def add_currency_name(name, iso4217): @@ -37,7 +37,6 @@ def add_currency_name(name, iso4217): db_names = db['names'] - if not isinstance(iso4217, basestring): print "problem", name, iso4217 return @@ -52,7 +51,7 @@ def add_currency_name(name, iso4217): if iso4217_set is not None and iso4217 not in iso4217_set: db_names[name].append(iso4217) else: - db_names[name] = [ iso4217 ] + db_names[name] = [iso4217] def add_currency_label(label, iso4217, language): @@ -66,26 +65,26 @@ def get_property_value(data, name): prop = data.get('claims', {}).get(name, {}) if len(prop) == 0: return None - - value = prop[0].get('mainsnak', {}).get('datavalue', {}).get('value', '') + + value = prop[0].get('mainsnak', {}).get('datavalue', {}).get('value', '') if value == '': return None return value - + def parse_currency(data): iso4217 = get_property_value(data, 'P498') - + if iso4217 is not None: unit = get_property_value(data, 'P558') if unit is not None: add_currency_name(unit, iso4217) - + labels = data.get('labels', {}) for language in languages: name = labels.get(language, {}).get('value', None) - if name != None: + if name is not None: add_currency_name(name, iso4217) add_currency_label(name, iso4217, language) @@ -95,22 +94,22 @@ def parse_currency(data): alias = aliases[language][i].get('value', None) add_currency_name(alias, iso4217) - + def fetch_data(wikidata_ids): - url = url_template.format(query=urlencode({'ids' : '|'.join(wikidata_ids)})) + url = url_template.format(query=urlencode({'ids': '|'.join(wikidata_ids)})) htmlresponse = get(url) jsonresponse = json.loads(htmlresponse.content) entities = jsonresponse.get('entities', {}) - + for pname in entities: pvalue = entities.get(pname) parse_currency(pvalue) - - + + def add_q(i): return "Q" + str(i) - - + + def fetch_data_batch(wikidata_ids): while len(wikidata_ids) > 0: if len(wikidata_ids) > 50: @@ -119,35 +118,36 @@ def fetch_data_batch(wikidata_ids): else: fetch_data(wikidata_ids) wikidata_ids = [] - - + + def wdq_query(query): url = url_wmflabs_template + query htmlresponse = get(url) jsonresponse = json.loads(htmlresponse.content) qlist = map(add_q, jsonresponse.get('items', {})) error = jsonresponse.get('status', {}).get('error', None) - if error != None and error != 'OK': + if error is not None and error != 'OK': print "error for query '" + query + "' :" + error fetch_data_batch(qlist) - - + + def wd_query(query, offset=0): qlist = [] - + url = url_wikidata_search_template.format(query=urlencode({'srsearch': query, 'srlimit': 50, 'sroffset': offset})) htmlresponse = get(url) jsonresponse = json.loads(htmlresponse.content) for r in jsonresponse.get('query', {}).get('search', {}): qlist.append(r.get('title', '')) fetch_data_batch(qlist) - -## fetch ## + + +# fetch # for q in wmflabs_queries: wdq_query(q) -# static +# static add_currency_name(u"euro", 'EUR') add_currency_name(u"euros", 'EUR') add_currency_name(u"dollar", 'USD')