diff options
Diffstat (limited to 'searx')
108 files changed, 1907 insertions, 272 deletions
diff --git a/searx/__init__.py b/searx/__init__.py index 110f46af8..2d545a809 100644 --- a/searx/__init__.py +++ b/searx/__init__.py @@ -36,11 +36,6 @@ if 'SEARX_SETTINGS_PATH' in environ: else: settings_path = join(searx_dir, 'settings.yml') -if 'SEARX_HTTPS_REWRITE_PATH' in environ: - https_rewrite_path = environ['SEARX_HTTPS_REWRITE_PATH'] -else: - https_rewrite_path = join(searx_dir, 'https_rules') - # load settings with open(settings_path) as settings_yaml: settings = load(settings_yaml) @@ -52,10 +47,4 @@ else: logger = logging.getLogger('searx') -# load https rules only if https rewrite is enabled -if settings.get('server', {}).get('https_rewrite'): - # loade https rules - from searx.https_rewrite import load_https_rules - load_https_rules(https_rewrite_path) - logger.info('Initialisation done') diff --git a/searx/autocomplete.py b/searx/autocomplete.py index 9d31aa36f..83e204890 100644 --- a/searx/autocomplete.py +++ b/searx/autocomplete.py @@ -19,11 +19,19 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. from lxml import etree from json import loads from urllib import urlencode +from searx import settings from searx.languages import language_codes from searx.engines import ( categories, engines, engine_shortcuts ) -from searx.poolrequests import get +from searx.poolrequests import get as http_get + + +def get(*args, **kwargs): + if not 'timeout' in kwargs: + kwargs['timeout'] = settings['server']['request_timeout'] + + return http_get(*args, **kwargs) def searx_bang(full_query): diff --git a/searx/engines/deezer.py b/searx/engines/deezer.py index 433ceffa1..7fbd3c200 100644 --- a/searx/engines/deezer.py +++ b/searx/engines/deezer.py @@ -16,11 +16,11 @@ categories = ['music'] paging = True # search-url -url = 'http://api.deezer.com/' +url = 'https://api.deezer.com/' search_url = url + 'search?{query}&index={offset}' embedded_url = '<iframe scrolling="no" frameborder="0" allowTransparency="true" ' +\ - 'data-src="http://www.deezer.com/plugins/player?type=tracks&id={audioid}" ' +\ + 'data-src="https://www.deezer.com/plugins/player?type=tracks&id={audioid}" ' +\ 'width="540" height="80"></iframe>' @@ -45,6 +45,10 @@ def response(resp): if result['type'] == 'track': title = result['title'] url = result['link'] + + if url.startswith('http://'): + url = 'https' + url[4:] + content = result['artist']['name'] +\ " • " +\ result['album']['title'] +\ diff --git a/searx/engines/kickass.py b/searx/engines/kickass.py index ea7f17c23..9c4639c32 100644 --- a/searx/engines/kickass.py +++ b/searx/engines/kickass.py @@ -34,10 +34,6 @@ def request(query, params): params['url'] = search_url.format(search_term=quote(query), pageno=params['pageno']) - # FIX: SSLError: hostname 'kickass.so' - # doesn't match either of '*.kickass.to', 'kickass.to' - params['verify'] = False - return params diff --git a/searx/engines/photon.py b/searx/engines/photon.py index a9c558c4b..869916cd4 100644 --- a/searx/engines/photon.py +++ b/searx/engines/photon.py @@ -41,9 +41,6 @@ def request(query, params): # using searx User-Agent params['headers']['User-Agent'] = searx_useragent() - # FIX: SSLError: SSL3_GET_SERVER_CERTIFICATE:certificate verify failed - params['verify'] = False - return params diff --git a/searx/engines/piratebay.py b/searx/engines/piratebay.py index fa5c61128..adee6c1fd 100644 --- a/searx/engines/piratebay.py +++ b/searx/engines/piratebay.py @@ -1,4 +1,4 @@ -## Piratebay (Videos, Music, Files) +# Piratebay (Videos, Music, Files) # # @website https://thepiratebay.se # @provide-api no (nothing found) @@ -42,6 +42,10 @@ def request(query, params): search_type=search_type, pageno=params['pageno'] - 1) + # FIX: SSLError: hostname 'kthepiratebay.se' + # doesn't match either of 'ssl2000.cloudflare.com', 'cloudflare.com', '*.cloudflare.com' + params['verify'] = False + return params @@ -78,7 +82,11 @@ def response(resp): leech = 0 magnetlink = result.xpath(magnet_xpath)[0] - torrentfile = result.xpath(torrent_xpath)[0] + torrentfile_links = result.xpath(torrent_xpath) + if torrentfile_links: + torrentfile_link = torrentfile_links[0].attrib.get('href') + else: + torrentfile_link = None # append result results.append({'url': href, @@ -87,7 +95,7 @@ def response(resp): 'seed': seed, 'leech': leech, 'magnetlink': magnetlink.attrib.get('href'), - 'torrentfile': torrentfile.attrib.get('href'), + 'torrentfile': torrentfile_link, 'template': 'torrent.html'}) # return results sorted by seeder diff --git a/searx/engines/spotify.py b/searx/engines/spotify.py new file mode 100644 index 000000000..61f3721ec --- /dev/null +++ b/searx/engines/spotify.py @@ -0,0 +1,60 @@ +## Spotify (Music) +# +# @website https://spotify.com +# @provide-api yes (https://developer.spotify.com/web-api/search-item/) +# +# @using-api yes +# @results JSON +# @stable yes +# @parse url, title, content, embedded + +from json import loads +from urllib import urlencode + +# engine dependent config +categories = ['music'] +paging = True + +# search-url +url = 'https://api.spotify.com/' +search_url = url + 'v1/search?{query}&type=track&offset={offset}' + +embedded_url = '<iframe data-src="https://embed.spotify.com/?uri=spotify:track:{audioid}"\ + width="300" height="80" frameborder="0" allowtransparency="true"></iframe>' + + +# do search-request +def request(query, params): + offset = (params['pageno'] - 1) * 20 + + params['url'] = search_url.format(query=urlencode({'q': query}), + offset=offset) + + return params + + +# get response from search-request +def response(resp): + results = [] + + search_res = loads(resp.text) + + # parse results + for result in search_res.get('tracks', {}).get('items', {}): + if result['type'] == 'track': + title = result['name'] + url = result['external_urls']['spotify'] + content = result['artists'][0]['name'] +\ + " • " +\ + result['album']['name'] +\ + " • " + result['name'] + embedded = embedded_url.format(audioid=result['id']) + + # append result + results.append({'url': url, + 'title': title, + 'embedded': embedded, + 'content': content}) + + # return results + return results diff --git a/searx/engines/yahoo.py b/searx/engines/yahoo.py index 161f7513b..11663a415 100644 --- a/searx/engines/yahoo.py +++ b/searx/engines/yahoo.py @@ -24,11 +24,11 @@ base_url = 'https://search.yahoo.com/' search_url = 'search?{query}&b={offset}&fl=1&vl=lang_{lang}' # specific xpath variables -results_xpath = '//div[@class="res"]' +results_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' Sr ')]" url_xpath = './/h3/a/@href' title_xpath = './/h3/a' -content_xpath = './/div[@class="abstr"]' -suggestion_xpath = '//div[@id="satat"]//a' +content_xpath = './/div[@class="compText aAbs"]' +suggestion_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' AlsoTry ')]//a" # remove yahoo-specific tracking-url @@ -91,11 +91,12 @@ def response(resp): 'content': content}) # if no suggestion found, return results - if not dom.xpath(suggestion_xpath): + suggestions = dom.xpath(suggestion_xpath) + if not suggestions: return results # parse suggestion - for suggestion in dom.xpath(suggestion_xpath): + for suggestion in suggestions: # append suggestion results.append({'suggestion': extract_text(suggestion)}) diff --git a/searx/plugins/__init__.py b/searx/plugins/__init__.py new file mode 100644 index 000000000..5ac3f447c --- /dev/null +++ b/searx/plugins/__init__.py @@ -0,0 +1,75 @@ +''' +searx is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +searx is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with searx. If not, see < http://www.gnu.org/licenses/ >. + +(C) 2015 by Adam Tauber, <asciimoo@gmail.com> +''' +from sys import exit +from searx import logger + +logger = logger.getChild('plugins') + +from searx.plugins import (https_rewrite, + self_ip, + search_on_category_select) + +required_attrs = (('name', str), + ('description', str), + ('default_on', bool)) + +optional_attrs = (('js_dependencies', tuple), + ('css_dependencies', tuple)) + + +class Plugin(): + default_on = False + name = 'Default plugin' + description = 'Default plugin description' + + +class PluginStore(): + + def __init__(self): + self.plugins = [] + + def __iter__(self): + for plugin in self.plugins: + yield plugin + + def register(self, *plugins): + for plugin in plugins: + for plugin_attr, plugin_attr_type in required_attrs: + if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type): + logger.critical('missing attribute "{0}", cannot load plugin: {1}'.format(plugin_attr, plugin)) + exit(3) + for plugin_attr, plugin_attr_type in optional_attrs: + if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type): + setattr(plugin, plugin_attr, plugin_attr_type()) + plugin.id = plugin.name.replace(' ', '_') + self.plugins.append(plugin) + + def call(self, plugin_type, request, *args, **kwargs): + ret = True + for plugin in request.user_plugins: + if hasattr(plugin, plugin_type): + ret = getattr(plugin, plugin_type)(request, *args, **kwargs) + if not ret: + break + + return ret + + +plugins = PluginStore() +plugins.register(https_rewrite) +plugins.register(self_ip) +plugins.register(search_on_category_select) diff --git a/searx/https_rewrite.py b/searx/plugins/https_rewrite.py index 71aec1c9b..a24f15a28 100644 --- a/searx/https_rewrite.py +++ b/searx/plugins/https_rewrite.py @@ -18,11 +18,22 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. import re from urlparse import urlparse from lxml import etree -from os import listdir +from os import listdir, environ from os.path import isfile, isdir, join -from searx import logger +from searx.plugins import logger +from flask.ext.babel import gettext +from searx import searx_dir +name = "HTTPS rewrite" +description = gettext('Rewrite HTTP links to HTTPS if possible') +default_on = True + +if 'SEARX_HTTPS_REWRITE_PATH' in environ: + rules_path = environ['SEARX_rules_path'] +else: + rules_path = join(searx_dir, 'plugins/https_rules') + logger = logger.getChild("https_rewrite") # https://gitweb.torproject.org/\ @@ -33,7 +44,7 @@ https_rules = [] # load single ruleset from a xml file -def load_single_https_ruleset(filepath): +def load_single_https_ruleset(rules_path): ruleset = () # init parser @@ -41,7 +52,7 @@ def load_single_https_ruleset(filepath): # load and parse xml-file try: - tree = etree.parse(filepath, parser) + tree = etree.parse(rules_path, parser) except: # TODO, error message return () @@ -207,3 +218,13 @@ def https_url_rewrite(result): # target has matched, do not search over the other rules break return result + + +def on_result(request, ctx): + result = ctx['result'] + if result['parsed_url'].scheme == 'http': + https_url_rewrite(result) + return True + + +load_https_rules(rules_path) diff --git a/searx/https_rules/00README b/searx/plugins/https_rules/00README index fcd8a7724..fcd8a7724 100644 --- a/searx/https_rules/00README +++ b/searx/plugins/https_rules/00README diff --git a/searx/https_rules/Bing.xml b/searx/plugins/https_rules/Bing.xml index 8b403f108..8b403f108 100644 --- a/searx/https_rules/Bing.xml +++ b/searx/plugins/https_rules/Bing.xml diff --git a/searx/https_rules/Dailymotion.xml b/searx/plugins/https_rules/Dailymotion.xml index 743100cb7..743100cb7 100644 --- a/searx/https_rules/Dailymotion.xml +++ b/searx/plugins/https_rules/Dailymotion.xml diff --git a/searx/https_rules/Deviantart.xml b/searx/plugins/https_rules/Deviantart.xml index 7830fc20f..7830fc20f 100644 --- a/searx/https_rules/Deviantart.xml +++ b/searx/plugins/https_rules/Deviantart.xml diff --git a/searx/https_rules/DuckDuckGo.xml b/searx/plugins/https_rules/DuckDuckGo.xml index 173a9ad9f..173a9ad9f 100644 --- a/searx/https_rules/DuckDuckGo.xml +++ b/searx/plugins/https_rules/DuckDuckGo.xml diff --git a/searx/https_rules/Flickr.xml b/searx/plugins/https_rules/Flickr.xml index 85c6e8065..85c6e8065 100644 --- a/searx/https_rules/Flickr.xml +++ b/searx/plugins/https_rules/Flickr.xml diff --git a/searx/https_rules/Github-Pages.xml b/searx/plugins/https_rules/Github-Pages.xml index d3be58a4c..d3be58a4c 100644 --- a/searx/https_rules/Github-Pages.xml +++ b/searx/plugins/https_rules/Github-Pages.xml diff --git a/searx/https_rules/Github.xml b/searx/plugins/https_rules/Github.xml index a9a3a1e53..a9a3a1e53 100644 --- a/searx/https_rules/Github.xml +++ b/searx/plugins/https_rules/Github.xml diff --git a/searx/https_rules/Google-mismatches.xml b/searx/plugins/https_rules/Google-mismatches.xml index de9d3eb18..de9d3eb18 100644 --- a/searx/https_rules/Google-mismatches.xml +++ b/searx/plugins/https_rules/Google-mismatches.xml diff --git a/searx/https_rules/Google.org.xml b/searx/plugins/https_rules/Google.org.xml index d6cc47881..d6cc47881 100644 --- a/searx/https_rules/Google.org.xml +++ b/searx/plugins/https_rules/Google.org.xml diff --git a/searx/https_rules/GoogleAPIs.xml b/searx/plugins/https_rules/GoogleAPIs.xml index 85a5a8081..85a5a8081 100644 --- a/searx/https_rules/GoogleAPIs.xml +++ b/searx/plugins/https_rules/GoogleAPIs.xml diff --git a/searx/https_rules/GoogleCanada.xml b/searx/plugins/https_rules/GoogleCanada.xml index d5eefe816..d5eefe816 100644 --- a/searx/https_rules/GoogleCanada.xml +++ b/searx/plugins/https_rules/GoogleCanada.xml diff --git a/searx/https_rules/GoogleImages.xml b/searx/plugins/https_rules/GoogleImages.xml index 0112001e0..0112001e0 100644 --- a/searx/https_rules/GoogleImages.xml +++ b/searx/plugins/https_rules/GoogleImages.xml diff --git a/searx/https_rules/GoogleMainSearch.xml b/searx/plugins/https_rules/GoogleMainSearch.xml index df504d90c..df504d90c 100644 --- a/searx/https_rules/GoogleMainSearch.xml +++ b/searx/plugins/https_rules/GoogleMainSearch.xml diff --git a/searx/https_rules/GoogleMaps.xml b/searx/plugins/https_rules/GoogleMaps.xml index 0f82c5267..0f82c5267 100644 --- a/searx/https_rules/GoogleMaps.xml +++ b/searx/plugins/https_rules/GoogleMaps.xml diff --git a/searx/https_rules/GoogleMelange.xml b/searx/plugins/https_rules/GoogleMelange.xml index ec23cd45f..ec23cd45f 100644 --- a/searx/https_rules/GoogleMelange.xml +++ b/searx/plugins/https_rules/GoogleMelange.xml diff --git a/searx/https_rules/GoogleSearch.xml b/searx/plugins/https_rules/GoogleSearch.xml index 66b7ffdb0..66b7ffdb0 100644 --- a/searx/https_rules/GoogleSearch.xml +++ b/searx/plugins/https_rules/GoogleSearch.xml diff --git a/searx/https_rules/GoogleServices.xml b/searx/plugins/https_rules/GoogleServices.xml index 704646b53..704646b53 100644 --- a/searx/https_rules/GoogleServices.xml +++ b/searx/plugins/https_rules/GoogleServices.xml diff --git a/searx/https_rules/GoogleShopping.xml b/searx/plugins/https_rules/GoogleShopping.xml index 6ba69a91d..6ba69a91d 100644 --- a/searx/https_rules/GoogleShopping.xml +++ b/searx/plugins/https_rules/GoogleShopping.xml diff --git a/searx/https_rules/GoogleSorry.xml b/searx/plugins/https_rules/GoogleSorry.xml index 72a19210d..72a19210d 100644 --- a/searx/https_rules/GoogleSorry.xml +++ b/searx/plugins/https_rules/GoogleSorry.xml diff --git a/searx/https_rules/GoogleTranslate.xml b/searx/plugins/https_rules/GoogleTranslate.xml index a004025ae..a004025ae 100644 --- a/searx/https_rules/GoogleTranslate.xml +++ b/searx/plugins/https_rules/GoogleTranslate.xml diff --git a/searx/https_rules/GoogleVideos.xml b/searx/plugins/https_rules/GoogleVideos.xml index a5e88fcf0..a5e88fcf0 100644 --- a/searx/https_rules/GoogleVideos.xml +++ b/searx/plugins/https_rules/GoogleVideos.xml diff --git a/searx/https_rules/GoogleWatchBlog.xml b/searx/plugins/https_rules/GoogleWatchBlog.xml index afec70c97..afec70c97 100644 --- a/searx/https_rules/GoogleWatchBlog.xml +++ b/searx/plugins/https_rules/GoogleWatchBlog.xml diff --git a/searx/https_rules/Google_App_Engine.xml b/searx/plugins/https_rules/Google_App_Engine.xml index 851e051d1..851e051d1 100644 --- a/searx/https_rules/Google_App_Engine.xml +++ b/searx/plugins/https_rules/Google_App_Engine.xml diff --git a/searx/https_rules/Googleplex.com.xml b/searx/plugins/https_rules/Googleplex.com.xml index 7ddbb5ba9..7ddbb5ba9 100644 --- a/searx/https_rules/Googleplex.com.xml +++ b/searx/plugins/https_rules/Googleplex.com.xml diff --git a/searx/https_rules/OpenStreetMap.xml b/searx/plugins/https_rules/OpenStreetMap.xml index 58a661823..58a661823 100644 --- a/searx/https_rules/OpenStreetMap.xml +++ b/searx/plugins/https_rules/OpenStreetMap.xml diff --git a/searx/https_rules/Rawgithub.com.xml b/searx/plugins/https_rules/Rawgithub.com.xml index 3868f332a..3868f332a 100644 --- a/searx/https_rules/Rawgithub.com.xml +++ b/searx/plugins/https_rules/Rawgithub.com.xml diff --git a/searx/https_rules/Soundcloud.xml b/searx/plugins/https_rules/Soundcloud.xml index 6958e8cbc..6958e8cbc 100644 --- a/searx/https_rules/Soundcloud.xml +++ b/searx/plugins/https_rules/Soundcloud.xml diff --git a/searx/https_rules/ThePirateBay.xml b/searx/plugins/https_rules/ThePirateBay.xml index 010387b6b..010387b6b 100644 --- a/searx/https_rules/ThePirateBay.xml +++ b/searx/plugins/https_rules/ThePirateBay.xml diff --git a/searx/https_rules/Torproject.xml b/searx/plugins/https_rules/Torproject.xml index 69269af7e..69269af7e 100644 --- a/searx/https_rules/Torproject.xml +++ b/searx/plugins/https_rules/Torproject.xml diff --git a/searx/https_rules/Twitter.xml b/searx/plugins/https_rules/Twitter.xml index 3285f44e0..3285f44e0 100644 --- a/searx/https_rules/Twitter.xml +++ b/searx/plugins/https_rules/Twitter.xml diff --git a/searx/https_rules/Vimeo.xml b/searx/plugins/https_rules/Vimeo.xml index f2a3e5764..f2a3e5764 100644 --- a/searx/https_rules/Vimeo.xml +++ b/searx/plugins/https_rules/Vimeo.xml diff --git a/searx/https_rules/WikiLeaks.xml b/searx/plugins/https_rules/WikiLeaks.xml index 977709d2d..977709d2d 100644 --- a/searx/https_rules/WikiLeaks.xml +++ b/searx/plugins/https_rules/WikiLeaks.xml diff --git a/searx/https_rules/Wikimedia.xml b/searx/plugins/https_rules/Wikimedia.xml index 9f25831a2..9f25831a2 100644 --- a/searx/https_rules/Wikimedia.xml +++ b/searx/plugins/https_rules/Wikimedia.xml diff --git a/searx/https_rules/Yahoo.xml b/searx/plugins/https_rules/Yahoo.xml index 33548c4ab..33548c4ab 100644 --- a/searx/https_rules/Yahoo.xml +++ b/searx/plugins/https_rules/Yahoo.xml diff --git a/searx/https_rules/YouTube.xml b/searx/plugins/https_rules/YouTube.xml index bddc2a5f3..bddc2a5f3 100644 --- a/searx/https_rules/YouTube.xml +++ b/searx/plugins/https_rules/YouTube.xml diff --git a/searx/plugins/search_on_category_select.py b/searx/plugins/search_on_category_select.py new file mode 100644 index 000000000..d4b725acf --- /dev/null +++ b/searx/plugins/search_on_category_select.py @@ -0,0 +1,22 @@ +''' +searx is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +searx is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with searx. If not, see < http://www.gnu.org/licenses/ >. + +(C) 2015 by Adam Tauber, <asciimoo@gmail.com> +''' +from flask.ext.babel import gettext +name = gettext('Search on category select') +description = gettext('Perform search immediately if a category selected. Disable to select multiple categories.') +default_on = True + +js_dependencies = ('js/search_on_category_select.js',) diff --git a/searx/plugins/self_ip.py b/searx/plugins/self_ip.py new file mode 100644 index 000000000..5184ea4cf --- /dev/null +++ b/searx/plugins/self_ip.py @@ -0,0 +1,35 @@ +''' +searx is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +searx is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with searx. If not, see < http://www.gnu.org/licenses/ >. + +(C) 2015 by Adam Tauber, <asciimoo@gmail.com> +''' +from flask.ext.babel import gettext +name = "Self IP" +description = gettext('Display your source IP address if the query expression is "ip"') +default_on = True + + +# attach callback to the post search hook +# request: flask request object +# ctx: the whole local context of the pre search hook +def post_search(request, ctx): + if ctx['search'].query == 'ip': + x_forwarded_for = request.headers.getlist("X-Forwarded-For") + if x_forwarded_for: + ip = x_forwarded_for[0] + else: + ip = request.remote_addr + ctx['search'].answers.clear() + ctx['search'].answers.add(ip) + return True diff --git a/searx/poolrequests.py b/searx/poolrequests.py index 65853c2e9..e2a757665 100644 --- a/searx/poolrequests.py +++ b/searx/poolrequests.py @@ -1,20 +1,63 @@ import requests +from itertools import cycle +from searx import settings -the_http_adapter = requests.adapters.HTTPAdapter(pool_connections=100) -the_https_adapter = requests.adapters.HTTPAdapter(pool_connections=100) +class HTTPAdapterWithConnParams(requests.adapters.HTTPAdapter): + + def __init__(self, pool_connections=requests.adapters.DEFAULT_POOLSIZE, + pool_maxsize=requests.adapters.DEFAULT_POOLSIZE, + max_retries=requests.adapters.DEFAULT_RETRIES, + pool_block=requests.adapters.DEFAULT_POOLBLOCK, + **conn_params): + if max_retries == requests.adapters.DEFAULT_RETRIES: + self.max_retries = requests.adapters.Retry(0, read=False) + else: + self.max_retries = requests.adapters.Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(requests.adapters.HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + self._conn_params = conn_params + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block, **conn_params) + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # because self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block, **self._conn_params) + + +if settings.get('source_ips'): + http_adapters = cycle(HTTPAdapterWithConnParams(pool_connections=100, source_address=(source_ip, 0)) + for source_ip in settings['source_ips']) + https_adapters = cycle(HTTPAdapterWithConnParams(pool_connections=100, source_address=(source_ip, 0)) + for source_ip in settings['source_ips']) +else: + http_adapters = cycle((HTTPAdapterWithConnParams(pool_connections=100), )) + https_adapters = cycle((HTTPAdapterWithConnParams(pool_connections=100), )) class SessionSinglePool(requests.Session): def __init__(self): - global the_https_adapter, the_http_adapter super(SessionSinglePool, self).__init__() # reuse the same adapters self.adapters.clear() - self.mount('https://', the_https_adapter) - self.mount('http://', the_http_adapter) + self.mount('https://', next(https_adapters)) + self.mount('http://', next(http_adapters)) def close(self): """Call super, but clear adapters since there are managed globaly""" @@ -23,8 +66,10 @@ class SessionSinglePool(requests.Session): def request(method, url, **kwargs): - """same as requests/requests/api.py request(...) except it use SessionSinglePool""" + """same as requests/requests/api.py request(...) except it use SessionSinglePool and force proxies""" + global settings session = SessionSinglePool() + kwargs['proxies'] = settings.get('outgoing_proxies', None) response = session.request(method=method, url=url, **kwargs) session.close() return response diff --git a/searx/search.py b/searx/search.py index 83163d1e5..862b17e33 100644 --- a/searx/search.py +++ b/searx/search.py @@ -329,8 +329,8 @@ class Search(object): self.blocked_engines = get_blocked_engines(engines, request.cookies) self.results = [] - self.suggestions = [] - self.answers = [] + self.suggestions = set() + self.answers = set() self.infoboxes = [] self.request_data = {} @@ -429,9 +429,6 @@ class Search(object): requests = [] results_queue = Queue() results = {} - suggestions = set() - answers = set() - infoboxes = [] # increase number of searches number_of_searches += 1 @@ -511,7 +508,7 @@ class Search(object): selected_engine['name'])) if not requests: - return results, suggestions, answers, infoboxes + return self # send all search-request threaded_requests(requests) @@ -519,19 +516,19 @@ class Search(object): engine_name, engine_results = results_queue.get_nowait() # TODO type checks - [suggestions.add(x['suggestion']) + [self.suggestions.add(x['suggestion']) for x in list(engine_results) if 'suggestion' in x and engine_results.remove(x) is None] - [answers.add(x['answer']) + [self.answers.add(x['answer']) for x in list(engine_results) if 'answer' in x and engine_results.remove(x) is None] - infoboxes.extend(x for x in list(engine_results) - if 'infobox' in x - and engine_results.remove(x) is None) + self.infoboxes.extend(x for x in list(engine_results) + if 'infobox' in x + and engine_results.remove(x) is None) results[engine_name] = engine_results @@ -541,16 +538,16 @@ class Search(object): engines[engine_name].stats['result_count'] += len(engine_results) # score results and remove duplications - results = score_results(results) + self.results = score_results(results) # merge infoboxes according to their ids - infoboxes = merge_infoboxes(infoboxes) + self.infoboxes = merge_infoboxes(self.infoboxes) # update engine stats, using calculated score - for result in results: + for result in self.results: for res_engine in result['engines']: engines[result['engine']]\ .stats['score_count'] += result['score'] # return results, suggestions, answers and infoboxes - return results, suggestions, answers, infoboxes + return self diff --git a/searx/settings.yml b/searx/settings.yml index 8e2833ef0..78bdc37bb 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -6,11 +6,23 @@ server: base_url : False # Set custom base_url. Possible values: False or "https://your.custom.host/location/" themes_path : "" # Custom ui themes path - leave it blank if you didn't change default_theme : oscar # ui theme - https_rewrite : True # Force rewrite result urls. See searx/https_rewrite.py useragent_suffix : "" # suffix of searx_useragent, could contain informations like an email address to the administrator image_proxy : False # Proxying image results through searx default_locale : "" # Default interface locale - leave blank to detect from browser information or use codes from the 'locales' config section +# 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 +#outgoing_proxies : +# http : http://127.0.0.1:8080 +# https: http://127.0.0.1:8080 + +# uncomment below section only if you have more than one network interface +# which can be the source of outgoing search requests +#source_ips: +# - 1.1.1.1 +# - 1.1.1.2 + engines: - name : wikipedia engine : mediawiki @@ -20,24 +32,16 @@ engines: - name : bing engine : bing - locale : en-US shortcut : bi - name : bing images engine : bing_images - locale : en-US shortcut : bii - name : bing news engine : bing_news - locale : en-US shortcut : bin - - name : blekko images - engine : blekko_images - locale : en-US - shortcut : bli - - name : btdigg engine : btdigg shortcut : bt @@ -78,12 +82,6 @@ engines: # shortcut : fa # api_key : 'apikey' # required! -# down - website is under criminal investigation by the UK -# - name : filecrop -# engine : filecrop -# categories : files -# shortcut : fc - - name : 500px engine : www500px shortcut : px @@ -103,14 +101,10 @@ engines: # Or you can use the html non-stable engine, activated by default engine : flickr_noapi - - name : general-file - engine : generalfile - shortcut : gf - disabled : True - - name : gigablast engine : gigablast shortcut : gb + disabled: True - name : github engine : github @@ -195,6 +189,10 @@ engines: shortcut : scc disabled : True + - name : spotify + engine : spotify + shortcut : stf + - name : subtitleseeker engine : subtitleseeker shortcut : ss @@ -250,9 +248,14 @@ engines: - name : vimeo engine : vimeo - locale : en-US shortcut : vm +#The blekko technology and team have joined IBM Watson! -> https://blekko.com/ +# - name : blekko images +# engine : blekko_images +# locale : en-US +# shortcut : bli + # - name : yacy # engine : yacy # shortcut : ya diff --git a/searx/static/js/search_on_category_select.js b/searx/static/js/search_on_category_select.js new file mode 100644 index 000000000..6156ca4e8 --- /dev/null +++ b/searx/static/js/search_on_category_select.js @@ -0,0 +1,14 @@ +$(document).ready(function() { + if($('#q')) { + $('#categories label').click(function(e) { + $('#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'); + $('#search_form').submit(); + return false; + }); + } +}); diff --git a/searx/static/themes/pix-art/css/style.css b/searx/static/themes/pix-art/css/style.css new file mode 100644 index 000000000..229cf86a2 --- /dev/null +++ b/searx/static/themes/pix-art/css/style.css @@ -0,0 +1 @@ +html{font-family:"Courier New",Courier,monospace;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444;padding:0;margin:0}body,#container{padding:0;margin:0}canvas{image-rendering:optimizeSpeed;image-rendering:-moz-crisp-edges;image-rendering:-webkit-optimize-contrast;image-rendering:optimize-contrast;image-rendering:pixelated;-ms-interpolation-mode:nearest-neighbor;width:32px;height:32px}#container{width:100%;position:absolute;top:0}.search{padding:0;margin:0}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q{background:none repeat scroll 0 0 #fff;border:1px solid #3498db;color:#222;font-size:16px;font-family:"Courier New",Courier,monospace;height:28px;margin:0;outline:medium none;padding:2px;padding-left:8px;padding-right:0 !important;width:100%;z-index:2}#search_submit{position:absolute;top:15px;right:5px;padding:0;border:0;background:url('../img/search-icon-pixel.png') no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:24px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498db;padding:4px 10px}a:link.hmarg{color:#3498db}a:visited.hmarg{color:#3498db}a:active.hmarg{color:#3498db}a:hover.hmarg{color:#3498db}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url('../img/searx-pixel.png') no-repeat;width:100%;min-height:80px;background-position:center}div.title h1{visibility:hidden}input[type="button"],input[type="submit"]{font-family:"Courier New",Courier,monospace;padding:4px 12px;margin:2px 4px;display:inline-block;background:#3498db;color:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}input[type="button"]:disabled{cursor:progress}input[type="checkbox"]{visibility:hidden}fieldset{margin:8px;border:1px solid #3498db}#logo{position:absolute;top:13px;left:10px}#categories{margin:0 10px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type="checkbox"]:checked+label{background:#3498db;color:#fff}.engine_checkbox{padding:4px}label.allow{background:#e74c3c;padding:4px 8px;color:#fff;display:none}label.deny{background:#2ecc71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type="checkbox"]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type="checkbox"]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8e44ad}.engines{color:#888}.small_font{font-size:.8em}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.percentage{position:relative;width:300px}.percentage div{background:#444}table{width:100%}td{padding:0 4px}tr:hover{background:#ddd}#results{margin:auto;padding:0;width:50em;margin-bottom:20px}#search_url{margin-top:8px}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url('../img/preference-icon-pixel.png') no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}#pagination{clear:both;text-align:center}#pagination br{clear:both}#apis{margin-top:8px;clear:both}#categories_container{position:relative}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed !important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto}#apis{display:none}#search_url{display:none}#logo{display:none}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:none repeat scroll 0 0 #3498db;border:0 none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#fff}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:white;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8}
\ No newline at end of file diff --git a/searx/static/themes/pix-art/img/favicon.png b/searx/static/themes/pix-art/img/favicon.png Binary files differnew file mode 100644 index 000000000..28afb0111 --- /dev/null +++ b/searx/static/themes/pix-art/img/favicon.png diff --git a/searx/static/themes/pix-art/img/preference-icon-pixel.png b/searx/static/themes/pix-art/img/preference-icon-pixel.png Binary files differnew file mode 100644 index 000000000..424e01e87 --- /dev/null +++ b/searx/static/themes/pix-art/img/preference-icon-pixel.png diff --git a/searx/static/themes/pix-art/img/search-icon-pixel.png b/searx/static/themes/pix-art/img/search-icon-pixel.png Binary files differnew file mode 100644 index 000000000..8235882eb --- /dev/null +++ b/searx/static/themes/pix-art/img/search-icon-pixel.png diff --git a/searx/static/themes/pix-art/img/searx-pixel-small.png b/searx/static/themes/pix-art/img/searx-pixel-small.png Binary files differnew file mode 100644 index 000000000..76f381c5c --- /dev/null +++ b/searx/static/themes/pix-art/img/searx-pixel-small.png diff --git a/searx/static/themes/pix-art/img/searx-pixel.png b/searx/static/themes/pix-art/img/searx-pixel.png Binary files differnew file mode 100644 index 000000000..6b8dbace6 --- /dev/null +++ b/searx/static/themes/pix-art/img/searx-pixel.png diff --git a/searx/static/themes/pix-art/js/searx.js b/searx/static/themes/pix-art/js/searx.js new file mode 100644 index 000000000..5eb0af99d --- /dev/null +++ b/searx/static/themes/pix-art/js/searx.js @@ -0,0 +1,141 @@ +if(searx.autocompleter) { + window.addEvent('domready', function() { + new Autocompleter.Request.JSON('q', '/autocompleter', { + postVar:'q', + postData:{ + 'format': 'json' + }, + ajaxOptions:{ + timeout: 5 // Correct option? + }, + 'minLength': 4, + 'selectMode': false, + cache: true, + delay: 300 + }); + }); +} + +(function (w, d) { + 'use strict'; + function addListener(el, type, fn) { + if (el.addEventListener) { + el.addEventListener(type, fn, false); + } else { + el.attachEvent('on' + type, fn); + } + } + + function placeCursorAtEnd() { + if (this.setSelectionRange) { + var len = this.value.length * 2; + this.setSelectionRange(len, len); + } + } + + addListener(w, 'load', function () { + var qinput = d.getElementById('q'); + if (qinput !== null && qinput.value === "") { + addListener(qinput, 'focus', placeCursorAtEnd); + qinput.focus(); + } + }); + + if (!!('ontouchstart' in window)) { + document.getElementsByTagName("html")[0].className += " touch"; + } + +})(window, document); + +var xmlHttp + +function GetXmlHttpObject(){ + + var xmlHttp = null; + + try { + // Firefox, Opera 8.0+, Safari + xmlHttp = new XMLHttpRequest(); + } + catch (e) { + // Internet Explorer + try { + xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); + } + catch (e){ + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + } + return xmlHttp; +} + +var timer; + +// Load more results +function load_more(query,page){ + + xmlHttp = GetXmlHttpObject(); + clearTimeout(timer); + + if(xmlHttp == null){ + alert ("Your browser does not support AJAX!"); + return; + } + + favicons[page] = []; + + xmlHttp.onreadystatechange = function(){ + + var loader = document.getElementById('load_more'); + + // If 4, response OK + if (xmlHttp.readyState == 4){ + + var res = xmlHttp.responseText; + + clearTimeout(timer); + timer = setTimeout(function(){},6000); + + var results = document.getElementById('results_list'); + + var newNode = document.createElement('span'); + newNode.innerHTML = res; + results_list.appendChild(newNode); + + var scripts = newNode.getElementsByTagName('script'); + for (var ix = 0; ix < scripts.length; ix++) { + eval(scripts[ix].text); + } + + load_images(page); + document.getElementById("load_more").onclick = function() { load_more(query, (page+1)); } + loader.removeAttribute("disabled"); + + } else { + loader.disabled = 'disabled'; + } + } + var url = "/"; + var params = "q="+query+"&pageno="+page+"&category_general=1&category_files=1&category_images=1&category_it=1&category_map=1&category_music=1&category_news=1&category_social+media=1&category_videos=1"; + xmlHttp.open("POST",url,true); + xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xmlHttp.setRequestHeader("Content-length", params.length); + xmlHttp.setRequestHeader("Connection", "close"); + xmlHttp.send(params); +} + +// Load the images on the canvas in the page +function load_images(page){ + var arrayLength = favicons[page].length; + for (var i = 1; i < arrayLength+1; i++) { + var img = new Image(); + img.setAttribute("i",i) + img.onload = function () { + var id = 'canvas-'+page+'-'+this.getAttribute("i"); + var can = document.getElementById(id); + var ctx = can.getContext("2d"); + ctx.drawImage(this, 0, 0, 16, 16); + }; + img.src = favicons[page][i]; + } +}
\ No newline at end of file diff --git a/searx/static/themes/pix-art/less/definitions.less b/searx/static/themes/pix-art/less/definitions.less new file mode 100644 index 000000000..0ac0cc90c --- /dev/null +++ b/searx/static/themes/pix-art/less/definitions.less @@ -0,0 +1,119 @@ +/* + * searx, A privacy-respecting, hackable metasearch engine + * + * To change the colors of the site, simple edit this variables + */ + +/// Basic Colors + +@color-base: #3498DB; +@color-base-dark: #2980B9; +@color-base-light: #ECF0F1; +@color-highlight: #094089; +@color-black: #000000; + +/// General + +@color-font: #444; +@color-font-light: #888; + +@color-red: #C0392B; + +@color-url-font: #1a11be; +@color-url-visited-font: #8E44AD; +@results-width: 50em; + + +/// Start-Screen + +// hmarg +@color-hmarg-border: @color-base; +@color-hmarg-font: @color-base; +@color-hmarg-font-hover: @color-base; + + +/// Search-Input + +@color-search-border: @color-base; +@color-search-background: #FFF; +@color-search-font: #222; + +/// Autocompleter + +@color-autocompleter-choices-background: #FFF; +@color-autocompleter-choices-border: @color-base; +@color-autocompleter-choices-border-left-right: @color-base; +@color-autocompleter-choices-border-bottom: @color-base; + +@color-autocompleter-choices-font: #444; + +/// Answers +@color-answers-border: @color-base-dark; + +// Selected +@color-autocompleter-selected-background: #444; +@color-autocompleter-selected-font: #FFF; +@color-autocompleter-selected-queried-font: #9FCFFF; + +/// Categories + +@color-categories-item-selected: @color-base; +@color-categories-item-selected-font: #FFF; + +@color-categories-item-border-selected: @color-base-dark; +@color-categories-item-border-unselected: #E8E7E6; +@color-categories-item-border-unselected-hover: @color-base; + + +/// Results + +@color-suggestions-button-background: @color-base; +@color-suggestions-button-font: #FFF; + +@color-download-button-background: @color-base; +@color-download-button-font: #FFF; + +@color-result-search-background: @color-base-light; + +@color-result-definition-border: gray; +@color-result-torrent-border: lightgray; +@color-result-top-border: #E8E7E6; + +// Link to result +@color-result-link-font: @color-base-dark; +@color-result-link-visited-font: @color-url-visited-font; + +// Url to result +@color-result-url-font: @color-red; + +// Publish Date +@color-result-publishdate-font: @color-font-light; + +// Images +@color-result-image-span-background-hover: rgba(0, 0, 0, 0.6); +@color-result-image-span-font: #FFF; + +// Search-URL +@color-result-search-url-border: #888; +@color-result-search-url-font: #444; + + +/// Settings + +@color-settings-fieldset: @color-base; +@color-settings-tr-hover: #DDD; + +// Labels +@color-settings-label-allowed-background: #E74C3C; +@color-settings-label-allowed-font: #FFF; + +@color-settings-label-deny-background: #2ECC71; +@color-settings-label-deny-font: @color-font; + +@color-settings-return-background: @color-base; +@color-settings-return-font: #FFF; + +/// Other + +@color-engines-font: @color-font-light; +@color-percentage-div-background: #444; diff --git a/searx/static/themes/pix-art/less/mixins.less b/searx/static/themes/pix-art/less/mixins.less new file mode 100644 index 000000000..dbccce6e3 --- /dev/null +++ b/searx/static/themes/pix-art/less/mixins.less @@ -0,0 +1,27 @@ +/* + * searx, A privacy-respecting, hackable metasearch engine + */ + +// Mixins + +.text-size-adjust (@property: 100%) { + -webkit-text-size-adjust: @property; + -ms-text-size-adjust: @property; + -moz-text-size-adjust: @property; + text-size-adjust: @property; +} + +.rounded-corners (@radius: 4px) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +.user-select () { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/searx/static/themes/pix-art/less/search.less b/searx/static/themes/pix-art/less/search.less new file mode 100644 index 000000000..f5ac33ee1 --- /dev/null +++ b/searx/static/themes/pix-art/less/search.less @@ -0,0 +1,57 @@ +/* + * searx, A privacy-respecting, hackable metasearch engine + */ + +.search { + padding: 0; + margin: 0; +} + +#search_wrapper { + position: relative; + width: @results-width; + padding: 10px; +} + +.center #search_wrapper { + margin-left: auto; + margin-right: auto; +} + +.q { + background: none repeat scroll 0 0 @color-search-background; + border: 1px solid @color-search-border; + color: @color-search-font; + font-size: 16px; + font-family: "Courier New", Courier, monospace; + height: 28px; + margin: 0; + outline: medium none; + padding: 2px; + padding-left: 8px; + padding-right: 0px !important; + width: 100%; + z-index: 2; +} + +#search_submit { + position: absolute; + top: 15px; + right: 5px; + padding: 0; + border: 0; + background: url('../img/search-icon-pixel.png') no-repeat; + background-size: 24px 24px; + opacity: 0.8; + width: 24px; + height: 24px; + font-size: 0; +} + +@media screen and (max-width: @results-width) { + #search_wrapper { + width: 90%; + clear:both; + overflow: hidden + } +} diff --git a/searx/static/themes/pix-art/less/style.less b/searx/static/themes/pix-art/less/style.less new file mode 100644 index 000000000..a2088e985 --- /dev/null +++ b/searx/static/themes/pix-art/less/style.less @@ -0,0 +1,451 @@ +/* + * searx, A privacy-respecting, hackable metasearch engine + * + * To convert "style.less" to "style.css" run: $make styles + */ + +@import "definitions.less"; + +@import "mixins.less"; + + +// Main LESS-Code + +html { + font-family: "Courier New", Courier, monospace; + font-size: 0.9em; + .text-size-adjust; + color: @color-font; + padding: 0; + margin: 0; +} + +body, #container { + padding: 0; + margin: 0; +} + +canvas { + image-rendering: optimizeSpeed; + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: optimize-contrast; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; + width:32px; + height:32px; +} + +#container { + width: 100%; + position: absolute; + top: 0; +} + +// Search-Field + +@import "search.less"; + +.row { + max-width: 800px; + margin: 20px auto; + text-align: justify; + + h1 { + font-size: 3em; + margin-top: 50px; + } + + p { + padding: 0 10px; + max-width: 700px; + } + + h3,ul { + margin: 4px 8px; + } +} + +.hmarg { + margin: 0 20px; + border: 1px solid @color-hmarg-border; + padding: 4px 10px; +} + +a { + &:link.hmarg { + color: @color-hmarg-font; + } + + &:visited.hmarg { + color: @color-hmarg-font; + } + + &:active.hmarg { + color: @color-hmarg-font-hover; + } + + &:hover.hmarg { + color: @color-hmarg-font-hover; + } +} + +.top_margin { + margin-top: 60px; +} + +.center { + text-align: center; +} + +h1 { + font-size: 5em; +} + +div.title { + background: url('../img/searx-pixel.png') no-repeat; + width: 100%; + min-height: 80px; + background-position: center; + + h1 { + visibility: hidden; + } +} + +input[type="button"], +input[type="submit"] { + font-family: "Courier New", Courier, monospace; + padding: 4px 12px; + margin: 2px 4px; + display: inline-block; + background: @color-download-button-background; + color: @color-download-button-font; + .rounded-corners; + border: 0; + cursor: pointer; +} + +input[type="button"]:disabled { + cursor: progress; +} + +input[type="checkbox"] { + visibility: hidden; +} + +fieldset { + margin: 8px; + border: 1px solid @color-settings-fieldset; +} + +#logo { + position: absolute; + top: 13px; + left: 10px; +} + +#categories { + margin: 0 10px; + .user-select; +} + +.checkbox_container { + display: inline-block; + position: relative; + margin: 0 3px; + padding: 0px; + + input { + display: none; + } +} + +.checkbox_container label, .engine_checkbox label { + cursor: pointer; + padding: 4px 10px; + margin: 0; + display: block; + text-transform: capitalize; + .user-select; +} + +.checkbox_container input[type="checkbox"]:checked + label { + background: @color-categories-item-selected; + color: @color-categories-item-selected-font; +} + +.engine_checkbox { + padding: 4px; +} + +label { + &.allow { + background: @color-settings-label-allowed-background; + padding: 4px 8px; + color: @color-settings-label-allowed-font; + display: none; + } + + &.deny { + background: @color-settings-label-deny-background; + padding: 4px 8px; + color: @color-settings-label-deny-font; + display: inline; + } +} + +.engine_checkbox input[type="checkbox"]:checked + label { + &:nth-child(2) + label { + display: none; + } + + &.allow { + display: inline; + } +} + +a { + text-decoration: none; + color: @color-url-font; + + &:visited { + color: @color-url-visited-font; + } +} + +.engines { + color: @color-engines-font; +} + +.small_font { + font-size: 0.8em; +} + +.small p { + margin: 2px 0; +} + +.right { + float: right; +} + +.invisible { + display: none; +} + +.left { + float: left; +} + +.highlight { + color: @color-highlight; +} + +.content .highlight { + color: @color-black; +} + +.percentage { + position: relative; + width: 300px; + + div { + background: @color-percentage-div-background; + } +} + +table { + width: 100%; +} + +td { + padding: 0 4px; +} + +tr { + &:hover { + background: @color-settings-tr-hover; + } +} + +#results { + margin: auto; + padding: 0; + width: @results-width; + margin-bottom: 20px; +} + +#search_url { + margin-top: 8px; + + input { + border: 1px solid @color-result-search-url-border; + padding: 4px; + color: @color-result-search-url-font; + width: 14em; + display: block; + margin: 4px; + font-size: 0.8em; + } +} + +#preferences { + top: 10px; + padding: 0; + border: 0; + background: url('../img/preference-icon-pixel.png') no-repeat; + background-size: 28px 28px; + opacity: 0.8; + width: 28px; + height: 30px; + display: block; + + * { + display: none; + } +} + +#pagination { + clear: both; + text-align: center; + br { + clear: both; + } +} + +#apis { + margin-top: 8px; + clear: both; +} + +#categories_container { + position: relative; +} + +@media screen and (max-width: @results-width) { + + #results { + margin: auto; + padding: 0; + width: 90%; + } + + .checkbox_container { + display: block; + width: 90%; + //float: left; + + label { + border-bottom: 0; + } + } + + .preferences_container { + display: none; + postion: fixed !important; + top: 100px; + right: 0px; + } + +} + +@media screen and (max-width: 75em) { + + div.title { + + h1 { + font-size: 1em; + } + } + + html.touch #categories { + width: 95%; + height: 30px; + text-align: left; + overflow-x: scroll; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + + #categories_container { + width: 1000px; + width: -moz-max-content; + width: -webkit-max-content; + width: max-content; + + .checkbox_container { + display: inline-block; + width: auto; + } + } + } + + #categories { + font-size: 90%; + clear: both; + + .checkbox_container { + margin-top: 2px; + margin: auto; + } + } + + #categories { + font-size: 90%; + clear: both; + + .checkbox_container { + margin-top: 2px; + margin: auto; + } + } + + #apis { + display: none; + } + + #search_url { + display: none; + } + + #logo { + display: none; + } +} + +.favicon { + float: left; + margin-right: 4px; + margin-top: 2px; +} + +.preferences_back { + background: none repeat scroll 0 0 @color-settings-return-background; + border: 0 none; + .rounded-corners; + cursor: pointer; + display: inline-block; + margin: 2px 4px; + padding: 4px 6px; + + a { + color: @color-settings-return-font; + } +} + +.hidden { + opacity: 0; + overflow: hidden; + font-size: 0.8em; + position: absolute; + bottom: -20px; + width: 100%; + text-position: center; + background: white; + transition: opacity 1s ease; +} + +#categories_container:hover .hidden { + transition: opacity 1s ease; + opacity: 0.8; +} diff --git a/searx/templates/courgette/base.html b/searx/templates/courgette/base.html index 58957335d..276fae870 100644 --- a/searx/templates/courgette/base.html +++ b/searx/templates/courgette/base.html @@ -5,6 +5,7 @@ <meta name="description" content="Searx - a privacy-respecting, hackable metasearch engine" /> <meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> <meta name="generator" content="searx/{{ searx_version }}"> + <meta name="referrer" content="no-referrer"> <meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=1" /> <title>{% block title %}{% endblock %}searx</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css" media="screen" /> @@ -39,4 +40,4 @@ <script src="{{ url_for('static', filename='js/searx.js') }}" ></script> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/searx/templates/courgette/preferences.html b/searx/templates/courgette/preferences.html index 2abfafb13..2afb74d11 100644 --- a/searx/templates/courgette/preferences.html +++ b/searx/templates/courgette/preferences.html @@ -101,8 +101,8 @@ <th>{{ _('Category') }}</th> <th>{{ _('Allow') }} / {{ _('Block') }}</th> </tr> - {% for (categ,search_engines) in categs %} - {% for search_engine in search_engines %} + {% for categ in categories %} + {% for search_engine in engines_by_category[categ] %} {% if not search_engine.private %} <tr> @@ -125,7 +125,8 @@ </p> <input type="submit" value="{{ _('save') }}" /> + <div class="right preferences_back"><a href="{{ url_for('clear_cookies') }}">{{ _('Reset defaults') }}</a></div> <div class="right preferences_back"><a href="{{ url_for('index') }}">{{ _('back') }}</a></div> - </form> + </form> </div> {% endblock %} diff --git a/searx/templates/courgette/result_templates/code.html b/searx/templates/courgette/result_templates/code.html index 382ce2a56..726f305cb 100644 --- a/searx/templates/courgette/result_templates/code.html +++ b/searx/templates/courgette/result_templates/code.html @@ -1,8 +1,8 @@ <div class="result {{ result.class }}">
- <h3 class="result_title">{% if result['favicon'] %}<img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" alt="{{result['favicon']}}" />{% endif %}<a href="{{ result.url }}">{{ result.title|safe }}</a></h3>
+ <h3 class="result_title">{% if result['favicon'] %}<img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" alt="{{result['favicon']}}" />{% endif %}<a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %}
<p class="content">{% if result.img_src %}<img src="{{ image_proxify(result.img_src) }}" class="image" />{% endif %}{% if result.content %}{{ result.content|safe }}<br class="last"/>{% endif %}</p>
- {% if result.repository %}<p class="content"><a href="{{ result.repository|safe }}">{{ result.repository }}</a></p>{% endif %}
+ {% if result.repository %}<p class="content"><a href="{{ result.repository|safe }}" rel="noreferrer">{{ result.repository }}</a></p>{% endif %}
<div dir="ltr">
{{ result.codelines|code_highlighter(result.code_language)|safe }}
</div>
diff --git a/searx/templates/courgette/result_templates/default.html b/searx/templates/courgette/result_templates/default.html index f5f5acef7..585ecf3f5 100644 --- a/searx/templates/courgette/result_templates/default.html +++ b/searx/templates/courgette/result_templates/default.html @@ -5,9 +5,9 @@ {% endif %} <div> - <h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3> + <h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %} <p class="content">{% if result.content %}{{ result.content|safe }}<br />{% endif %}</p> <p class="url">{{ result.pretty_url }}‎</p> </div> -</div>
\ No newline at end of file +</div> diff --git a/searx/templates/courgette/result_templates/images.html b/searx/templates/courgette/result_templates/images.html index 7228578e7..87fc7744c 100644 --- a/searx/templates/courgette/result_templates/images.html +++ b/searx/templates/courgette/result_templates/images.html @@ -1,6 +1,6 @@ <div class="image_result"> <p> - <a href="{{ result.img_src }}"><img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> - <span class="url"><a href="{{ result.url }}" class="small_font">{{ _('original context') }}</a></span> + <a href="{{ result.img_src }}" rel="noreferrer"><img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> + <span class="url"><a href="{{ result.url }}" rel="noreferrer" class="small_font">{{ _('original context') }}</a></span> </p> </div> diff --git a/searx/templates/courgette/result_templates/map.html b/searx/templates/courgette/result_templates/map.html index f5f5acef7..585ecf3f5 100644 --- a/searx/templates/courgette/result_templates/map.html +++ b/searx/templates/courgette/result_templates/map.html @@ -5,9 +5,9 @@ {% endif %} <div> - <h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3> + <h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %} <p class="content">{% if result.content %}{{ result.content|safe }}<br />{% endif %}</p> <p class="url">{{ result.pretty_url }}‎</p> </div> -</div>
\ No newline at end of file +</div> diff --git a/searx/templates/courgette/result_templates/torrent.html b/searx/templates/courgette/result_templates/torrent.html index b961eb09b..33b574244 100644 --- a/searx/templates/courgette/result_templates/torrent.html +++ b/searx/templates/courgette/result_templates/torrent.html @@ -2,12 +2,12 @@ {% if "icon_"~result.engine~".ico" in favicons %} <img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" /> {% endif %} - <h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3> + <h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> {% if result.content %}<span class="content">{{ result.content|safe }}</span><br />{% endif %} <span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span><br /> <span> {% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %} - {% if result.torrentfile %}<a href="{{ result.torrentfile }}" class="torrentfile">{{ _('torrent file') }}</a>{% endif %} + {% if result.torrentfile %}<a href="{{ result.torrentfile }}" class="torrentfile" rel="noreferrer">{{ _('torrent file') }}</a>{% endif %} </span> <p class="url">{{ result.pretty_url }}‎</p> -</div>
\ No newline at end of file +</div> diff --git a/searx/templates/courgette/result_templates/videos.html b/searx/templates/courgette/result_templates/videos.html index 4e0174ba6..ceed8b28f 100644 --- a/searx/templates/courgette/result_templates/videos.html +++ b/searx/templates/courgette/result_templates/videos.html @@ -3,8 +3,8 @@ <img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" /> {% endif %} - <h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3> + <h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span><br />{% endif %} - <a href="{{ result.url }}"><img width="400" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> + <a href="{{ result.url }}" rel="noreferrer"><img width="400" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> <p class="url">{{ result.pretty_url }}‎</p> </div> diff --git a/searx/templates/default/base.html b/searx/templates/default/base.html index fa96d9437..143bdb8d2 100644 --- a/searx/templates/default/base.html +++ b/searx/templates/default/base.html @@ -5,6 +5,7 @@ <meta name="description" content="Searx - a privacy-respecting, hackable metasearch engine" /> <meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> <meta name="generator" content="searx/{{ searx_version }}"> + <meta name="referrer" content="no-referrer"> <meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=1" /> <title>{% block title %}{% endblock %}searx</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css" media="screen" /> diff --git a/searx/templates/default/infobox.html b/searx/templates/default/infobox.html index 50f5e098e..1733f7753 100644 --- a/searx/templates/default/infobox.html +++ b/searx/templates/default/infobox.html @@ -17,7 +17,7 @@ <div class="urls"> <ul> {% for url in infobox.urls %} - <li class="url"><a href="{{ url.url }}">{{ url.title }}</a></li> + <li class="url"><a href="{{ url.url }}" rel="noreferrer">{{ url.title }}</a></li> {% endfor %} </ul> </div> diff --git a/searx/templates/default/preferences.html b/searx/templates/default/preferences.html index e03c18e3f..0afe9f7d0 100644 --- a/searx/templates/default/preferences.html +++ b/searx/templates/default/preferences.html @@ -89,8 +89,8 @@ <th>{{ _('Category') }}</th> <th>{{ _('Allow') }} / {{ _('Block') }}</th> </tr> - {% for (categ,search_engines) in categs %} - {% for search_engine in search_engines %} + {% for categ in categories %} + {% for search_engine in engines_by_category[categ] %} {% if not search_engine.private %} <tr> @@ -113,7 +113,8 @@ </p> <input type="submit" value="{{ _('save') }}" /> + <div class="{% if rtl %}left{% else %}right{% endif %} preferences_back"><a href="{{ url_for('clear_cookies') }}">{{ _('Reset defaults') }}</a></div> <div class="{% if rtl %}left{% else %}right{% endif %} preferences_back"><a href="{{ url_for('index') }}">{{ _('back') }}</a></div> - </form> + </form> </div> {% endblock %} diff --git a/searx/templates/default/result_templates/code.html b/searx/templates/default/result_templates/code.html index 78eda1884..ad1d97e9e 100644 --- a/searx/templates/default/result_templates/code.html +++ b/searx/templates/default/result_templates/code.html @@ -1,9 +1,9 @@ <div class="result {{ result.class }}">
- <h3 class="result_title"> {% if result['favicon'] %}<img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" alt="{{result['favicon']}}" />{% endif %}<a href="{{ result.url }}">{{ result.title|safe }}</a></h3>
- <p class="url">{{ result.pretty_url }}‎ <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}">{{ _('cached') }}</a></p>
+ <h3 class="result_title"> {% if result['favicon'] %}<img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" alt="{{result['favicon']}}" />{% endif %}<a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3>
+ <p class="url">{{ result.pretty_url }}‎ <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ _('cached') }}</a></p>
{% if result.publishedDate %}<p class="published_date">{{ result.publishedDate }}</p>{% endif %}
<p class="content">{% if result.img_src %}<img src="{{ image_proxify(result.img_src) }}" class="image" />{% endif %}{% if result.content %}{{ result.content|safe }}<br class="last"/>{% endif %}</p>
- {% if result.repository %}<p class="result-content"><a href="{{ result.repository|safe }}">{{ result.repository }}</a></p>{% endif %}
+ {% if result.repository %}<p class="result-content"><a href="{{ result.repository|safe }}" rel="noreferrer">{{ result.repository }}</a></p>{% endif %}
<div dir="ltr">
{{ result.codelines|code_highlighter(result.code_language)|safe }}
diff --git a/searx/templates/default/result_templates/default.html b/searx/templates/default/result_templates/default.html index 4cbf231f1..89091e280 100644 --- a/searx/templates/default/result_templates/default.html +++ b/searx/templates/default/result_templates/default.html @@ -1,6 +1,6 @@ <div class="result {{ result.class }}"> - <h3 class="result_title">{% if "icon_"~result.engine~".ico" in favicons %}<img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />{% endif %}<a href="{{ result.url }}">{{ result.title|safe }}</a></h3> - <p class="url">{{ result.pretty_url }}‎ <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}">{{ _('cached') }}</a> + <h3 class="result_title">{% if "icon_"~result.engine~".ico" in favicons %}<img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />{% endif %}<a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> + <p class="url">{{ result.pretty_url }}‎ <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ _('cached') }}</a> {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %}</p> <p class="content">{% if result.img_src %}<img src="{{ image_proxify(result.img_src) }}" class="image" />{% endif %}{% if result.content %}{{ result.content|safe }}<br class="last"/>{% endif %}</p> </div> diff --git a/searx/templates/default/result_templates/images.html b/searx/templates/default/result_templates/images.html index e06dcc194..d85f841ae 100644 --- a/searx/templates/default/result_templates/images.html +++ b/searx/templates/default/result_templates/images.html @@ -1,6 +1,6 @@ <div class="image_result"> <p> - <a href="{{ result.img_src }}"><img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}" /></a> - <span class="url"><a href="{{ result.url }}" class="small_font">{{ _('original context') }}</a></span> + <a href="{{ result.img_src }}" rel="noreferrer"><img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}" /></a> + <span class="url"><a href="{{ result.url }}" rel="noreferrer" class="small_font">{{ _('original context') }}</a></span> </p> </div> diff --git a/searx/templates/default/result_templates/map.html b/searx/templates/default/result_templates/map.html index e2d00f920..d413742e7 100644 --- a/searx/templates/default/result_templates/map.html +++ b/searx/templates/default/result_templates/map.html @@ -5,8 +5,8 @@ {% endif %} <div> - <h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3> - <p class="url">{{ result.pretty_url }}‎ <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}">{{ _('cached') }}</a> + <h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> + <p class="url">{{ result.pretty_url }}‎ <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ _('cached') }}</a> {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %}</p> <p class="content">{% if result.img_src %}<img src="{{ image_proxify(result.img_src) }}" class="image" />{% endif %}{% if result.content %}{{ result.content|safe }}<br class="last"/>{% endif %}</p> </div> diff --git a/searx/templates/default/result_templates/torrent.html b/searx/templates/default/result_templates/torrent.html index b91babfc6..4b2522ad0 100644 --- a/searx/templates/default/result_templates/torrent.html +++ b/searx/templates/default/result_templates/torrent.html @@ -2,12 +2,12 @@ {% if "icon_"~result.engine~".ico" in favicons %} <img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" /> {% endif %} - <h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3> + <h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <p class="url">{{ result.pretty_url }}‎</p> {% if result.content %}<p class="content">{{ result.content|safe }}</p>{% endif %} <p> {% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %} - {% if result.torrentfile %}<a href="{{ result.torrentfile }}" class="torrentfile">{{ _('torrent file') }}</a>{% endif %} - + {% if result.torrentfile %}<a href="{{ result.torrentfile }}" rel="noreferrer" class="torrentfile">{{ _('torrent file') }}</a>{% endif %} - <span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span> </p> </div> diff --git a/searx/templates/default/result_templates/videos.html b/searx/templates/default/result_templates/videos.html index b85b4cf5a..5a377b70f 100644 --- a/searx/templates/default/result_templates/videos.html +++ b/searx/templates/default/result_templates/videos.html @@ -1,6 +1,6 @@ <div class="result"> - <h3 class="result_title">{% if "icon_"~result.engine~".ico" in favicons %}<img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />{% endif %}<a href="{{ result.url }}">{{ result.title|safe }}</a></h3> + <h3 class="result_title">{% if "icon_"~result.engine~".ico" in favicons %}<img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />{% endif %}<a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span><br />{% endif %} - <a href="{{ result.url }}"><img class="thumbnail" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> + <a href="{{ result.url }}" rel="noreferrer"><img class="thumbnail" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> <p class="url">{{ result.url }}‎</p> </div> diff --git a/searx/templates/oscar/base.html b/searx/templates/oscar/base.html index c185f8774..a799376f5 100644 --- a/searx/templates/oscar/base.html +++ b/searx/templates/oscar/base.html @@ -6,20 +6,24 @@ <meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="generator" content="searx/{{ searx_version }}"> + <meta name="referrer" content="no-referrer"> <meta name="viewport" content="width=device-width, initial-scale=1 , maximum-scale=1.0, user-scalable=1" /> {% block meta %}{% endblock %} <title>{% block title %}{% endblock %}searx</title> - + <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" type="text/css" /> - <link rel="stylesheet" href="{{ url_for('static', filename='css/oscar.min.css') }}" type="text/css" /> + <link rel="stylesheet" href="{{ url_for('static', filename='css/oscar.min.css') }}" type="text/css" /> <link rel="stylesheet" href="{{ url_for('static', filename='css/leaflet.min.css') }}" type="text/css" /> + {% for css in styles %} + <link rel="stylesheet" href="{{ url_for('static', filename=css) }}" type="text/css" /> + {% endfor %} <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="{{ url_for('static', filename='js/html5shiv.min.js') }}"></script> <script src="{{ url_for('static', filename='js/respond.min.js') }}"></script> <![endif]--> - + <link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.png') }}" /> {% block styles %} @@ -28,7 +32,7 @@ {% endblock %} <link title="searx" type="application/opensearchdescription+xml" rel="search" href="{{ url_for('opensearch') }}"/> - + <script type="text/javascript"> searx = {}; searx.method = "{{ method or 'POST' }}"; @@ -79,5 +83,8 @@ {% if autocomplete %}<script src="{{ url_for('static', filename='js/typeahead.bundle.min.js') }}"></script>{% endif %} <script src="{{ url_for('static', filename='js/require-2.1.15.min.js') }}"></script> <script src="{{ url_for('static', filename='js/searx.min.js') }}"></script> + {% for script in scripts %} + <script src="{{ url_for('static', filename=script) }}"></script> + {% endfor %} </body> </html> diff --git a/searx/templates/oscar/infobox.html b/searx/templates/oscar/infobox.html index 1aa2e5c11..2abdbf0ec 100644 --- a/searx/templates/oscar/infobox.html +++ b/searx/templates/oscar/infobox.html @@ -20,7 +20,7 @@ {% if infobox.urls %} <div class="infobox_part"> {% for url in infobox.urls %} - <p class="btn btn-default btn-xs"><a href="{{ url.url }}">{{ url.title }}</a></p> + <p class="btn btn-default btn-xs"><a href="{{ url.url }}" rel="noreferrer">{{ url.title }}</a></p> {% endfor %} </div> {% endif %} diff --git a/searx/templates/oscar/macros.html b/searx/templates/oscar/macros.html index 1ba1617a9..5866c132c 100644 --- a/searx/templates/oscar/macros.html +++ b/searx/templates/oscar/macros.html @@ -11,15 +11,15 @@ <!-- Draw result header -->
{% macro result_header(result, favicons) -%}
- <h4 class="result_header">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}<a href="{{ result.url }}">{{ result.title|safe }}</a></h4>
+ <h4 class="result_header">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}<a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h4>
{%- endmacro %}
<!-- Draw result sub header -->
{% macro result_sub_header(result) -%}
{% if result.publishedDate %}<time class="text-muted" datetime="{{ result.pubdate }}" >{{ result.publishedDate }}</time>{% endif %}
- <small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}">{{ icon('link') }} {{ _('cached') }}</a></small>
+ <small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ icon('link') }} {{ _('cached') }}</a></small>
{% if result.magnetlink %}<small> • <a href="{{ result.magnetlink }}" class="magnetlink">{{ icon('magnet') }} {{ _('magnet link') }}</a></small>{% endif %}
- {% if result.torrentfile %}<small> • <a href="{{ result.torrentfile }}" class="torrentfile">{{ icon('download-alt') }} {{ _('torrent file') }}</a></small>{% endif %}
+ {% if result.torrentfile %}<small> • <a href="{{ result.torrentfile }}" class="torrentfile" rel="noreferrer">{{ icon('download-alt') }} {{ _('torrent file') }}</a></small>{% endif %}
{%- endmacro %}
<!-- Draw result footer -->
@@ -59,3 +59,11 @@ </div>
{% endif %}
{%- endmacro %}
+
+{% macro checkbox_toggle(id, blocked) -%}
+ <div class="checkbox">
+ <input class="hidden" type="checkbox" id="{{ id }}" name="{{ id }}"{% if blocked %} checked="checked"{% endif %} />
+ <label class="btn btn-success label_hide_if_checked" for="{{ id }}">{{ _('Block') }}</label>
+ <label class="btn btn-danger label_hide_if_not_checked" for="{{ id }}">{{ _('Allow') }}</label>
+ </div>
+{%- endmacro %}
diff --git a/searx/templates/oscar/preferences.html b/searx/templates/oscar/preferences.html index 2425b9ea4..693167807 100644 --- a/searx/templates/oscar/preferences.html +++ b/searx/templates/oscar/preferences.html @@ -1,4 +1,4 @@ -{% from 'oscar/macros.html' import preferences_item_header, preferences_item_header_rtl, preferences_item_footer, preferences_item_footer_rtl %} +{% from 'oscar/macros.html' import preferences_item_header, preferences_item_header_rtl, preferences_item_footer, preferences_item_footer_rtl, checkbox_toggle %} {% extends "oscar/base.html" %} {% block title %}{{ _('preferences') }} - {% endblock %} {% block site_alert_warning_nojs %} @@ -16,6 +16,7 @@ <ul class="nav nav-tabs nav-justified hide_if_nojs" role="tablist" style="margin-bottom:20px;"> <li class="active"><a href="#tab_general" role="tab" data-toggle="tab">{{ _('General') }}</a></li> <li><a href="#tab_engine" role="tab" data-toggle="tab">{{ _('Engines') }}</a></li> + <li><a href="#tab_plugins" role="tab" data-toggle="tab">{{ _('Plugins') }}</a></li> <li><a href="#tab_cookies" role="tab" data-toggle="tab">{{ _('Cookies') }}</a></li> </ul> @@ -116,7 +117,7 @@ <!-- Nav tabs --> <ul class="nav nav-tabs nav-justified hide_if_nojs" role="tablist" style="margin-bottom:20px;"> - {% for (categ,search_engines) in categs %} + {% for categ in categories %} <li{% if loop.first %} class="active"{% endif %}><a href="#tab_engine_{{ categ|replace(' ', '_') }}" role="tab" data-toggle="tab">{{ _(categ) }}</a></li> {% endfor %} </ul> @@ -127,24 +128,20 @@ <!-- Tab panes --> <div class="tab-content"> - {% for (categ,search_engines) in categs %} + {% for categ in categories %} <noscript><label>{{ _(categ) }}</label> </noscript> <div class="tab-pane{% if loop.first %} active{% endif %} active_if_nojs" id="tab_engine_{{ categ|replace(' ', '_') }}"> <div class="container-fluid"> <fieldset> - {% for search_engine in search_engines %} + {% for search_engine in engines_by_category[categ] %} {% if not search_engine.private %} <div class="row"> {% if not rtl %} <div class="col-xs-6 col-sm-4 col-md-4">{{ search_engine.name }} ({{ shortcuts[search_engine.name] }})</div> {% endif %} <div class="col-xs-6 col-sm-4 col-md-4"> - <div class="checkbox"> - <input class="hidden" type="checkbox" id="engine_{{ categ|replace(' ', '_') }}_{{ search_engine.name|replace(' ', '_') }}" name="engine_{{ search_engine.name }}__{{ categ }}"{% if (search_engine.name, categ) in blocked_engines %} checked="checked"{% endif %} /> - <label class="btn btn-success label_hide_if_checked" for="engine_{{ categ|replace(' ', '_') }}_{{ search_engine.name|replace(' ', '_') }}">{{ _('Block') }}</label> - <label class="btn btn-danger label_hide_if_not_checked" for="engine_{{ categ|replace(' ', '_') }}_{{ search_engine.name|replace(' ', '_') }}">{{ _('Allow') }}</label> - </div> + {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in blocked_engines) }} </div> {% if rtl %} <div class="col-xs-6 col-sm-4 col-md-4">{{ search_engine.name }} ({{ shortcuts[search_engine.name] }})‎</div> @@ -158,6 +155,28 @@ {% endfor %} </div> </div> + <div class="tab-pane active_if_nojs" id="tab_plugins"> + <noscript> + <h3>{{ _('Plugins') }}</h3> + </noscript> + <fieldset> + <div class="container-fluid"> + {% for plugin in plugins %} + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">{{ plugin.name }}</h3> + </div> + <div class="panel-body"> + <div class="col-xs-6 col-sm-4 col-md-6">{{ plugin.description }}</div> + <div class="col-xs-6 col-sm-4 col-md-6"> + {{ checkbox_toggle('plugin_' + plugin.id, plugin.id not in allowed_plugins) }} + </div> + </div> + </div> + {% endfor %} + </div> + </fieldset> + </div> <div class="tab-pane active_if_nojs" id="tab_cookies"> <noscript> @@ -191,6 +210,7 @@ <input type="submit" class="btn btn-primary" value="{{ _('save') }}" /> <a href="{{ url_for('index') }}"><div class="btn btn-default">{{ _('back') }}</div></a> + <a href="{{ url_for('clear_cookies') }}"><div class="btn btn-default">{{ _('Reset defaults') }}</div></a> </form> </div> {% endblock %} diff --git a/searx/templates/oscar/result_templates/code.html b/searx/templates/oscar/result_templates/code.html index 6890c7851..582a2149c 100644 --- a/searx/templates/oscar/result_templates/code.html +++ b/searx/templates/oscar/result_templates/code.html @@ -5,7 +5,7 @@ {% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %}
-{% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository|safe }}">{{ result.repository }}</a></p>{% endif %}
+{% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository|safe }}" rel="noreferrer">{{ result.repository }}</a></p>{% endif %}
<div dir="ltr">
{{ result.codelines|code_highlighter(result.code_language)|safe }}
diff --git a/searx/templates/oscar/result_templates/images.html b/searx/templates/oscar/result_templates/images.html index 3f39ca6da..1bfff0a1a 100644 --- a/searx/templates/oscar/result_templates/images.html +++ b/searx/templates/oscar/result_templates/images.html @@ -1,6 +1,6 @@ {% from 'oscar/macros.html' import draw_favicon %}
-<a href="{{ result.img_src }}" data-toggle="modal" data-target="#modal-{{ index }}">
+<a href="{{ result.img_src }}" rel="noreferrer" data-toggle="modal" data-target="#modal-{{ index }}">
<img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" alt="{{ result.title|striptags }}" title="{{ result.title|striptags }}" class="img-thumbnail">
</a>
@@ -20,8 +20,8 @@ <span class="label label-default pull-right">{{ result.engine }}</span>
<p class="text-muted pull-left">{{ result.pretty_url }}</p>
<div class="clearfix"></div>
- <a href="{{ result.img_src }}" class="btn btn-default">{{ _('Get image') }}</a>
- <a href="{{ result.url }}" class="btn btn-default">{{ _('View source') }}</a>
+ <a href="{{ result.img_src }}" rel="noreferrer" class="btn btn-default">{{ _('Get image') }}</a>
+ <a href="{{ result.url }}" rel="noreferrer" class="btn btn-default">{{ _('View source') }}</a>
</div>
</div>
</div>
diff --git a/searx/templates/oscar/result_templates/videos.html b/searx/templates/oscar/result_templates/videos.html index ddf2894ac..af4841453 100644 --- a/searx/templates/oscar/result_templates/videos.html +++ b/searx/templates/oscar/result_templates/videos.html @@ -15,7 +15,7 @@ <div class="container-fluid">
<div class="row">
- <a href="{{ result.url }}"><img class="thumbnail col-xs-6 col-sm-4 col-md-4 result-content" src="{{ image_proxify(result.thumbnail) }}" alt="{{ result.title|striptags }} {{ result.engine }}" /></a>
+ <a href="{{ result.url }}" rel="noreferrer"><img class="thumbnail col-xs-6 col-sm-4 col-md-4 result-content" src="{{ image_proxify(result.thumbnail) }}" alt="{{ result.title|striptags }} {{ result.engine }}" /></a>
{% if result.content %}<p class="col-xs-12 col-sm-8 col-md-8 result-content">{{ result.content|safe }}</p>{% endif %}
</div>
</div>
diff --git a/searx/templates/oscar/results.html b/searx/templates/oscar/results.html index a75825611..155194546 100644 --- a/searx/templates/oscar/results.html +++ b/searx/templates/oscar/results.html @@ -25,8 +25,8 @@ {% endif %}
</div>
{% endfor %}
-
- {% if not results %}
+
+ {% if not results and not answers %}
{% include 'oscar/messages/no_results.html' %}
{% endif %}
@@ -82,7 +82,7 @@ {% for infobox in infoboxes %}
{% include 'oscar/infobox.html' %}
{% endfor %}
- {% endif %}
+ {% endif %}
{% if suggestions %}
<div class="panel panel-default">
@@ -111,7 +111,7 @@ <input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ base_url }}?q={{ q|urlencode }}&pageno={{ pageno }}{% if selected_categories %}&category_{{ selected_categories|join("&category_")|replace(' ','+') }}{% endif %}" readonly>
</div>
</form>
-
+
<label>{{ _('Download results') }}</label>
<div class="clearfix"></div>
{% for output_type in ('csv', 'json', 'rss') %}
@@ -122,7 +122,7 @@ <input type="hidden" name="pageno" value="{{ pageno }}">
<button type="submit" class="btn btn-default">{{ output_type }}</button>
</form>
- {% endfor %}
+ {% endfor %}
<div class="clearfix"></div>
</div>
</div>
diff --git a/searx/templates/pix-art/about.html b/searx/templates/pix-art/about.html new file mode 100644 index 000000000..cb4b351f8 --- /dev/null +++ b/searx/templates/pix-art/about.html @@ -0,0 +1,65 @@ +{% extends 'pix-art/base.html' %} +{% block content %} +<div class="row"{% if rtl %} dir="ltr"{% endif %}> + <h1>About <a href="{{ url_for('index') }}">searx</a></h1> + + <p>Searx is a <a href="https://en.wikipedia.org/wiki/Metasearch_engine">metasearch engine</a>, aggregating the results of other <a href="{{ url_for('preferences') }}">search engines</a> while not storing information about its users. + </p> + <h2>Why use Searx?</h2> + <ul> + <li>Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you</li> + <li>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</li> + <li>Searx is free software, the code is 100% open and you can help to make it better. See more on <a href="https://github.com/asciimoo/searx">github</a></li> + </ul> + <p>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</p> + +<h2>Technical details - How does it work?</h2> + +<p>Searx is a <a href="https://en.wikipedia.org/wiki/Metasearch_engine">metasearch engine</a>, +inspired by the <a href="http://seeks-project.info/">seeks project</a>.<br /> +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.<br /> +Searx can be added to your browser's search bar; moreover, it can be set as the default search engine. +</p> + +<h2>How can I make it my own?</h2> + +<p>Searx appreciates your concern regarding logs, so take the <a href="https://github.com/asciimoo/searx">code</a> and run it yourself! <br />Add your Searx to this <a href="https://github.com/asciimoo/searx/wiki/Searx-instances">list</a> to help other people reclaim their privacy and make the Internet freer! +<br />The more decentralized Internet is the more freedom we have!</p> + + +<h2>More about searx</h2> + +<ul> + <li><a href="https://github.com/asciimoo/searx">github</a></li> + <li><a href="https://www.ohloh.net/p/searx/">ohloh</a></li> + <li><a href="https://twitter.com/Searx_engine">twitter</a></li> + <li>IRC: #searx @ freenode (<a href="https://kiwiirc.com/client/irc.freenode.com/searx">webclient</a>)</li> + <li><a href="https://www.transifex.com/projects/p/searx/">transifex</a></li> +</ul> + + +<hr /> + +<h2 id="faq">FAQ</h2> + +<h3>How to add to firefox?</h3> +<p><a href="#" onclick="window.external.AddSearchProvider(window.location.protocol + '//' + window.location.host + '{{ url_for('opensearch') }}');">Install</a> searx as a search engine on any version of Firefox! (javascript required)</p> + +<h2 id="dev_faq">Developer FAQ</h2> + +<h3>New engines?</h3> +<ul> + <li>Edit your <a href="https://raw.github.com/asciimoo/searx/master/searx/settings.yml">settings.yml</a></li> + <li>Create your custom engine module, check the <a href="https://github.com/asciimoo/searx/blob/master/examples/basic_engine.py">example engine</a></li> +</ul> +<p>Don't forget to restart searx after config edit!</p> + +<h3>Installation/WSGI support?</h3> +<p>See the <a href="https://github.com/asciimoo/searx/wiki/Installation">installation and setup</a> wiki page</p> + +<h3>How to debug engines?</h3> +<p><a href="{{ url_for('stats') }}">Stats page</a> contains some useful data about the engines used.</p> + +</div> +{% endblock %} diff --git a/searx/templates/pix-art/base.html b/searx/templates/pix-art/base.html new file mode 100644 index 000000000..578180c84 --- /dev/null +++ b/searx/templates/pix-art/base.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}> + <head> + <meta charset="UTF-8" /> + <meta name="description" content="Searx - a privacy-respecting, hackable metasearch engine" /> + <meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> + <meta name="generator" content="searx/{{ searx_version }}"> + <meta name="referrer" content="no-referrer"> + <meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=1" /> + <title>{% block title %}{% endblock %}searx</title> + <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css" media="screen" /> + <link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.png') }}?v=2" /> + {% block styles %} + {% endblock %} + {% block meta %}{% endblock %} + {% block head %} + {% endblock %} + <script type="text/javascript"> + var favicons = [[]]; + </script> + </head> + <body> + <div id="container"> + {% block content %} + {% endblock %} + <script type="text/javascript"> + searx = {}; + </script> + <script src="{{ url_for('static', filename='js/searx.js') }}" ></script> + <script type="text/javascript"> + load_images(1); + </script> + </div> + </body> +</html> diff --git a/searx/templates/pix-art/index.html b/searx/templates/pix-art/index.html new file mode 100644 index 000000000..d398cc829 --- /dev/null +++ b/searx/templates/pix-art/index.html @@ -0,0 +1,12 @@ +{% extends "pix-art/base.html" %} +{% block content %} +<div class="center"> + <div class="title"><h1><img src="{{ url_for('static', filename='img/searx-pixel.png') }}" alt="Searx Logo"/></h1></div> + {% include 'pix-art/search.html' %} + <p class="top_margin"> + <a href="{{ url_for('about') }}" class="hmarg">{{ _('about') }}</a> + <a href="{{ url_for('preferences') }}" class="hmarg">{{ _('preferences') }}</a> + </p> +</div> +{% endblock %} + diff --git a/searx/templates/pix-art/preferences.html b/searx/templates/pix-art/preferences.html new file mode 100644 index 000000000..0caf31b46 --- /dev/null +++ b/searx/templates/pix-art/preferences.html @@ -0,0 +1,82 @@ +{% extends "default/base.html" %} +{% block head %} {% endblock %} +{% block content %} +<div class="row"> + <h2>{{ _('Preferences') }}</h2> + + <form method="post" action="{{ url_for('preferences') }}" id="search_form"> + <fieldset> + <legend>{{ _('Search language') }}</legend> + <p> + <select name='language'> + <option value="all" {% if current_language == 'all' %}selected="selected"{% endif %}>{{ _('Automatic') }}</option> + {% for lang_id,lang_name,country_name in language_codes | sort(attribute=1) %} + <option value="{{ lang_id }}" {% if lang_id == current_language %}selected="selected"{% endif %}>{{ lang_name }} ({{ country_name }}) - {{ lang_id }}</option> + {% endfor %} + </select> + </p> + </fieldset> + <fieldset> + <legend>{{ _('Interface language') }}</legend> + <p> + <select name='locale'> + {% for locale_id,locale_name in locales.items() | sort %} + <option value="{{ locale_id }}" {% if locale_id == current_locale %}selected="selected"{% endif %}>{{ locale_name }}</option> + {% endfor %} + </select> + </p> + </fieldset> + <fieldset> + <legend>{{ _('Method') }}</legend> + <p> + <select name='method'> + <option value="POST" {% if method == 'POST' %}selected="selected"{% endif %}>POST</option> + <option value="GET" {% if method == 'GET' %}selected="selected"{% endif %}>GET</option> + </select> + </p> + </fieldset> + <fieldset> + <legend>{{ _('Themes') }}</legend> + <p> + <select name="theme"> + {% for name in themes %} + <option value="{{ name }}" {% if name == theme %}selected="selected"{% endif %}>{{ name }}</option> + {% endfor %} + </select> + </p> + </fieldset> + <fieldset> + <legend>{{ _('Currently used search engines') }}</legend> + + <table> + <tr> + <th>{{ _('Engine name') }}</th> + <th>{{ _('Allow') }} / {{ _('Block') }}</th> + </tr> + {% for (categ,search_engines) in categs %} + {% for search_engine in search_engines %} + + {% if not search_engine.private %} + <tr> + <td>{{ search_engine.name }} ({{ shortcuts[search_engine.name] }})‎</td> + <td class="engine_checkbox"> + <input type="checkbox" id="engine_{{ categ|replace(' ', '_') }}_{{ search_engine.name|replace(' ', '_') }}" name="engine_{{ search_engine.name }}__{{ categ }}"{% if (search_engine.name, categ) in blocked_engines %} checked="checked"{% endif %} /> + <label class="allow" for="engine_{{ categ|replace(' ', '_') }}_{{ search_engine.name|replace(' ', '_') }}">{{ _('Allow') }}</label> + <label class="deny" for="engine_{{ categ|replace(' ', '_') }}_{{ search_engine.name|replace(' ', '_') }}">{{ _('Block') }}</label> + </td> + </tr> + {% endif %} + {% endfor %} + {% endfor %} + </table> + </fieldset> + <p class="small_font">{{ _('These settings are stored in your cookies, this allows us not to store this data about you.') }} + <br /> + {{ _("These cookies serve your sole convenience, we don't use these cookies to track you.") }} + </p> + + <input type="submit" value="{{ _('save') }}" /> + <div class="{% if rtl %}left{% else %}right{% endif %} preferences_back"><a href="{{ url_for('index') }}">{{ _('back') }}</a></div> + </form> +</div> +{% endblock %} diff --git a/searx/templates/pix-art/result_templates/default.html b/searx/templates/pix-art/result_templates/default.html new file mode 100644 index 000000000..ada81e5a3 --- /dev/null +++ b/searx/templates/pix-art/result_templates/default.html @@ -0,0 +1,7 @@ +<a href="{{ result.url }}" title="{{ result.title | striptags }}" rel="noreferrer"> + <canvas id="canvas-{{ pageno }}-{{ index }}" class="icon" width="16" height="16"></canvas> +</a> +<script type="text/javascript"> +favicons[{{ pageno }}][{{ index }}] = 'http://{{ result.url | extract_domain }}/favicon.ico'; +</script> + diff --git a/searx/templates/pix-art/result_templates/images.html b/searx/templates/pix-art/result_templates/images.html new file mode 100644 index 000000000..d85f841ae --- /dev/null +++ b/searx/templates/pix-art/result_templates/images.html @@ -0,0 +1,6 @@ +<div class="image_result"> + <p> + <a href="{{ result.img_src }}" rel="noreferrer"><img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}" /></a> + <span class="url"><a href="{{ result.url }}" rel="noreferrer" class="small_font">{{ _('original context') }}</a></span> + </p> +</div> diff --git a/searx/templates/pix-art/results.html b/searx/templates/pix-art/results.html new file mode 100644 index 000000000..9385b608a --- /dev/null +++ b/searx/templates/pix-art/results.html @@ -0,0 +1,32 @@ +{% if pageno > 1 %} + {% for result in results %} + {% set index = loop.index %} + {% include 'pix-art/result_templates/default.html' %} + {% endfor %} +{% else %} +{% extends "pix-art/base.html" %} +{% block title %}{{ q }} - {% endblock %} +{% block meta %}{% endblock %} +{% block content %} +<div id="logo"><a href="./"><img src="{{ url_for('static', filename='img/searx-pixel-small.png') }}" alt="Searx Logo"/></a></div> +<div class="preferences_container right"><a href="{{ url_for('preferences') }}" id="preferences"><span>preferences</span></a></div> +<div class="small search center"> + {% include 'pix-art/search.html' %} +</div> +<script type="text/javascript"> + favicons[{{ pageno }}] = []; +</script> +<div id="results"> + <span id="results_list"> + {% for result in results %} + {% set index = loop.index %} + {% include 'pix-art/result_templates/default.html' %} + {% endfor %} + </span> + <div id="pagination"> + <br /> + <input type="button" onclick="load_more('{{ q }}', {{ pageno+1 }})" id="load_more" value="{{ _('Load more...') }}" /> + </div> +</div> +{% endblock %} +{% endif %}
\ No newline at end of file diff --git a/searx/templates/pix-art/search.html b/searx/templates/pix-art/search.html new file mode 100644 index 000000000..4d129ec68 --- /dev/null +++ b/searx/templates/pix-art/search.html @@ -0,0 +1,9 @@ +<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form"> + <div id="search_wrapper"> + <input type="text" placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" size="100" {% if q %}value="{{ q }}"{% endif %}/> + <input type="submit" value="" id="search_submit" /> + {% for category in categories %} + <input type="hidden" name="category_{{ category }}" value="1"/> + {% endfor %} + </div> +</form> diff --git a/searx/templates/pix-art/stats.html b/searx/templates/pix-art/stats.html new file mode 100644 index 000000000..70fe98ac7 --- /dev/null +++ b/searx/templates/pix-art/stats.html @@ -0,0 +1,22 @@ +{% extends "default/base.html" %} +{% block head %} {% endblock %} +{% block content %} +<h2>{{ _('Engine stats') }}</h2> + +{% for stat_name,stat_category in stats %} +<div class="left"> + <table> + <tr colspan="3"> + <th>{{ stat_name }}</th> + </tr> + {% for engine in stat_category %} + <tr> + <td>{{ engine.name }}</td> + <td>{{ '%.02f'|format(engine.avg) }}</td> + <td class="percentage"><div style="width: {{ engine.percentage }}%"> </div></td> + </tr> + {% endfor %} + </table> +</div> +{% endfor %} +{% endblock %} diff --git a/searx/tests/engines/test_blekko_images.py b/searx/tests/engines/test_blekko_images.py index 793fadbad..beb0853e3 100644 --- a/searx/tests/engines/test_blekko_images.py +++ b/searx/tests/engines/test_blekko_images.py @@ -12,9 +12,14 @@ class TestBlekkoImagesEngine(SearxTestCase): dicto['pageno'] = 0 dicto['safesearch'] = 1 params = blekko_images.request(query, dicto) - self.assertTrue('url' in params) - self.assertTrue(query in params['url']) - self.assertTrue('blekko.com' in params['url']) + self.assertIn('url', params) + self.assertIn(query, params['url']) + self.assertIn('blekko.com', params['url']) + self.assertIn('page', params['url']) + + dicto['pageno'] = 1 + params = blekko_images.request(query, dicto) + self.assertNotIn('page', params['url']) def test_response(self): self.assertRaises(AttributeError, blekko_images.response, None) diff --git a/searx/tests/engines/test_deezer.py b/searx/tests/engines/test_deezer.py index c8c2c90f2..ad09d2a2c 100644 --- a/searx/tests/engines/test_deezer.py +++ b/searx/tests/engines/test_deezer.py @@ -30,9 +30,9 @@ class TestDeezerEngine(SearxTestCase): json = """ {"data":[ {"id":100, "title":"Title of track", - "link":"http:\/\/www.deezer.com\/track\/1094042","duration":232, + "link":"https:\/\/www.deezer.com\/track\/1094042","duration":232, "artist":{"id":200,"name":"Artist Name", - "link":"http:\/\/www.deezer.com\/artist\/1217","type":"artist"}, + "link":"https:\/\/www.deezer.com\/artist\/1217","type":"artist"}, "album":{"id":118106,"title":"Album Title","type":"album"},"type":"track"} ]} """ @@ -41,14 +41,14 @@ class TestDeezerEngine(SearxTestCase): self.assertEqual(type(results), list) self.assertEqual(len(results), 1) self.assertEqual(results[0]['title'], 'Title of track') - self.assertEqual(results[0]['url'], 'http://www.deezer.com/track/1094042') + self.assertEqual(results[0]['url'], 'https://www.deezer.com/track/1094042') self.assertEqual(results[0]['content'], 'Artist Name • Album Title • Title of track') self.assertTrue('100' in results[0]['embedded']) json = """ {"data":[ {"id":200,"name":"Artist Name", - "link":"http:\/\/www.deezer.com\/artist\/1217","type":"artist"} + "link":"https:\/\/www.deezer.com\/artist\/1217","type":"artist"} ]} """ response = mock.Mock(text=json) diff --git a/searx/tests/engines/test_google_images.py b/searx/tests/engines/test_google_images.py index 6870ff52f..32d133334 100644 --- a/searx/tests/engines/test_google_images.py +++ b/searx/tests/engines/test_google_images.py @@ -11,9 +11,14 @@ class TestGoogleImagesEngine(SearxTestCase): dicto = defaultdict(dict) dicto['pageno'] = 1 params = google_images.request(query, dicto) - self.assertTrue('url' in params) - self.assertTrue(query in params['url']) - self.assertTrue('googleapis.com' in params['url']) + self.assertIn('url', params) + self.assertIn(query, params['url']) + self.assertIn('googleapis.com', params['url']) + self.assertIn('safe=on', params['url']) + + dicto['safesearch'] = 0 + params = google_images.request(query, dicto) + self.assertIn('safe=off', params['url']) def test_response(self): self.assertRaises(AttributeError, google_images.response, None) diff --git a/searx/tests/engines/test_kickass.py b/searx/tests/engines/test_kickass.py index 971d0847e..4cfcaa63c 100644 --- a/searx/tests/engines/test_kickass.py +++ b/searx/tests/engines/test_kickass.py @@ -15,7 +15,6 @@ class TestKickassEngine(SearxTestCase): self.assertIn('url', params) self.assertIn(query, params['url']) self.assertIn('kickass.to', params['url']) - self.assertIn('verify', params) self.assertFalse(params['verify']) def test_response(self): diff --git a/searx/tests/engines/test_piratebay.py b/searx/tests/engines/test_piratebay.py index 17bc3a526..5699380be 100644 --- a/searx/tests/engines/test_piratebay.py +++ b/searx/tests/engines/test_piratebay.py @@ -65,12 +65,39 @@ class TestPiratebayEngine(SearxTestCase): <td align="right">13</td> <td align="right">334</td> </tr> + <tr> + <td class="vertTh"> + <center> + <a href="#" title="More from this category">Anime</a><br/> + (<a href="#" title="More from this category">Anime</a>) + </center> + </td> + <td> + <div class="detName"> + <a href="/this.is.the.link" class="detLink" title="Title"> + This is the title + </a> + </div> + <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet"> + <img src="/static/img/icon-magnet.gif" alt="Magnet link"/> + </a> + <a href="/user/HorribleSubs"> + <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/> + </a> + <img src="/static/img/11x11p.png"/> + <font class="detDesc"> + This is the content <span>and should be</span> OK + </font> + </td> + <td align="right">13</td> + <td align="right">334</td> + </tr> </table> """ response = mock.Mock(text=html) results = piratebay.response(response) self.assertEqual(type(results), list) - self.assertEqual(len(results), 1) + self.assertEqual(len(results), 2) self.assertEqual(results[0]['title'], 'This is the title') self.assertEqual(results[0]['url'], 'https://thepiratebay.se/this.is.the.link') self.assertEqual(results[0]['content'], 'This is the content and should be OK') @@ -79,6 +106,8 @@ class TestPiratebayEngine(SearxTestCase): self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETLINK') self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/TORRENTFILE.torrent') + self.assertEqual(results[1]['torrentfile'], None) + html = """ <table id="searchResult"> <tr> diff --git a/searx/tests/engines/test_spotify.py b/searx/tests/engines/test_spotify.py new file mode 100644 index 000000000..fd274abbd --- /dev/null +++ b/searx/tests/engines/test_spotify.py @@ -0,0 +1,124 @@ +from collections import defaultdict +import mock +from searx.engines import spotify +from searx.testing import SearxTestCase + + +class TestSpotifyEngine(SearxTestCase): + + def test_request(self): + query = 'test_query' + dicto = defaultdict(dict) + dicto['pageno'] = 0 + params = spotify.request(query, dicto) + self.assertIn('url', params) + self.assertIn(query, params['url']) + self.assertIn('spotify.com', params['url']) + + def test_response(self): + self.assertRaises(AttributeError, spotify.response, None) + self.assertRaises(AttributeError, spotify.response, []) + self.assertRaises(AttributeError, spotify.response, '') + self.assertRaises(AttributeError, spotify.response, '[]') + + response = mock.Mock(text='{}') + self.assertEqual(spotify.response(response), []) + + response = mock.Mock(text='{"data": []}') + self.assertEqual(spotify.response(response), []) + + json = """ + { + "tracks": { + "href": "https://api.spotify.com/v1/search?query=nosfell&offset=0&limit=20&type=track", + "items": [ + { + "album": { + "album_type": "album", + "external_urls": { + "spotify": "https://open.spotify.com/album/5c9ap1PBkSGLxT3J73toxA" + }, + "href": "https://api.spotify.com/v1/albums/5c9ap1PBkSGLxT3J73toxA", + "id": "5c9ap1PBkSGLxT3J73toxA", + "name": "Album Title", + "type": "album", + "uri": "spotify:album:5c9ap1PBkSGLxT3J73toxA" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/0bMc6b75FfZEpQHG1jifKu" + }, + "href": "https://api.spotify.com/v1/artists/0bMc6b75FfZEpQHG1jifKu", + "id": "0bMc6b75FfZEpQHG1jifKu", + "name": "Artist Name", + "type": "artist", + "uri": "spotify:artist:0bMc6b75FfZEpQHG1jifKu" + } + ], + "disc_number": 1, + "duration_ms": 202386, + "explicit": false, + "external_ids": { + "isrc": "FRV640600067" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/2GzvFiedqW8hgqUpWcASZa" + }, + "href": "https://api.spotify.com/v1/tracks/2GzvFiedqW8hgqUpWcASZa", + "id": "1000", + "is_playable": true, + "name": "Title of track", + "popularity": 6, + "preview_url": "https://p.scdn.co/mp3-preview/7b8ecda580965a066b768c2647f877e43f7b1a0a", + "track_number": 3, + "type": "track", + "uri": "spotify:track:2GzvFiedqW8hgqUpWcASZa" + } + ], + "limit": 20, + "next": "https://api.spotify.com/v1/search?query=nosfell&offset=20&limit=20&type=track", + "offset": 0, + "previous": null, + "total": 107 + } + } + """ + response = mock.Mock(text=json) + results = spotify.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['title'], 'Title of track') + self.assertEqual(results[0]['url'], 'https://open.spotify.com/track/2GzvFiedqW8hgqUpWcASZa') + self.assertEqual(results[0]['content'], 'Artist Name • Album Title • Title of track') + self.assertIn('1000', results[0]['embedded']) + + json = """ + { + "tracks": { + "href": "https://api.spotify.com/v1/search?query=nosfell&offset=0&limit=20&type=track", + "items": [ + { + "href": "https://api.spotify.com/v1/tracks/2GzvFiedqW8hgqUpWcASZa", + "id": "1000", + "is_playable": true, + "name": "Title of track", + "popularity": 6, + "preview_url": "https://p.scdn.co/mp3-preview/7b8ecda580965a066b768c2647f877e43f7b1a0a", + "track_number": 3, + "type": "album", + "uri": "spotify:track:2GzvFiedqW8hgqUpWcASZa" + } + ], + "limit": 20, + "next": "https://api.spotify.com/v1/search?query=nosfell&offset=20&limit=20&type=track", + "offset": 0, + "previous": null, + "total": 107 + } + } + """ + response = mock.Mock(text=json) + results = spotify.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 0) diff --git a/searx/tests/engines/test_yahoo.py b/searx/tests/engines/test_yahoo.py index e5c78701d..11ef9db22 100644 --- a/searx/tests/engines/test_yahoo.py +++ b/searx/tests/engines/test_yahoo.py @@ -55,37 +55,44 @@ class TestYahooEngine(SearxTestCase): self.assertEqual(yahoo.response(response), []) html = """ - <div class="res"> - <div> - <h3> - <a id="link-1" class="yschttl spt" href="http://r.search.yahoo.com/_ylt=A0LEVzClb9JUSKcAEGRXNyoA; - _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2JmMQR2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10 - /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"target="_blank" data-bk="5063.1"> - <b>This</b> is the title - </a> +<ol class="reg mb-15 searchCenterMiddle"> + <li class="first"> + <div class="dd algo fst Sr"> + <div class="compTitle"> + <h3 class="title"><a class=" td-u" href="http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA; + _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10 + /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-" + target="_blank" data-bid="54e712e13671c"> + <b><b>This is the title</b></b></a> </h3> </div> - <span class="url" dir="ltr">www.<b>test</b>.com</span> - <div class="abstr"> - <b>This</b> is the content + <div class="compText aAbs"> + <p class="lh-18"><b><b>This is the </b>content</b> + </p> </div> </div> - <div id="satat" data-bns="Yahoo" data-bk="124.1"> - <h2>Also Try</h2> - <table> - <tbody> - <tr> - <td> - <a id="srpnat0" class="" href="https://search.yahoo.com/search=rs-bottom" > - <span> - <b></b>This is <b>the suggestion</b> - </span> - </a> - </td> - </tr> - </tbody> - </table> + </li> + <li> + <div class="dd algo lst Sr"> + <div class="compTitle"> + </div> + <div class="compText aAbs"> + <p class="lh-18">This is the second content</p> + </div> </div> + </li> +</ol> +<div class="dd assist fst lst AlsoTry" data-bid="54e712e138d04"> + <div class="compTitle mb-4 h-17"> + <h3 class="title">Also Try</h3> </div> + <table class="compTable m-0 ac-1st td-u fz-ms"> + <tbody> + <tr> + <td class="w-50p pr-28"><a href="https://search.yahoo.com/"><B>This is the </B>suggestion<B></B></a> + </td> + </tr> + </table> +</div> """ response = mock.Mock(text=html) results = yahoo.response(response) @@ -97,44 +104,24 @@ class TestYahooEngine(SearxTestCase): self.assertEqual(results[1]['suggestion'], 'This is the suggestion') html = """ - <div class="res"> - <div> - <h3> - <a id="link-1" class="yschttl spt" href="http://r.search.yahoo.com/_ylt=A0LEVzClb9JUSKcAEGRXNyoA; - _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2JmMQR2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10 - /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"target="_blank" data-bk="5063.1"> - <b>This</b> is the title - </a> - </h3> - </div> - <span class="url" dir="ltr">www.<b>test</b>.com</span> - <div class="abstr"> - <b>This</b> is the content - </div> - </div> - <div class="res"> - <div> - <h3> - <a id="link-1" class="yschttl spt"> - <b>This</b> is the title - </a> - </h3> - </div> - <span class="url" dir="ltr">www.<b>test</b>.com</span> - <div class="abstr"> - <b>This</b> is the content - </div> - </div> - <div class="res"> - <div> - <h3> +<ol class="reg mb-15 searchCenterMiddle"> + <li class="first"> + <div class="dd algo fst Sr"> + <div class="compTitle"> + <h3 class="title"><a class=" td-u" href="http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA; + _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10 + /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-" + target="_blank" data-bid="54e712e13671c"> + <b><b>This is the title</b></b></a> </h3> </div> - <span class="url" dir="ltr">www.<b>test</b>.com</span> - <div class="abstr"> - <b>This</b> is the content + <div class="compText aAbs"> + <p class="lh-18"><b><b>This is the </b>content</b> + </p> </div> </div> + </li> +</ol> """ response = mock.Mock(text=html) results = yahoo.response(response) diff --git a/searx/tests/test_engines.py b/searx/tests/test_engines.py index 9b1c12cb1..5770458f3 100644 --- a/searx/tests/test_engines.py +++ b/searx/tests/test_engines.py @@ -28,6 +28,7 @@ from searx.tests.engines.test_piratebay import * # noqa from searx.tests.engines.test_searchcode_code import * # noqa from searx.tests.engines.test_searchcode_doc import * # noqa from searx.tests.engines.test_soundcloud import * # noqa +from searx.tests.engines.test_spotify import * # noqa from searx.tests.engines.test_stackoverflow import * # noqa from searx.tests.engines.test_startpage import * # noqa from searx.tests.engines.test_subtitleseeker import * # noqa diff --git a/searx/tests/test_plugins.py b/searx/tests/test_plugins.py new file mode 100644 index 000000000..8dcad1142 --- /dev/null +++ b/searx/tests/test_plugins.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +from searx.testing import SearxTestCase +from searx import plugins +from mock import Mock + + +class PluginStoreTest(SearxTestCase): + + def test_PluginStore_init(self): + store = plugins.PluginStore() + self.assertTrue(isinstance(store.plugins, list) and len(store.plugins) == 0) + + def test_PluginStore_register(self): + store = plugins.PluginStore() + testplugin = plugins.Plugin() + store.register(testplugin) + + self.assertTrue(len(store.plugins) == 1) + + def test_PluginStore_call(self): + store = plugins.PluginStore() + testplugin = plugins.Plugin() + store.register(testplugin) + setattr(testplugin, 'asdf', Mock()) + request = Mock(user_plugins=[]) + store.call('asdf', request, Mock()) + + self.assertFalse(testplugin.asdf.called) + + request.user_plugins.append(testplugin) + store.call('asdf', request, Mock()) + + self.assertTrue(testplugin.asdf.called) + + +class SelfIPTest(SearxTestCase): + + def test_PluginStore_init(self): + store = plugins.PluginStore() + store.register(plugins.self_ip) + + self.assertTrue(len(store.plugins) == 1) + + request = Mock(user_plugins=store.plugins, + remote_addr='127.0.0.1') + request.headers.getlist.return_value = [] + ctx = {'search': Mock(answers=set(), + query='ip')} + store.call('post_search', request, ctx) + self.assertTrue('127.0.0.1' in ctx['search'].answers) diff --git a/searx/tests/test_webapp.py b/searx/tests/test_webapp.py index 8bbe5d056..471ec2f2d 100644 --- a/searx/tests/test_webapp.py +++ b/searx/tests/test_webapp.py @@ -2,7 +2,6 @@ import json from urlparse import ParseResult -from mock import patch from searx import webapp from searx.testing import SearxTestCase @@ -33,6 +32,11 @@ class ViewsTestCase(SearxTestCase): }, ] + def search_mock(search_self, *args): + search_self.results = self.test_results + + webapp.Search.search = search_mock + self.maxDiff = None # to see full diffs def test_index_empty(self): @@ -40,17 +44,10 @@ class ViewsTestCase(SearxTestCase): self.assertEqual(result.status_code, 200) self.assertIn('<div class="title"><h1>searx</h1></div>', result.data) - @patch('searx.search.Search.search') - def test_index_html(self, search): - search.return_value = ( - self.test_results, - set(), - set(), - set() - ) + def test_index_html(self): result = self.app.post('/', data={'q': 'test'}) self.assertIn( - '<h3 class="result_title"><img width="14" height="14" class="favicon" src="/static/themes/default/img/icons/icon_youtube.ico" alt="youtube" /><a href="http://second.test.xyz">Second <span class="highlight">Test</span></a></h3>', # noqa + '<h3 class="result_title"><img width="14" height="14" class="favicon" src="/static/themes/default/img/icons/icon_youtube.ico" alt="youtube" /><a href="http://second.test.xyz" rel="noreferrer">Second <span class="highlight">Test</span></a></h3>', # noqa result.data ) self.assertIn( @@ -58,14 +55,7 @@ class ViewsTestCase(SearxTestCase): result.data ) - @patch('searx.search.Search.search') - def test_index_json(self, search): - search.return_value = ( - self.test_results, - set(), - set(), - set() - ) + def test_index_json(self): result = self.app.post('/', data={'q': 'test', 'format': 'json'}) result_dict = json.loads(result.data) @@ -76,14 +66,7 @@ class ViewsTestCase(SearxTestCase): self.assertEqual( result_dict['results'][0]['url'], 'http://first.test.xyz') - @patch('searx.search.Search.search') - def test_index_csv(self, search): - search.return_value = ( - self.test_results, - set(), - set(), - set() - ) + def test_index_csv(self): result = self.app.post('/', data={'q': 'test', 'format': 'csv'}) self.assertEqual( @@ -93,14 +76,7 @@ class ViewsTestCase(SearxTestCase): result.data ) - @patch('searx.search.Search.search') - def test_index_rss(self, search): - search.return_value = ( - self.test_results, - set(), - set(), - set() - ) + def test_index_rss(self): result = self.app.post('/', data={'q': 'test', 'format': 'rss'}) self.assertIn( diff --git a/searx/webapp.py b/searx/webapp.py index 13c965e0d..3ef5a72c8 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -26,9 +26,23 @@ import json import cStringIO import os import hashlib +import requests + +from searx import logger +logger = logger.getChild('webapp') + +try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import HtmlFormatter +except: + logger.critical("cannot import dependency: pygments") + from sys import exit + exit(1) from datetime import datetime, timedelta from urllib import urlencode +from urlparse import urlparse from werkzeug.contrib.fixers import ProxyFix from flask import ( Flask, request, render_template, url_for, Response, make_response, @@ -36,7 +50,6 @@ from flask import ( ) from flask.ext.babel import Babel, gettext, format_date from searx import settings, searx_dir -from searx.poolrequests import get as http_get from searx.engines import ( categories, engines, get_engines_stats, engine_shortcuts ) @@ -47,22 +60,21 @@ from searx.utils import ( ) from searx.version import VERSION_STRING from searx.languages import language_codes -from searx.https_rewrite import https_url_rewrite from searx.search import Search from searx.query import Query from searx.autocomplete import searx_bang, backends as autocomplete_backends -from searx import logger -try: - from pygments import highlight - from pygments.lexers import get_lexer_by_name - from pygments.formatters import HtmlFormatter -except: - logger.critical("cannot import dependency: pygments") - from sys import exit - exit(1) +from searx.plugins import plugins +# check if the pyopenssl, ndg-httpsclient, pyasn1 packages are installed. +# They are needed for SSL connection without trouble, see #298 +try: + import OpenSSL.SSL # NOQA + import ndg.httpsclient # NOQA + import pyasn1 # NOQA +except ImportError: + logger.critical("The pyopenssl, ndg-httpsclient, pyasn1 packages have to be installed.\n" + "Some HTTPS connections will failed") -logger = logger.getChild('webapp') static_path, templates_path, themes =\ get_themes(settings['themes_path'] @@ -109,6 +121,8 @@ _category_names = (gettext('files'), gettext('news'), gettext('map')) +outgoing_proxies = settings.get('outgoing_proxies', None) + @babel.localeselector def get_locale(): @@ -180,6 +194,12 @@ def code_highlighter(codelines, language=None): return html_code +# Extract domain from url +@app.template_filter('extract_domain') +def extract_domain(url): + return urlparse(url)[1] + + def get_base_url(): if settings['server']['base_url']: hostname = settings['server']['base_url'] @@ -299,10 +319,37 @@ def render(template_name, override_theme=None, **kwargs): kwargs['cookies'] = request.cookies + kwargs['scripts'] = set() + for plugin in request.user_plugins: + for script in plugin.js_dependencies: + kwargs['scripts'].add(script) + + kwargs['styles'] = set() + for plugin in request.user_plugins: + for css in plugin.css_dependencies: + kwargs['styles'].add(css) + return render_template( '{}/{}'.format(kwargs['theme'], template_name), **kwargs) +@app.before_request +def pre_request(): + # merge GET, POST vars + request.form = dict(request.form.items()) + for k, v in request.args.items(): + if k not in request.form: + request.form[k] = v + + request.user_plugins = [] + allowed_plugins = request.cookies.get('allowed_plugins', '').split(',') + disabled_plugins = request.cookies.get('disabled_plugins', '').split(',') + for plugin in plugins: + if ((plugin.default_on and plugin.id not in disabled_plugins) + or plugin.id in allowed_plugins): + request.user_plugins.append(plugin) + + @app.route('/search', methods=['GET', 'POST']) @app.route('/', methods=['GET', 'POST']) def index(): @@ -323,20 +370,17 @@ def index(): 'index.html', ) - search.results, search.suggestions,\ - search.answers, search.infoboxes = search.search(request) + if plugins.call('pre_search', request, locals()): + search.search(request) + + plugins.call('post_search', request, locals()) for result in search.results: + plugins.call('on_result', request, locals()) if not search.paging and engines[result['engine']].paging: search.paging = True - # check if HTTPS rewrite is required - if settings['server']['https_rewrite']\ - and result['parsed_url'].scheme == 'http': - - result = https_url_rewrite(result) - if search.request_data.get('format', 'html') == 'html': if 'content' in result: result['content'] = highlight_content(result['content'], @@ -344,11 +388,10 @@ def index(): result['title'] = highlight_content(result['title'], search.query.encode('utf-8')) else: - if 'content' in result: + if result.get('content'): result['content'] = html_to_text(result['content']).strip() # removing html content and whitespace duplications - result['title'] = ' '.join(html_to_text(result['title']) - .strip().split()) + result['title'] = ' '.join(html_to_text(result['title']).strip().split()) result['pretty_url'] = prettify_url(result['url']) @@ -487,11 +530,11 @@ def preferences(): blocked_engines = get_blocked_engines(engines, request.cookies) else: # on save selected_categories = [] + post_disabled_plugins = [] locale = None autocomplete = '' method = 'POST' safesearch = '1' - for pd_name, pd in request.form.items(): if pd_name.startswith('category_'): category = pd_name[9:] @@ -514,14 +557,34 @@ def preferences(): safesearch = pd elif pd_name.startswith('engine_'): if pd_name.find('__') > -1: - engine_name, category = pd_name.replace('engine_', '', 1).split('__', 1) + # TODO fix underscore vs space + engine_name, category = [x.replace('_', ' ') for x in + pd_name.replace('engine_', '', 1).split('__', 1)] if engine_name in engines and category in engines[engine_name].categories: blocked_engines.append((engine_name, category)) elif pd_name == 'theme': theme = pd if pd in themes else default_theme + elif pd_name.startswith('plugin_'): + plugin_id = pd_name.replace('plugin_', '', 1) + if not any(plugin.id == plugin_id for plugin in plugins): + continue + post_disabled_plugins.append(plugin_id) else: resp.set_cookie(pd_name, pd, max_age=cookie_max_age) + disabled_plugins = [] + allowed_plugins = [] + for plugin in plugins: + if plugin.default_on: + if plugin.id in post_disabled_plugins: + disabled_plugins.append(plugin.id) + elif plugin.id not in post_disabled_plugins: + allowed_plugins.append(plugin.id) + + resp.set_cookie('disabled_plugins', ','.join(disabled_plugins), max_age=cookie_max_age) + + resp.set_cookie('allowed_plugins', ','.join(allowed_plugins), max_age=cookie_max_age) + resp.set_cookie( 'blocked_engines', ','.join('__'.join(e) for e in blocked_engines), max_age=cookie_max_age @@ -566,11 +629,13 @@ def preferences(): current_language=lang or 'all', image_proxy=image_proxy, language_codes=language_codes, - categs=categories.items(), + engines_by_category=categories, blocked_engines=blocked_engines, autocomplete_backends=autocomplete_backends, shortcuts={y: x for x, y in engine_shortcuts.items()}, themes=themes, + plugins=plugins, + allowed_plugins=[plugin.id for plugin in request.user_plugins], theme=get_current_theme_name()) @@ -589,10 +654,11 @@ def image_proxy(): headers = dict_subset(request.headers, {'If-Modified-Since', 'If-None-Match'}) headers['User-Agent'] = gen_useragent() - resp = http_get(url, - stream=True, - timeout=settings['server'].get('request_timeout', 2), - headers=headers) + resp = requests.get(url, + stream=True, + timeout=settings['server'].get('request_timeout', 2), + headers=headers, + proxies=outgoing_proxies) if resp.status_code == 304: return '', resp.status_code @@ -644,6 +710,10 @@ Disallow: /preferences @app.route('/opensearch.xml', methods=['GET']) def opensearch(): method = 'post' + + if request.cookies.get('method', 'POST') == 'GET': + method = 'get' + # chrome/chromium only supports HTTP GET.... if request.headers.get('User-Agent', '').lower().find('webkit') >= 0: method = 'get' @@ -668,6 +738,14 @@ def favicon(): mimetype='image/vnd.microsoft.icon') +@app.route('/clear_cookies') +def clear_cookies(): + resp = make_response(redirect(url_for('index'))) + for cookie_name in request.cookies: + resp.delete_cookie(cookie_name) + return resp + + def run(): app.run( debug=settings['server']['debug'], |