summaryrefslogtreecommitdiff
path: root/searx
diff options
context:
space:
mode:
Diffstat (limited to 'searx')
-rw-r--r--searx/__init__.py11
-rw-r--r--searx/autocomplete.py10
-rw-r--r--searx/engines/deezer.py8
-rw-r--r--searx/engines/kickass.py4
-rw-r--r--searx/engines/photon.py3
-rw-r--r--searx/engines/piratebay.py14
-rw-r--r--searx/engines/spotify.py60
-rw-r--r--searx/engines/yahoo.py11
-rw-r--r--searx/plugins/__init__.py75
-rw-r--r--searx/plugins/https_rewrite.py (renamed from searx/https_rewrite.py)29
-rw-r--r--searx/plugins/https_rules/00README (renamed from searx/https_rules/00README)0
-rw-r--r--searx/plugins/https_rules/Bing.xml (renamed from searx/https_rules/Bing.xml)0
-rw-r--r--searx/plugins/https_rules/Dailymotion.xml (renamed from searx/https_rules/Dailymotion.xml)0
-rw-r--r--searx/plugins/https_rules/Deviantart.xml (renamed from searx/https_rules/Deviantart.xml)0
-rw-r--r--searx/plugins/https_rules/DuckDuckGo.xml (renamed from searx/https_rules/DuckDuckGo.xml)0
-rw-r--r--searx/plugins/https_rules/Flickr.xml (renamed from searx/https_rules/Flickr.xml)0
-rw-r--r--searx/plugins/https_rules/Github-Pages.xml (renamed from searx/https_rules/Github-Pages.xml)0
-rw-r--r--searx/plugins/https_rules/Github.xml (renamed from searx/https_rules/Github.xml)0
-rw-r--r--searx/plugins/https_rules/Google-mismatches.xml (renamed from searx/https_rules/Google-mismatches.xml)0
-rw-r--r--searx/plugins/https_rules/Google.org.xml (renamed from searx/https_rules/Google.org.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleAPIs.xml (renamed from searx/https_rules/GoogleAPIs.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleCanada.xml (renamed from searx/https_rules/GoogleCanada.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleImages.xml (renamed from searx/https_rules/GoogleImages.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleMainSearch.xml (renamed from searx/https_rules/GoogleMainSearch.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleMaps.xml (renamed from searx/https_rules/GoogleMaps.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleMelange.xml (renamed from searx/https_rules/GoogleMelange.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleSearch.xml (renamed from searx/https_rules/GoogleSearch.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleServices.xml (renamed from searx/https_rules/GoogleServices.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleShopping.xml (renamed from searx/https_rules/GoogleShopping.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleSorry.xml (renamed from searx/https_rules/GoogleSorry.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleTranslate.xml (renamed from searx/https_rules/GoogleTranslate.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleVideos.xml (renamed from searx/https_rules/GoogleVideos.xml)0
-rw-r--r--searx/plugins/https_rules/GoogleWatchBlog.xml (renamed from searx/https_rules/GoogleWatchBlog.xml)0
-rw-r--r--searx/plugins/https_rules/Google_App_Engine.xml (renamed from searx/https_rules/Google_App_Engine.xml)0
-rw-r--r--searx/plugins/https_rules/Googleplex.com.xml (renamed from searx/https_rules/Googleplex.com.xml)0
-rw-r--r--searx/plugins/https_rules/OpenStreetMap.xml (renamed from searx/https_rules/OpenStreetMap.xml)0
-rw-r--r--searx/plugins/https_rules/Rawgithub.com.xml (renamed from searx/https_rules/Rawgithub.com.xml)0
-rw-r--r--searx/plugins/https_rules/Soundcloud.xml (renamed from searx/https_rules/Soundcloud.xml)0
-rw-r--r--searx/plugins/https_rules/ThePirateBay.xml (renamed from searx/https_rules/ThePirateBay.xml)0
-rw-r--r--searx/plugins/https_rules/Torproject.xml (renamed from searx/https_rules/Torproject.xml)0
-rw-r--r--searx/plugins/https_rules/Twitter.xml (renamed from searx/https_rules/Twitter.xml)0
-rw-r--r--searx/plugins/https_rules/Vimeo.xml (renamed from searx/https_rules/Vimeo.xml)0
-rw-r--r--searx/plugins/https_rules/WikiLeaks.xml (renamed from searx/https_rules/WikiLeaks.xml)0
-rw-r--r--searx/plugins/https_rules/Wikimedia.xml (renamed from searx/https_rules/Wikimedia.xml)0
-rw-r--r--searx/plugins/https_rules/Yahoo.xml (renamed from searx/https_rules/Yahoo.xml)0
-rw-r--r--searx/plugins/https_rules/YouTube.xml (renamed from searx/https_rules/YouTube.xml)0
-rw-r--r--searx/plugins/search_on_category_select.py22
-rw-r--r--searx/plugins/self_ip.py35
-rw-r--r--searx/poolrequests.py57
-rw-r--r--searx/search.py27
-rw-r--r--searx/settings.yml45
-rw-r--r--searx/static/js/search_on_category_select.js14
-rw-r--r--searx/static/themes/pix-art/css/style.css1
-rw-r--r--searx/static/themes/pix-art/img/favicon.pngbin0 -> 3064 bytes
-rw-r--r--searx/static/themes/pix-art/img/preference-icon-pixel.pngbin0 -> 242 bytes
-rw-r--r--searx/static/themes/pix-art/img/search-icon-pixel.pngbin0 -> 204 bytes
-rw-r--r--searx/static/themes/pix-art/img/searx-pixel-small.pngbin0 -> 238 bytes
-rw-r--r--searx/static/themes/pix-art/img/searx-pixel.pngbin0 -> 439 bytes
-rw-r--r--searx/static/themes/pix-art/js/searx.js141
-rw-r--r--searx/static/themes/pix-art/less/definitions.less119
-rw-r--r--searx/static/themes/pix-art/less/mixins.less27
-rw-r--r--searx/static/themes/pix-art/less/search.less57
-rw-r--r--searx/static/themes/pix-art/less/style.less451
-rw-r--r--searx/templates/courgette/base.html3
-rw-r--r--searx/templates/courgette/preferences.html7
-rw-r--r--searx/templates/courgette/result_templates/code.html4
-rw-r--r--searx/templates/courgette/result_templates/default.html4
-rw-r--r--searx/templates/courgette/result_templates/images.html4
-rw-r--r--searx/templates/courgette/result_templates/map.html4
-rw-r--r--searx/templates/courgette/result_templates/torrent.html6
-rw-r--r--searx/templates/courgette/result_templates/videos.html4
-rw-r--r--searx/templates/default/base.html1
-rw-r--r--searx/templates/default/infobox.html2
-rw-r--r--searx/templates/default/preferences.html7
-rw-r--r--searx/templates/default/result_templates/code.html6
-rw-r--r--searx/templates/default/result_templates/default.html4
-rw-r--r--searx/templates/default/result_templates/images.html4
-rw-r--r--searx/templates/default/result_templates/map.html4
-rw-r--r--searx/templates/default/result_templates/torrent.html4
-rw-r--r--searx/templates/default/result_templates/videos.html4
-rw-r--r--searx/templates/oscar/base.html15
-rw-r--r--searx/templates/oscar/infobox.html2
-rw-r--r--searx/templates/oscar/macros.html14
-rw-r--r--searx/templates/oscar/preferences.html38
-rw-r--r--searx/templates/oscar/result_templates/code.html2
-rw-r--r--searx/templates/oscar/result_templates/images.html6
-rw-r--r--searx/templates/oscar/result_templates/videos.html2
-rw-r--r--searx/templates/oscar/results.html10
-rw-r--r--searx/templates/pix-art/about.html65
-rw-r--r--searx/templates/pix-art/base.html35
-rw-r--r--searx/templates/pix-art/index.html12
-rw-r--r--searx/templates/pix-art/preferences.html82
-rw-r--r--searx/templates/pix-art/result_templates/default.html7
-rw-r--r--searx/templates/pix-art/result_templates/images.html6
-rw-r--r--searx/templates/pix-art/results.html32
-rw-r--r--searx/templates/pix-art/search.html9
-rw-r--r--searx/templates/pix-art/stats.html22
-rw-r--r--searx/tests/engines/test_blekko_images.py11
-rw-r--r--searx/tests/engines/test_deezer.py8
-rw-r--r--searx/tests/engines/test_google_images.py11
-rw-r--r--searx/tests/engines/test_kickass.py1
-rw-r--r--searx/tests/engines/test_piratebay.py31
-rw-r--r--searx/tests/engines/test_spotify.py124
-rw-r--r--searx/tests/engines/test_yahoo.py107
-rw-r--r--searx/tests/test_engines.py1
-rw-r--r--searx/tests/test_plugins.py51
-rw-r--r--searx/tests/test_webapp.py44
-rw-r--r--searx/webapp.py138
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'] +\
" &bull; " +\
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'] +\
+ " &bull; " +\
+ result['album']['name'] +\
+ " &bull; " + 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
new file mode 100644
index 000000000..28afb0111
--- /dev/null
+++ b/searx/static/themes/pix-art/img/favicon.png
Binary files differ
diff --git a/searx/static/themes/pix-art/img/preference-icon-pixel.png b/searx/static/themes/pix-art/img/preference-icon-pixel.png
new file mode 100644
index 000000000..424e01e87
--- /dev/null
+++ b/searx/static/themes/pix-art/img/preference-icon-pixel.png
Binary files differ
diff --git a/searx/static/themes/pix-art/img/search-icon-pixel.png b/searx/static/themes/pix-art/img/search-icon-pixel.png
new file mode 100644
index 000000000..8235882eb
--- /dev/null
+++ b/searx/static/themes/pix-art/img/search-icon-pixel.png
Binary files differ
diff --git a/searx/static/themes/pix-art/img/searx-pixel-small.png b/searx/static/themes/pix-art/img/searx-pixel-small.png
new file mode 100644
index 000000000..76f381c5c
--- /dev/null
+++ b/searx/static/themes/pix-art/img/searx-pixel-small.png
Binary files differ
diff --git a/searx/static/themes/pix-art/img/searx-pixel.png b/searx/static/themes/pix-art/img/searx-pixel.png
new file mode 100644
index 000000000..6b8dbace6
--- /dev/null
+++ b/searx/static/themes/pix-art/img/searx-pixel.png
Binary files differ
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 }}&lrm;</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 }}&lrm;</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 }}&lrm;</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 }}&lrm;</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 }}&lrm; <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 }}&lrm; <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 }}&lrm; <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 }}&lrm; <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 }}&lrm; <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 }}&lrm; <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 }}&lrm;</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 }}&lrm;</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> &bull; <a href="{{ result.magnetlink }}" class="magnetlink">{{ icon('magnet') }} {{ _('magnet link') }}</a></small>{% endif %}
- {% if result.torrentfile %}<small> &bull; <a href="{{ result.torrentfile }}" class="torrentfile">{{ icon('download-alt') }} {{ _('torrent file') }}</a></small>{% endif %}
+ {% if result.torrentfile %}<small> &bull; <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] }})&lrm;</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 }}&amp;pageno={{ pageno }}{% if selected_categories %}&amp;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] }})&lrm;</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 }}%">&nbsp;</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 &bull; Album Title &bull; 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 &bull; Album Title &bull; 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'],