Update upstream sources to 0.10.0

This commit is contained in:
bastinc 2016-10-20 15:37:56 +02:00
parent 7028ebaf83
commit 1f21305b7c
169 changed files with 5564 additions and 2049 deletions

1
sources/.gitignore vendored
View File

@ -1,4 +1,5 @@
.coverage .coverage
coverage/
.installed.cfg .installed.cfg
engines.cfg engines.cfg
env env

View File

@ -16,11 +16,10 @@ install:
- ./manage.sh update_dev_packages - ./manage.sh update_dev_packages
- pip install coveralls - pip install coveralls
script: script:
- ./manage.sh pep8_check
- ./manage.sh styles - ./manage.sh styles
- ./manage.sh grunt_build - ./manage.sh grunt_build
- ./manage.sh tests
- ./manage.sh py_test_coverage - ./manage.sh py_test_coverage
- ./manage.sh robot_tests
after_success: after_success:
coveralls coveralls
notifications: notifications:

View File

@ -1,4 +1,4 @@
Searx was created by Adam Tauber and is maintained by Adam Tauber and Alexandre Flament. Searx was created by Adam Tauber and is maintained by Adam Tauber, Alexandre Flament and Noémi Ványi.
Major contributing authors: Major contributing authors:
@ -7,6 +7,7 @@ Major contributing authors:
- Thomas Pointhuber - Thomas Pointhuber
- Alexandre Flament `@dalf <https://github.com/dalf>`_ - Alexandre Flament `@dalf <https://github.com/dalf>`_
- @Cqoicebordel - @Cqoicebordel
- Noémi Ványi
People who have submitted patches/translates, reported bugs, consulted features or People who have submitted patches/translates, reported bugs, consulted features or
generally made searx better: generally made searx better:
@ -39,15 +40,21 @@ generally made searx better:
- @underr - @underr
- Emmanuel Benazera - Emmanuel Benazera
- @GreenLunar - @GreenLunar
- Noemi Vanyi
- Kang-min Liu - Kang-min Liu
- Kirill Isakov - Kirill Isakov
- Guilhem Bonnefille - Guilhem Bonnefille
- Marc Abonce Seguin - Marc Abonce Seguin
- @jibe-b - @jibe-b
- Christian Pietsch @pietsch - Christian Pietsch @pietsch
- @Maxqia - @Maxqia
- Ashutosh Das @pyprism - Ashutosh Das @pyprism
- YuLun Shih @imZack - YuLun Shih @imZack
- Dmitry Mikhirev @mikhirev - Dmitry Mikhirev @mikhirev
- David A Roberts `@davidar <https://github.com/davidar>`_
- Jan Verbeek @blyxxyz
- Ammar Najjar @ammarnajjar
- @stepshal
- François Revol @mmuman
- marc @a01200356
- Harry Wood @harry-wood
- Thomas Renard @threnard

View File

@ -1,3 +1,38 @@
0.10.0 2016.09.06
=================
- New engines
- Archive.is (general)
- INA (videos)
- Scanr (science)
- Google Scholar (science)
- Crossref (science)
- Openrepos (files)
- Microsoft Academic Search Engine (science)
- Hoogle (it)
- Diggbt (files)
- Dictzone (general - dictionary)
- Translated (general - translation)
- New Plugins
- Infinite scroll on results page
- DOAI rewrite
- Full theme redesign
- Display the number of results
- Filter searches by date range
- Instance config API endpoint
- Dependency version updates
- Socks proxy support for outgoing requests
- 404 page
News
~~~~
@kvch joined the maintainer team
0.9.0 2016.05.24 0.9.0 2016.05.24
================ ================
@ -36,6 +71,7 @@
- Multilingual autocompleter - Multilingual autocompleter
- Qwant autocompleter backend - Qwant autocompleter backend
0.8.1 2015.12.22 0.8.1 2015.12.22
================ ================

View File

@ -15,7 +15,7 @@ Installation
~~~~~~~~~~~~ ~~~~~~~~~~~~
- clone source: - clone source:
``git clone git@github.com:asciimoo/searx.git && cd searx`` ``git clone https://github.com/asciimoo/searx.git && cd searx``
- install dependencies: ``./manage.sh update_packages`` - install dependencies: ``./manage.sh update_packages``
- edit your - edit your
`settings.yml <https://github.com/asciimoo/searx/blob/master/searx/settings.yml>`__ `settings.yml <https://github.com/asciimoo/searx/blob/master/searx/settings.yml>`__

View File

@ -1,5 +1,6 @@
categories = ['general'] # optional categories = ['general'] # optional
def request(query, params): def request(query, params):
'''pre-request callback '''pre-request callback
@ -22,4 +23,3 @@ def response(resp):
resp: requests response object resp: requests response object
''' '''
return [{'url': '', 'title': '', 'content': ''}] return [{'url': '', 'title': '', 'content': ''}]

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
BASE_DIR=$(dirname `readlink -f $0`) BASE_DIR=$(dirname "`readlink -f "$0"`")
PYTHONPATH=$BASE_DIR PYTHONPATH=$BASE_DIR
SEARX_DIR="$BASE_DIR/searx" SEARX_DIR="$BASE_DIR/searx"
ACTION=$1 ACTION=$1
@ -58,7 +58,8 @@ styles() {
build_style themes/courgette/less/style.less themes/courgette/css/style.css build_style themes/courgette/less/style.less themes/courgette/css/style.css
build_style themes/courgette/less/style-rtl.less themes/courgette/css/style-rtl.css build_style themes/courgette/less/style-rtl.less themes/courgette/css/style-rtl.css
build_style less/bootstrap/bootstrap.less css/bootstrap.min.css build_style less/bootstrap/bootstrap.less css/bootstrap.min.css
build_style themes/oscar/less/oscar/oscar.less themes/oscar/css/oscar.min.css build_style themes/oscar/less/pointhi/oscar.less themes/oscar/css/pointhi.min.css
build_style themes/oscar/less/logicodev/oscar.less themes/oscar/css/logicodev.min.css
build_style themes/pix-art/less/style.less themes/pix-art/css/style.css build_style themes/pix-art/less/style.less themes/pix-art/css/style.css
} }

View File

@ -1,8 +1,8 @@
babel==2.2.0 babel==2.3.4
mock==1.0.1 mock==2.0.0
nose2[coverage-plugin] nose2[coverage-plugin]
pep8==1.7.0 pep8==1.7.0
plone.testing==4.0.15 plone.testing==5.0.0
robotframework-selenium2library==1.7.4 robotframework-selenium2library==1.7.4
robotsuite==1.7.0 robotsuite==1.7.0
transifex-client==0.11 transifex-client==0.11

View File

@ -1,12 +1,12 @@
certifi==2015.11.20.1 certifi==2016.2.28
flask==0.10.1 flask==0.11.1
flask-babel==0.9 flask-babel==0.11.1
lxml==3.5.0 lxml==3.6.0
ndg-httpsclient==0.4.0 ndg-httpsclient==0.4.1
pyasn1==0.1.9 pyasn1==0.1.9
pyasn1-modules==0.0.8 pyasn1-modules==0.0.8
pygments==2.0.2 pygments==2.1.3
pyopenssl==0.15.1 pyopenssl==0.15.1
python-dateutil==2.4.2 python-dateutil==2.5.3
pyyaml==3.11 pyyaml==3.11
requests==2.9.1 requests[socks]==2.10.0

View File

@ -19,7 +19,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
from os.path import realpath, dirname, splitext, join from os.path import realpath, dirname, splitext, join
import sys import sys
from imp import load_source from imp import load_source
from flask.ext.babel import gettext from flask_babel import gettext
from operator import itemgetter from operator import itemgetter
from searx import settings from searx import settings
from searx import logger from searx import logger
@ -42,7 +42,8 @@ engine_default_args = {'paging': False,
'shortcut': '-', 'shortcut': '-',
'disabled': False, 'disabled': False,
'suspend_end_time': 0, 'suspend_end_time': 0,
'continuous_errors': 0} 'continuous_errors': 0,
'time_range_support': False}
def load_module(filename): def load_module(filename):
@ -57,7 +58,11 @@ def load_module(filename):
def load_engine(engine_data): def load_engine(engine_data):
engine_name = engine_data['engine'] engine_name = engine_data['engine']
engine = load_module(engine_name + '.py') try:
engine = load_module(engine_name + '.py')
except:
logger.exception('Cannot load engine "{}"'.format(engine_name))
return None
for param_name in engine_data: for param_name in engine_data:
if param_name == 'engine': if param_name == 'engine':
@ -199,4 +204,5 @@ if 'engines' not in settings or not settings['engines']:
for engine_data in settings['engines']: for engine_data in settings['engines']:
engine = load_engine(engine_data) engine = load_engine(engine_data)
engines[engine.name] = engine if engine is not None:
engines[engine.name] = engine

View File

@ -34,6 +34,7 @@ def locale_to_lang_code(locale):
locale = locale.split('_')[0] locale = locale.split('_')[0]
return locale return locale
# wikis for some languages were moved off from the main site, we need to make # wikis for some languages were moved off from the main site, we need to make
# requests to correct URLs to be able to get results in those languages # requests to correct URLs to be able to get results in those languages
lang_urls = { lang_urls = {
@ -70,6 +71,7 @@ def get_lang_urls(language):
return lang_urls[language] return lang_urls[language]
return lang_urls['all'] return lang_urls['all']
# Language names to build search requests for # Language names to build search requests for
# those languages which are hosted on the main site. # those languages which are hosted on the main site.
main_langs = { main_langs = {

View File

@ -16,6 +16,7 @@ from urllib import quote
from lxml import html from lxml import html
from operator import itemgetter from operator import itemgetter
from searx.engines.xpath import extract_text from searx.engines.xpath import extract_text
from searx.utils import get_torrent_size
# engine dependent config # engine dependent config
categories = ['videos', 'music', 'files'] categories = ['videos', 'music', 'files']
@ -68,20 +69,7 @@ def response(resp):
leech = 0 leech = 0
# convert filesize to byte if possible # convert filesize to byte if possible
try: filesize = get_torrent_size(filesize, filesize_multiplier)
filesize = float(filesize)
# convert filesize to byte
if filesize_multiplier == 'TB':
filesize = int(filesize * 1024 * 1024 * 1024 * 1024)
elif filesize_multiplier == 'GB':
filesize = int(filesize * 1024 * 1024 * 1024)
elif filesize_multiplier == 'MB':
filesize = int(filesize * 1024 * 1024)
elif filesize_multiplier == 'KB':
filesize = int(filesize * 1024)
except:
filesize = None
# convert files to int if possible # convert files to int if possible
if files.isdigit(): if files.isdigit():

View File

@ -9,7 +9,7 @@ categories = []
url = 'https://download.finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s={query}=X' url = 'https://download.finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s={query}=X'
weight = 100 weight = 100
parser_re = re.compile(u'.*?(\d+(?:\.\d+)?) ([^.0-9]+) (?:in|to) ([^.0-9]+)', re.I) # noqa parser_re = re.compile(u'.*?(\\d+(?:\\.\\d+)?) ([^.0-9]+) (?:in|to) ([^.0-9]+)', re.I) # noqa
db = 1 db = 1

View File

@ -13,7 +13,6 @@
""" """
from urllib import urlencode from urllib import urlencode
from urlparse import urljoin
from lxml import html from lxml import html
import re import re
from searx.engines.xpath import extract_text from searx.engines.xpath import extract_text
@ -21,10 +20,16 @@ from searx.engines.xpath import extract_text
# engine dependent config # engine dependent config
categories = ['images'] categories = ['images']
paging = True paging = True
time_range_support = True
# search-url # search-url
base_url = 'https://www.deviantart.com/' base_url = 'https://www.deviantart.com/'
search_url = base_url + 'browse/all/?offset={offset}&{query}' search_url = base_url + 'browse/all/?offset={offset}&{query}'
time_range_url = '&order={range}'
time_range_dict = {'day': 11,
'week': 14,
'month': 15}
# do search-request # do search-request
@ -33,6 +38,8 @@ def request(query, params):
params['url'] = search_url.format(offset=offset, params['url'] = search_url.format(offset=offset,
query=urlencode({'q': query})) query=urlencode({'q': query}))
if params['time_range'] in time_range_dict:
params['url'] += time_range_url.format(range=time_range_dict[params['time_range']])
return params return params
@ -47,14 +54,13 @@ def response(resp):
dom = html.fromstring(resp.text) dom = html.fromstring(resp.text)
regex = re.compile('\/200H\/') regex = re.compile(r'\/200H\/')
# parse results # parse results
for result in dom.xpath('//div[contains(@class, "tt-a tt-fh")]'): for result in dom.xpath('.//span[@class="thumb wide"]'):
link = result.xpath('.//a[contains(@class, "thumb")]')[0] link = result.xpath('.//a[@class="torpedo-thumb-link"]')[0]
url = urljoin(base_url, link.attrib.get('href')) url = link.attrib.get('href')
title_links = result.xpath('.//span[@class="details"]//a[contains(@class, "t")]') title = extract_text(result.xpath('.//span[@class="title"]'))
title = extract_text(title_links[0])
thumbnail_src = link.xpath('.//img')[0].attrib.get('src') thumbnail_src = link.xpath('.//img')[0].attrib.get('src')
img_src = regex.sub('/', thumbnail_src) img_src = regex.sub('/', thumbnail_src)

View File

@ -0,0 +1,69 @@
"""
Dictzone
@website https://dictzone.com/
@provide-api no
@using-api no
@results HTML (using search portal)
@stable no (HTML can change)
@parse url, title, content
"""
import re
from urlparse import urljoin
from lxml import html
from cgi import escape
from searx.utils import is_valid_lang
categories = ['general']
url = u'http://dictzone.com/{from_lang}-{to_lang}-dictionary/{query}'
weight = 100
parser_re = re.compile(u'.*?([a-z]+)-([a-z]+) ([^ ]+)$', re.I)
results_xpath = './/table[@id="r"]/tr'
def request(query, params):
m = parser_re.match(unicode(query, 'utf8'))
if not m:
return params
from_lang, to_lang, query = m.groups()
from_lang = is_valid_lang(from_lang)
to_lang = is_valid_lang(to_lang)
if not from_lang or not to_lang:
return params
params['url'] = url.format(from_lang=from_lang[2],
to_lang=to_lang[2],
query=query)
return params
def response(resp):
results = []
dom = html.fromstring(resp.text)
for k, result in enumerate(dom.xpath(results_xpath)[1:]):
try:
from_result, to_results_raw = result.xpath('./td')
except:
continue
to_results = []
for to_result in to_results_raw.xpath('./p/a'):
t = to_result.text_content()
if t.strip():
to_results.append(to_result.text_content())
results.append({
'url': urljoin(resp.url, '?%d' % k),
'title': escape(from_result.text_content()),
'content': escape('; '.join(to_results))
})
return results

View File

@ -0,0 +1,58 @@
"""
DigBT (Videos, Music, Files)
@website https://digbt.org
@provide-api no
@using-api no
@results HTML (using search portal)
@stable no (HTML can change)
@parse url, title, content, magnetlink
"""
from urlparse import urljoin
from lxml import html
from searx.engines.xpath import extract_text
from searx.utils import get_torrent_size
categories = ['videos', 'music', 'files']
paging = True
URL = 'https://digbt.org'
SEARCH_URL = URL + '/search/{query}-time-{pageno}'
FILESIZE = 3
FILESIZE_MULTIPLIER = 4
def request(query, params):
params['url'] = SEARCH_URL.format(query=query, pageno=params['pageno'])
return params
def response(resp):
dom = html.fromstring(resp.content)
search_res = dom.xpath('.//td[@class="x-item"]')
if not search_res:
return list()
results = list()
for result in search_res:
url = urljoin(URL, result.xpath('.//a[@title]/@href')[0])
title = result.xpath('.//a[@title]/text()')[0]
content = extract_text(result.xpath('.//div[@class="files"]'))
files_data = extract_text(result.xpath('.//div[@class="tail"]')).split()
filesize = get_torrent_size(files_data[FILESIZE], files_data[FILESIZE_MULTIPLIER])
magnetlink = result.xpath('.//div[@class="tail"]//a[@class="title"]/@href')[0]
results.append({'url': url,
'title': title,
'content': content,
'filesize': filesize,
'magnetlink': magnetlink,
'seed': 'N/A',
'leech': 'N/A',
'template': 'torrent.html'})
return results

View File

@ -11,21 +11,26 @@
@parse url, title, content @parse url, title, content
@todo rewrite to api @todo rewrite to api
@todo language support
(the current used site does not support language-change)
""" """
from urllib import urlencode from urllib import urlencode
from lxml.html import fromstring from lxml.html import fromstring
from searx.engines.xpath import extract_text from searx.engines.xpath import extract_text
from searx.languages import language_codes
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general']
paging = True paging = True
language_support = True language_support = True
time_range_support = True
# search-url # search-url
url = 'https://duckduckgo.com/html?{query}&s={offset}' url = 'https://duckduckgo.com/html?{query}&s={offset}'
time_range_url = '&df={range}'
time_range_dict = {'day': 'd',
'week': 'w',
'month': 'm'}
# specific xpath variables # specific xpath variables
result_xpath = '//div[@class="result results_links results_links_deep web-result "]' # noqa result_xpath = '//div[@class="result results_links results_links_deep web-result "]' # noqa
@ -39,13 +44,31 @@ def request(query, params):
offset = (params['pageno'] - 1) * 30 offset = (params['pageno'] - 1) * 30
if params['language'] == 'all': if params['language'] == 'all':
locale = 'en-us' locale = None
else: else:
locale = params['language'].replace('_', '-').lower() locale = params['language'].split('_')
if len(locale) == 2:
# country code goes first
locale = locale[1].lower() + '-' + locale[0].lower()
else:
# tries to get a country code from language
locale = locale[0].lower()
lang_codes = [x[0] for x in language_codes]
for lc in lang_codes:
lc = lc.split('_')
if locale == lc[0]:
locale = lc[1].lower() + '-' + lc[0].lower()
break
params['url'] = url.format( if locale:
query=urlencode({'q': query, 'kl': locale}), params['url'] = url.format(
offset=offset) query=urlencode({'q': query, 'kl': locale}), offset=offset)
else:
params['url'] = url.format(
query=urlencode({'q': query}), offset=offset)
if params['time_range'] in time_range_dict:
params['url'] += time_range_url.format(range=time_range_dict[params['time_range']])
return params return params

View File

@ -8,6 +8,7 @@ paging = True
class FilecropResultParser(HTMLParser): class FilecropResultParser(HTMLParser):
def __init__(self): def __init__(self):
HTMLParser.__init__(self) HTMLParser.__init__(self)
self.__start_processing = False self.__start_processing = False

View File

@ -24,6 +24,7 @@ categories = ['general']
paging = True paging = True
language_support = True language_support = True
use_locale_domain = True use_locale_domain = True
time_range_support = True
# based on https://en.wikipedia.org/wiki/List_of_Google_domains and tests # based on https://en.wikipedia.org/wiki/List_of_Google_domains and tests
default_hostname = 'www.google.com' default_hostname = 'www.google.com'
@ -92,6 +93,11 @@ search_url = ('https://{hostname}' +
search_path + search_path +
'?{query}&start={offset}&gws_rd=cr&gbv=1&lr={lang}&ei=x') '?{query}&start={offset}&gws_rd=cr&gbv=1&lr={lang}&ei=x')
time_range_search = "&tbs=qdr:{range}"
time_range_dict = {'day': 'd',
'week': 'w',
'month': 'm'}
# other URLs # other URLs
map_hostname_start = 'maps.google.' map_hostname_start = 'maps.google.'
maps_path = '/maps' maps_path = '/maps'
@ -179,6 +185,8 @@ def request(query, params):
query=urlencode({'q': query}), query=urlencode({'q': query}),
hostname=google_hostname, hostname=google_hostname,
lang=url_lang) lang=url_lang)
if params['time_range'] in time_range_dict:
params['url'] += time_range_search.format(range=time_range_dict[params['time_range']])
params['headers']['Accept-Language'] = language params['headers']['Accept-Language'] = language
params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
@ -300,9 +308,9 @@ def parse_map_detail(parsed_url, result, google_hostname):
results = [] results = []
# try to parse the geoloc # try to parse the geoloc
m = re.search('@([0-9\.]+),([0-9\.]+),([0-9]+)', parsed_url.path) m = re.search(r'@([0-9\.]+),([0-9\.]+),([0-9]+)', parsed_url.path)
if m is None: if m is None:
m = re.search('ll\=([0-9\.]+),([0-9\.]+)\&z\=([0-9]+)', parsed_url.query) m = re.search(r'll\=([0-9\.]+),([0-9\.]+)\&z\=([0-9]+)', parsed_url.query)
if m is not None: if m is not None:
# geoloc found (ignored) # geoloc found (ignored)

View File

@ -11,7 +11,6 @@
""" """
from urllib import urlencode from urllib import urlencode
from urlparse import parse_qs
from json import loads from json import loads
from lxml import html from lxml import html
@ -19,24 +18,38 @@ from lxml import html
categories = ['images'] categories = ['images']
paging = True paging = True
safesearch = True safesearch = True
time_range_support = True
number_of_results = 100
search_url = 'https://www.google.com/search'\ search_url = 'https://www.google.com/search'\
'?{query}'\ '?{query}'\
'&asearch=ichunk'\
'&async=_id:rg_s,_pms:s'\
'&tbm=isch'\ '&tbm=isch'\
'&ijn=1'\ '&yv=2'\
'&start={offset}' '&{search_options}'
time_range_attr = "qdr:{range}"
time_range_dict = {'day': 'd',
'week': 'w',
'month': 'm'}
# do search-request # do search-request
def request(query, params): def request(query, params):
offset = (params['pageno'] - 1) * 100
params['url'] = search_url.format(query=urlencode({'q': query}), search_options = {
offset=offset, 'ijn': params['pageno'] - 1,
safesearch=safesearch) 'start': (params['pageno'] - 1) * number_of_results
}
if params['time_range'] in time_range_dict:
search_options['tbs'] = time_range_attr.format(range=time_range_dict[params['time_range']])
if safesearch and params['safesearch']: if safesearch and params['safesearch']:
params['url'] += '&' + urlencode({'safe': 'active'}) search_options['safe'] = 'on'
params['url'] = search_url.format(query=urlencode({'q': query}),
search_options=urlencode(search_options))
return params return params
@ -45,12 +58,17 @@ def request(query, params):
def response(resp): def response(resp):
results = [] results = []
dom = html.fromstring(resp.text) g_result = loads(resp.text)
dom = html.fromstring(g_result[1][1])
# parse results # parse results
for result in dom.xpath('//div[@data-ved]'): for result in dom.xpath('//div[@data-ved]'):
metadata = loads(result.xpath('./div[@class="rg_meta"]/text()')[0]) try:
metadata = loads(''.join(result.xpath('./div[@class="rg_meta"]/text()')))
except:
continue
thumbnail_src = metadata['tu'] thumbnail_src = metadata['tu']

View File

@ -0,0 +1,83 @@
# INA (Videos)
#
# @website https://www.ina.fr/
# @provide-api no
#
# @using-api no
# @results HTML (using search portal)
# @stable no (HTML can change)
# @parse url, title, content, publishedDate, thumbnail
#
# @todo set content-parameter with correct data
# @todo embedded (needs some md5 from video page)
from json import loads
from urllib import urlencode
from lxml import html
from HTMLParser import HTMLParser
from searx.engines.xpath import extract_text
from dateutil import parser
# engine dependent config
categories = ['videos']
paging = True
page_size = 48
# search-url
base_url = 'https://www.ina.fr'
search_url = base_url + '/layout/set/ajax/recherche/result?autopromote=&hf={ps}&b={start}&type=Video&r=&{query}'
# specific xpath variables
results_xpath = '//div[contains(@class,"search-results--list")]/div[@class="media"]'
url_xpath = './/a/@href'
title_xpath = './/h3[@class="h3--title media-heading"]'
thumbnail_xpath = './/img/@src'
publishedDate_xpath = './/span[@class="broadcast"]'
content_xpath = './/p[@class="media-body__summary"]'
# do search-request
def request(query, params):
params['url'] = search_url.format(ps=page_size,
start=params['pageno'] * page_size,
query=urlencode({'q': query}))
return params
# get response from search-request
def response(resp):
results = []
# we get html in a JSON container...
response = loads(resp.text)
if "content" not in response:
return []
dom = html.fromstring(response["content"])
p = HTMLParser()
# parse results
for result in dom.xpath(results_xpath):
videoid = result.xpath(url_xpath)[0]
url = base_url + videoid
title = p.unescape(extract_text(result.xpath(title_xpath)))
thumbnail = extract_text(result.xpath(thumbnail_xpath)[0])
if thumbnail[0] == '/':
thumbnail = base_url + thumbnail
d = extract_text(result.xpath(publishedDate_xpath)[0])
d = d.split('/')
# force ISO date to avoid wrong parsing
d = "%s-%s-%s" % (d[2], d[1], d[0])
publishedDate = parser.parse(d)
content = extract_text(result.xpath(content_xpath))
# append result
results.append({'url': url,
'title': title,
'content': content,
'template': 'videos.html',
'publishedDate': publishedDate,
'thumbnail': thumbnail})
# return results
return results

View File

@ -6,7 +6,16 @@ search_url = None
url_query = None url_query = None
content_query = None content_query = None
title_query = None title_query = None
# suggestion_xpath = '' suggestion_query = ''
results_query = ''
# parameters for engines with paging support
#
# number of results on each page
# (only needed if the site requires not a page number, but an offset)
page_size = 1
# number of the first page (usually 0 or 1)
first_page_num = 1
def iterate(iterable): def iterate(iterable):
@ -69,19 +78,36 @@ def query(data, query_string):
def request(query, params): def request(query, params):
query = urlencode({'q': query})[2:] query = urlencode({'q': query})[2:]
params['url'] = search_url.format(query=query)
fp = {'query': query}
if paging and search_url.find('{pageno}') >= 0:
fp['pageno'] = (params['pageno'] - 1) * page_size + first_page_num
params['url'] = search_url.format(**fp)
params['query'] = query params['query'] = query
return params return params
def response(resp): def response(resp):
results = [] results = []
json = loads(resp.text) json = loads(resp.text)
if results_query:
for result in query(json, results_query)[0]:
url = query(result, url_query)[0]
title = query(result, title_query)[0]
content = query(result, content_query)[0]
results.append({'url': url, 'title': title, 'content': content})
else:
for url, title, content in zip(
query(json, url_query),
query(json, title_query),
query(json, content_query)
):
results.append({'url': url, 'title': title, 'content': content})
urls = query(json, url_query) if not suggestion_query:
contents = query(json, content_query) return results
titles = query(json, title_query) for suggestion in query(json, suggestion_query):
for url, title, content in zip(urls, titles, contents): results.append({'suggestion': suggestion})
results.append({'url': url, 'title': title, 'content': content})
return results return results

View File

@ -0,0 +1,78 @@
"""
ScanR Structures (Science)
@website https://scanr.enseignementsup-recherche.gouv.fr
@provide-api yes (https://scanr.enseignementsup-recherche.gouv.fr/api/swagger-ui.html)
@using-api yes
@results JSON
@stable yes
@parse url, title, content, img_src
"""
from urllib import urlencode
from json import loads, dumps
from dateutil import parser
from searx.utils import html_to_text
# engine dependent config
categories = ['science']
paging = True
page_size = 20
# search-url
url = 'https://scanr.enseignementsup-recherche.gouv.fr/'
search_url = url + 'api/structures/search'
# do search-request
def request(query, params):
params['url'] = search_url
params['method'] = 'POST'
params['headers']['Content-type'] = "application/json"
params['data'] = dumps({"query": query,
"searchField": "ALL",
"sortDirection": "ASC",
"sortOrder": "RELEVANCY",
"page": params['pageno'],
"pageSize": page_size})
return params
# get response from search-request
def response(resp):
results = []
search_res = loads(resp.text)
# return empty array if there are no results
if search_res.get('total') < 1:
return []
# parse results
for result in search_res['results']:
if 'id' not in result:
continue
# is it thumbnail or img_src??
thumbnail = None
if 'logo' in result:
thumbnail = result['logo']
if thumbnail[0] == '/':
thumbnail = url + thumbnail
content = None
if 'highlights' in result:
content = result['highlights'][0]['value']
# append result
results.append({'url': url + 'structure/' + result['id'],
'title': result['label'],
# 'thumbnail': thumbnail,
'img_src': thumbnail,
'content': html_to_text(content)})
# return results
return results

View File

@ -57,6 +57,7 @@ def get_client_id():
logger.warning("Unable to fetch guest client_id from SoundCloud, check parser!") logger.warning("Unable to fetch guest client_id from SoundCloud, check parser!")
return "" return ""
# api-key # api-key
guest_client_id = get_client_id() guest_client_id = get_client_id()

View File

@ -68,15 +68,15 @@ def response(resp):
url = link.attrib.get('href') url = link.attrib.get('href')
# block google-ad url's # block google-ad url's
if re.match("^http(s|)://(www\.)?google\.[a-z]+/aclk.*$", url): if re.match(r"^http(s|)://(www\.)?google\.[a-z]+/aclk.*$", url):
continue continue
# block startpage search url's # block startpage search url's
if re.match("^http(s|)://(www\.)?startpage\.com/do/search\?.*$", url): if re.match(r"^http(s|)://(www\.)?startpage\.com/do/search\?.*$", url):
continue continue
# block ixquick search url's # block ixquick search url's
if re.match("^http(s|)://(www\.)?ixquick\.com/do/search\?.*$", url): if re.match(r"^http(s|)://(www\.)?ixquick\.com/do/search\?.*$", url):
continue continue
title = escape(extract_text(link)) title = escape(extract_text(link))
@ -89,7 +89,7 @@ def response(resp):
published_date = None published_date = None
# check if search result starts with something like: "2 Sep 2014 ... " # check if search result starts with something like: "2 Sep 2014 ... "
if re.match("^([1-9]|[1-2][0-9]|3[0-1]) [A-Z][a-z]{2} [0-9]{4} \.\.\. ", content): if re.match(r"^([1-9]|[1-2][0-9]|3[0-1]) [A-Z][a-z]{2} [0-9]{4} \.\.\. ", content):
date_pos = content.find('...') + 4 date_pos = content.find('...') + 4
date_string = content[0:date_pos - 5] date_string = content[0:date_pos - 5]
published_date = parser.parse(date_string, dayfirst=True) published_date = parser.parse(date_string, dayfirst=True)
@ -98,7 +98,7 @@ def response(resp):
content = content[date_pos:] content = content[date_pos:]
# check if search result starts with something like: "5 days ago ... " # check if search result starts with something like: "5 days ago ... "
elif re.match("^[0-9]+ days? ago \.\.\. ", content): elif re.match(r"^[0-9]+ days? ago \.\.\. ", content):
date_pos = content.find('...') + 4 date_pos = content.find('...') + 4
date_string = content[0:date_pos - 5] date_string = content[0:date_pos - 5]

View File

@ -25,10 +25,10 @@ base_url = 'https://swisscows.ch/'
search_string = '?{query}&page={page}' search_string = '?{query}&page={page}'
# regex # regex
regex_json = re.compile('initialData: {"Request":(.|\n)*},\s*environment') regex_json = re.compile(r'initialData: {"Request":(.|\n)*},\s*environment')
regex_json_remove_start = re.compile('^initialData:\s*') regex_json_remove_start = re.compile(r'^initialData:\s*')
regex_json_remove_end = re.compile(',\s*environment$') regex_json_remove_end = re.compile(r',\s*environment$')
regex_img_url_remove_start = re.compile('^https?://i\.swisscows\.ch/\?link=') regex_img_url_remove_start = re.compile(r'^https?://i\.swisscows\.ch/\?link=')
# do search-request # do search-request

View File

@ -48,7 +48,7 @@ def response(resp):
return [] return []
# regular expression for parsing torrent size strings # regular expression for parsing torrent size strings
size_re = re.compile('Size:\s*([\d.]+)(TB|GB|MB|B)', re.IGNORECASE) size_re = re.compile(r'Size:\s*([\d.]+)(TB|GB|MB|B)', re.IGNORECASE)
# processing the results, two rows at a time # processing the results, two rows at a time
for i in xrange(0, len(rows), 2): for i in xrange(0, len(rows), 2):

View File

@ -0,0 +1,65 @@
"""
MyMemory Translated
@website https://mymemory.translated.net/
@provide-api yes (https://mymemory.translated.net/doc/spec.php)
@using-api yes
@results JSON
@stable yes
@parse url, title, content
"""
import re
from cgi import escape
from searx.utils import is_valid_lang
categories = ['general']
url = u'http://api.mymemory.translated.net/get?q={query}&langpair={from_lang}|{to_lang}{key}'
web_url = u'http://mymemory.translated.net/en/{from_lang}/{to_lang}/{query}'
weight = 100
parser_re = re.compile(u'.*?([a-z]+)-([a-z]+) (.{2,})$', re.I)
api_key = ''
def request(query, params):
m = parser_re.match(unicode(query, 'utf8'))
if not m:
return params
from_lang, to_lang, query = m.groups()
from_lang = is_valid_lang(from_lang)
to_lang = is_valid_lang(to_lang)
if not from_lang or not to_lang:
return params
if api_key:
key_form = '&key=' + api_key
else:
key_form = ''
params['url'] = url.format(from_lang=from_lang[1],
to_lang=to_lang[1],
query=query,
key=key_form)
params['query'] = query
params['from_lang'] = from_lang
params['to_lang'] = to_lang
return params
def response(resp):
results = []
results.append({
'url': escape(web_url.format(
from_lang=resp.search_params['from_lang'][2],
to_lang=resp.search_params['to_lang'][2],
query=resp.search_params['query'])),
'title': escape('[{0}-{1}] {2}'.format(
resp.search_params['from_lang'][1],
resp.search_params['to_lang'][1],
resp.search_params['query'])),
'content': escape(resp.json()['responseData']['translatedText'])
})
return results

View File

@ -1,56 +1,86 @@
import json # -*- coding: utf-8 -*-
"""
Wikidata
@website https://wikidata.org
@provide-api yes (https://wikidata.org/w/api.php)
@using-api partially (most things require scraping)
@results JSON, HTML
@stable no (html can change)
@parse url, infobox
"""
from searx import logger from searx import logger
from searx.poolrequests import get from searx.poolrequests import get
from searx.utils import format_date_by_locale from searx.engines.xpath import extract_text
from datetime import datetime from json import loads
from dateutil.parser import parse as dateutil_parse from lxml.html import fromstring
from urllib import urlencode from urllib import urlencode
logger = logger.getChild('wikidata') logger = logger.getChild('wikidata')
result_count = 1 result_count = 1
# urls
wikidata_host = 'https://www.wikidata.org' wikidata_host = 'https://www.wikidata.org'
url_search = wikidata_host \
+ '/wiki/Special:ItemDisambiguation?{query}'
wikidata_api = wikidata_host + '/w/api.php' wikidata_api = wikidata_host + '/w/api.php'
url_search = wikidata_api \
+ '?action=query&list=search&format=json'\
+ '&srnamespace=0&srprop=sectiontitle&{query}'
url_detail = wikidata_api\ url_detail = wikidata_api\
+ '?action=wbgetentities&format=json'\ + '?action=parse&format=json&{query}'\
+ '&props=labels%7Cinfo%7Csitelinks'\ + '&redirects=1&prop=text%7Cdisplaytitle%7Clanglinks%7Crevid'\
+ '%7Csitelinks%2Furls%7Cdescriptions%7Cclaims'\ + '&disableeditsection=1&disabletidy=1&preview=1&sectionpreview=1&disabletoc=1&utf8=1&formatversion=2'
+ '&{query}'
url_map = 'https://www.openstreetmap.org/'\ url_map = 'https://www.openstreetmap.org/'\
+ '?lat={latitude}&lon={longitude}&zoom={zoom}&layers=M' + '?lat={latitude}&lon={longitude}&zoom={zoom}&layers=M'
url_image = 'https://commons.wikimedia.org/wiki/Special:FilePath/{filename}?width=500&height=400'
# xpaths
wikidata_ids_xpath = '//div/ul[@class="wikibase-disambiguation"]/li/a/@title'
title_xpath = '//*[contains(@class,"wikibase-title-label")]'
description_xpath = '//div[contains(@class,"wikibase-entitytermsview-heading-description")]'
property_xpath = '//div[@id="{propertyid}"]'
label_xpath = './/div[contains(@class,"wikibase-statementgroupview-property-label")]/a'
url_xpath = './/a[contains(@class,"external free") or contains(@class, "wb-external-id")]'
wikilink_xpath = './/ul[contains(@class,"wikibase-sitelinklistview-listview")]'\
+ '/li[contains(@data-wb-siteid,"{wikiid}")]//a/@href'
property_row_xpath = './/div[contains(@class,"wikibase-statementview")]'
preferred_rank_xpath = './/span[contains(@class,"wikibase-rankselector-preferred")]'
value_xpath = './/div[contains(@class,"wikibase-statementview-mainsnak")]'\
+ '/*/div[contains(@class,"wikibase-snakview-value")]'
language_fallback_xpath = '//sup[contains(@class,"wb-language-fallback-indicator")]'
calendar_name_xpath = './/sup[contains(@class,"wb-calendar-name")]'
def request(query, params): def request(query, params):
language = params['language'].split('_')[0]
if language == 'all':
language = 'en'
params['url'] = url_search.format( params['url'] = url_search.format(
query=urlencode({'srsearch': query, query=urlencode({'label': query,
'srlimit': result_count})) 'language': language}))
return params return params
def response(resp): def response(resp):
results = [] results = []
search_res = json.loads(resp.text) html = fromstring(resp.content)
wikidata_ids = html.xpath(wikidata_ids_xpath)
wikidata_ids = set()
for r in search_res.get('query', {}).get('search', {}):
wikidata_ids.add(r.get('title', ''))
language = resp.search_params['language'].split('_')[0] language = resp.search_params['language'].split('_')[0]
if language == 'all': if language == 'all':
language = 'en' language = 'en'
url = url_detail.format(query=urlencode({'ids': '|'.join(wikidata_ids), # TODO: make requests asynchronous to avoid timeout when result_count > 1
'languages': language + '|en'})) for wikidata_id in wikidata_ids[:result_count]:
url = url_detail.format(query=urlencode({'page': wikidata_id,
htmlresponse = get(url) 'uselang': language}))
jsonresponse = json.loads(htmlresponse.content) htmlresponse = get(url)
for wikidata_id in wikidata_ids: jsonresponse = loads(htmlresponse.content)
results = results + getDetail(jsonresponse, wikidata_id, language, resp.search_params['language']) results += getDetail(jsonresponse, wikidata_id, language, resp.search_params['language'])
return results return results
@ -60,124 +90,206 @@ def getDetail(jsonresponse, wikidata_id, language, locale):
urls = [] urls = []
attributes = [] attributes = []
result = jsonresponse.get('entities', {}).get(wikidata_id, {}) title = jsonresponse.get('parse', {}).get('displaytitle', {})
result = jsonresponse.get('parse', {}).get('text', {})
title = result.get('labels', {}).get(language, {}).get('value', None) if not title or not result:
if title is None:
title = result.get('labels', {}).get('en', {}).get('value', None)
if title is None:
return results return results
description = result\ title = fromstring(title)
.get('descriptions', {})\ for elem in title.xpath(language_fallback_xpath):
.get(language, {})\ elem.getparent().remove(elem)
.get('value', None) title = extract_text(title.xpath(title_xpath))
if description is None: result = fromstring(result)
description = result\ for elem in result.xpath(language_fallback_xpath):
.get('descriptions', {})\ elem.getparent().remove(elem)
.get('en', {})\
.get('value', '')
claims = result.get('claims', {}) description = extract_text(result.xpath(description_xpath))
official_website = get_string(claims, 'P856', None)
if official_website is not None:
urls.append({'title': 'Official site', 'url': official_website})
results.append({'title': title, 'url': official_website})
# URLS
# official website
add_url(urls, result, 'P856', results=results)
# wikipedia
wikipedia_link_count = 0 wikipedia_link_count = 0
wikipedia_link = get_wikilink(result, language + 'wiki') wikipedia_link = get_wikilink(result, language + 'wiki')
wikipedia_link_count += add_url(urls, if wikipedia_link:
'Wikipedia (' + language + ')', wikipedia_link_count += 1
wikipedia_link) urls.append({'title': 'Wikipedia (' + language + ')',
'url': wikipedia_link})
if language != 'en': if language != 'en':
wikipedia_en_link = get_wikilink(result, 'enwiki') wikipedia_en_link = get_wikilink(result, 'enwiki')
wikipedia_link_count += add_url(urls, if wikipedia_en_link:
'Wikipedia (en)', wikipedia_link_count += 1
wikipedia_en_link) urls.append({'title': 'Wikipedia (en)',
if wikipedia_link_count == 0: 'url': wikipedia_en_link})
misc_language = get_wiki_firstlanguage(result, 'wiki')
if misc_language is not None:
add_url(urls,
'Wikipedia (' + misc_language + ')',
get_wikilink(result, misc_language + 'wiki'))
if language != 'en': # TODO: get_wiki_firstlanguage
add_url(urls, # if wikipedia_link_count == 0:
'Wiki voyage (' + language + ')',
get_wikilink(result, language + 'wikivoyage'))
add_url(urls, # more wikis
'Wiki voyage (en)', add_url(urls, result, default_label='Wikivoyage (' + language + ')', link_type=language + 'wikivoyage')
get_wikilink(result, 'enwikivoyage')) add_url(urls, result, default_label='Wikiquote (' + language + ')', link_type=language + 'wikiquote')
add_url(urls, result, default_label='Wikimedia Commons', link_type='commonswiki')
if language != 'en': add_url(urls, result, 'P625', 'OpenStreetMap', link_type='geo')
add_url(urls,
'Wikiquote (' + language + ')',
get_wikilink(result, language + 'wikiquote'))
add_url(urls, # musicbrainz
'Wikiquote (en)', add_url(urls, result, 'P434', 'MusicBrainz', 'http://musicbrainz.org/artist/')
get_wikilink(result, 'enwikiquote')) add_url(urls, result, 'P435', 'MusicBrainz', 'http://musicbrainz.org/work/')
add_url(urls, result, 'P436', 'MusicBrainz', 'http://musicbrainz.org/release-group/')
add_url(urls, result, 'P966', 'MusicBrainz', 'http://musicbrainz.org/label/')
add_url(urls, # IMDb
'Commons wiki', add_url(urls, result, 'P345', 'IMDb', 'https://www.imdb.com/', link_type='imdb')
get_wikilink(result, 'commonswiki')) # source code repository
add_url(urls, result, 'P1324')
# blog
add_url(urls, result, 'P1581')
# social media links
add_url(urls, result, 'P2397', 'YouTube', 'https://www.youtube.com/channel/')
add_url(urls, result, 'P1651', 'YouTube', 'https://www.youtube.com/watch?v=')
add_url(urls, result, 'P2002', 'Twitter', 'https://twitter.com/')
add_url(urls, result, 'P2013', 'Facebook', 'https://facebook.com/')
add_url(urls, result, 'P2003', 'Instagram', 'https://instagram.com/')
add_url(urls, urls.append({'title': 'Wikidata',
'Location', 'url': 'https://www.wikidata.org/wiki/'
get_geolink(claims, 'P625', None)) + wikidata_id + '?uselang=' + language})
add_url(urls, # INFOBOX ATTRIBUTES (ROWS)
'Wikidata',
'https://www.wikidata.org/wiki/'
+ wikidata_id + '?uselang=' + language)
musicbrainz_work_id = get_string(claims, 'P435') # DATES
if musicbrainz_work_id is not None: # inception date
add_url(urls, add_attribute(attributes, result, 'P571', date=True)
'MusicBrainz', # dissolution date
'http://musicbrainz.org/work/' add_attribute(attributes, result, 'P576', date=True)
+ musicbrainz_work_id) # start date
add_attribute(attributes, result, 'P580', date=True)
# end date
add_attribute(attributes, result, 'P582', date=True)
# date of birth
add_attribute(attributes, result, 'P569', date=True)
# date of death
add_attribute(attributes, result, 'P570', date=True)
# date of spacecraft launch
add_attribute(attributes, result, 'P619', date=True)
# date of spacecraft landing
add_attribute(attributes, result, 'P620', date=True)
musicbrainz_artist_id = get_string(claims, 'P434') # nationality
if musicbrainz_artist_id is not None: add_attribute(attributes, result, 'P27')
add_url(urls, # country of origin
'MusicBrainz', add_attribute(attributes, result, 'P495')
'http://musicbrainz.org/artist/' # country
+ musicbrainz_artist_id) add_attribute(attributes, result, 'P17')
# headquarters
add_attribute(attributes, result, 'Q180')
musicbrainz_release_group_id = get_string(claims, 'P436') # PLACES
if musicbrainz_release_group_id is not None: # capital
add_url(urls, add_attribute(attributes, result, 'P36', trim=True)
'MusicBrainz', # head of state
'http://musicbrainz.org/release-group/' add_attribute(attributes, result, 'P35', trim=True)
+ musicbrainz_release_group_id) # head of government
add_attribute(attributes, result, 'P6', trim=True)
# type of government
add_attribute(attributes, result, 'P122')
# official language
add_attribute(attributes, result, 'P37')
# population
add_attribute(attributes, result, 'P1082', trim=True)
# area
add_attribute(attributes, result, 'P2046')
# currency
add_attribute(attributes, result, 'P38', trim=True)
# heigth (building)
add_attribute(attributes, result, 'P2048')
musicbrainz_label_id = get_string(claims, 'P966') # MEDIA
if musicbrainz_label_id is not None: # platform (videogames)
add_url(urls, add_attribute(attributes, result, 'P400')
'MusicBrainz', # author
'http://musicbrainz.org/label/' add_attribute(attributes, result, 'P50')
+ musicbrainz_label_id) # creator
add_attribute(attributes, result, 'P170')
# director
add_attribute(attributes, result, 'P57')
# performer
add_attribute(attributes, result, 'P175')
# developer
add_attribute(attributes, result, 'P178')
# producer
add_attribute(attributes, result, 'P162')
# manufacturer
add_attribute(attributes, result, 'P176')
# screenwriter
add_attribute(attributes, result, 'P58')
# production company
add_attribute(attributes, result, 'P272')
# record label
add_attribute(attributes, result, 'P264')
# publisher
add_attribute(attributes, result, 'P123')
# original network
add_attribute(attributes, result, 'P449')
# distributor
add_attribute(attributes, result, 'P750')
# composer
add_attribute(attributes, result, 'P86')
# publication date
add_attribute(attributes, result, 'P577', date=True)
# genre
add_attribute(attributes, result, 'P136')
# original language
add_attribute(attributes, result, 'P364')
# isbn
add_attribute(attributes, result, 'Q33057')
# software license
add_attribute(attributes, result, 'P275')
# programming language
add_attribute(attributes, result, 'P277')
# version
add_attribute(attributes, result, 'P348', trim=True)
# narrative location
add_attribute(attributes, result, 'P840')
# musicbrainz_area_id = get_string(claims, 'P982') # LANGUAGES
# P1407 MusicBrainz series ID # number of speakers
# P1004 MusicBrainz place ID add_attribute(attributes, result, 'P1098')
# P1330 MusicBrainz instrument ID # writing system
# P1407 MusicBrainz series ID add_attribute(attributes, result, 'P282')
# regulatory body
add_attribute(attributes, result, 'P1018')
# language code
add_attribute(attributes, result, 'P218')
postal_code = get_string(claims, 'P281', None) # OTHER
if postal_code is not None: # ceo
attributes.append({'label': 'Postal code(s)', 'value': postal_code}) add_attribute(attributes, result, 'P169', trim=True)
# founder
add_attribute(attributes, result, 'P112')
# legal form (company/organization)
add_attribute(attributes, result, 'P1454')
# operator
add_attribute(attributes, result, 'P137')
# crew members (tripulation)
add_attribute(attributes, result, 'P1029')
# taxon
add_attribute(attributes, result, 'P225')
# chemical formula
add_attribute(attributes, result, 'P274')
# winner (sports/contests)
add_attribute(attributes, result, 'P1346')
# number of deaths
add_attribute(attributes, result, 'P1120')
# currency code
add_attribute(attributes, result, 'P498')
date_of_birth = get_time(claims, 'P569', locale, None) image = add_image(result)
if date_of_birth is not None:
attributes.append({'label': 'Date of birth', 'value': date_of_birth})
date_of_death = get_time(claims, 'P570', locale, None)
if date_of_death is not None:
attributes.append({'label': 'Date of death', 'value': date_of_death})
if len(attributes) == 0 and len(urls) == 2 and len(description) == 0: if len(attributes) == 0 and len(urls) == 2 and len(description) == 0:
results.append({ results.append({
@ -190,6 +302,7 @@ def getDetail(jsonresponse, wikidata_id, language, locale):
'infobox': title, 'infobox': title,
'id': wikipedia_link, 'id': wikipedia_link,
'content': description, 'content': description,
'img_src': image,
'attributes': attributes, 'attributes': attributes,
'urls': urls 'urls': urls
}) })
@ -197,92 +310,151 @@ def getDetail(jsonresponse, wikidata_id, language, locale):
return results return results
def add_url(urls, title, url): # only returns first match
if url is not None: def add_image(result):
urls.append({'title': title, 'url': url}) # P15: route map, P242: locator map, P154: logo, P18: image, P242: map, P41: flag, P2716: collage, P2910: icon
return 1 property_ids = ['P15', 'P242', 'P154', 'P18', 'P242', 'P41', 'P2716', 'P2910']
for property_id in property_ids:
image = result.xpath(property_xpath.replace('{propertyid}', property_id))
if image:
image_name = image[0].xpath(value_xpath)
image_src = url_image.replace('{filename}', extract_text(image_name[0]))
return image_src
# setting trim will only returned high ranked rows OR the first row
def add_attribute(attributes, result, property_id, default_label=None, date=False, trim=False):
attribute = result.xpath(property_xpath.replace('{propertyid}', property_id))
if attribute:
if default_label:
label = default_label
else:
label = extract_text(attribute[0].xpath(label_xpath))
label = label[0].upper() + label[1:]
if date:
trim = True
# remove calendar name
calendar_name = attribute[0].xpath(calendar_name_xpath)
for calendar in calendar_name:
calendar.getparent().remove(calendar)
concat_values = ""
values = []
first_value = None
for row in attribute[0].xpath(property_row_xpath):
if not first_value or not trim or row.xpath(preferred_rank_xpath):
value = row.xpath(value_xpath)
if not value:
continue
value = extract_text(value)
# save first value in case no ranked row is found
if trim and not first_value:
first_value = value
else:
# to avoid duplicate values
if value not in values:
concat_values += value + ", "
values.append(value)
if trim and not values:
attributes.append({'label': label,
'value': first_value})
else:
attributes.append({'label': label,
'value': concat_values[:-2]})
# requires property_id unless it's a wiki link (defined in link_type)
def add_url(urls, result, property_id=None, default_label=None, url_prefix=None, results=None, link_type=None):
links = []
# wiki links don't have property in wikidata page
if link_type and 'wiki' in link_type:
links.append(get_wikilink(result, link_type))
else: else:
return 0 dom_element = result.xpath(property_xpath.replace('{propertyid}', property_id))
if dom_element:
dom_element = dom_element[0]
if not default_label:
label = extract_text(dom_element.xpath(label_xpath))
label = label[0].upper() + label[1:]
if link_type == 'geo':
links.append(get_geolink(dom_element))
elif link_type == 'imdb':
links.append(get_imdblink(dom_element, url_prefix))
else:
url_results = dom_element.xpath(url_xpath)
for link in url_results:
if link is not None:
if url_prefix:
link = url_prefix + extract_text(link)
else:
link = extract_text(link)
links.append(link)
# append urls
for url in links:
if url is not None:
urls.append({'title': default_label or label,
'url': url})
if results is not None:
results.append({'title': default_label or label,
'url': url})
def get_mainsnak(claims, propertyName): def get_imdblink(result, url_prefix):
propValue = claims.get(propertyName, {}) imdb_id = result.xpath(value_xpath)
if len(propValue) == 0: if imdb_id:
imdb_id = extract_text(imdb_id)
id_prefix = imdb_id[:2]
if id_prefix == 'tt':
url = url_prefix + 'title/' + imdb_id
elif id_prefix == 'nm':
url = url_prefix + 'name/' + imdb_id
elif id_prefix == 'ch':
url = url_prefix + 'character/' + imdb_id
elif id_prefix == 'co':
url = url_prefix + 'company/' + imdb_id
elif id_prefix == 'ev':
url = url_prefix + 'event/' + imdb_id
else:
url = None
return url
def get_geolink(result):
coordinates = result.xpath(value_xpath)
if not coordinates:
return None return None
coordinates = extract_text(coordinates[0])
latitude, longitude = coordinates.split(',')
propValue = propValue[0].get('mainsnak', None) # convert to decimal
return propValue lat = int(latitude[:latitude.find(u'°')])
if latitude.find('\'') >= 0:
lat += int(latitude[latitude.find(u'°') + 1:latitude.find('\'')] or 0) / 60.0
def get_string(claims, propertyName, defaultValue=None): if latitude.find('"') >= 0:
propValue = claims.get(propertyName, {}) lat += float(latitude[latitude.find('\'') + 1:latitude.find('"')] or 0) / 3600.0
if len(propValue) == 0: if latitude.find('S') >= 0:
return defaultValue lat *= -1
lon = int(longitude[:longitude.find(u'°')])
result = [] if longitude.find('\'') >= 0:
for e in propValue: lon += int(longitude[longitude.find(u'°') + 1:longitude.find('\'')] or 0) / 60.0
mainsnak = e.get('mainsnak', {}) if longitude.find('"') >= 0:
lon += float(longitude[longitude.find('\'') + 1:longitude.find('"')] or 0) / 3600.0
datavalue = mainsnak.get('datavalue', {}) if longitude.find('W') >= 0:
if datavalue is not None: lon *= -1
result.append(datavalue.get('value', ''))
if len(result) == 0:
return defaultValue
else:
# TODO handle multiple urls
return result[0]
def get_time(claims, propertyName, locale, defaultValue=None):
propValue = claims.get(propertyName, {})
if len(propValue) == 0:
return defaultValue
result = []
for e in propValue:
mainsnak = e.get('mainsnak', {})
datavalue = mainsnak.get('datavalue', {})
if datavalue is not None:
value = datavalue.get('value', '')
result.append(value.get('time', ''))
if len(result) == 0:
date_string = defaultValue
else:
date_string = ', '.join(result)
try:
parsed_date = datetime.strptime(date_string, "+%Y-%m-%dT%H:%M:%SZ")
except:
if date_string.startswith('-'):
return date_string.split('T')[0]
try:
parsed_date = dateutil_parse(date_string, fuzzy=False, default=False)
except:
logger.debug('could not parse date %s', date_string)
return date_string.split('T')[0]
return format_date_by_locale(parsed_date, locale)
def get_geolink(claims, propertyName, defaultValue=''):
mainsnak = get_mainsnak(claims, propertyName)
if mainsnak is None:
return defaultValue
datatype = mainsnak.get('datatype', '')
datavalue = mainsnak.get('datavalue', {})
if datatype != 'globe-coordinate':
return defaultValue
value = datavalue.get('value', {})
precision = value.get('precision', 0.0002)
# TODO: get precision
precision = 0.0002
# there is no zoom information, deduce from precision (error prone) # there is no zoom information, deduce from precision (error prone)
# samples : # samples :
# 13 --> 5 # 13 --> 5
@ -298,26 +470,20 @@ def get_geolink(claims, propertyName, defaultValue=''):
zoom = int(15 - precision * 8.8322 + precision * precision * 0.625447) zoom = int(15 - precision * 8.8322 + precision * precision * 0.625447)
url = url_map\ url = url_map\
.replace('{latitude}', str(value.get('latitude', 0)))\ .replace('{latitude}', str(lat))\
.replace('{longitude}', str(value.get('longitude', 0)))\ .replace('{longitude}', str(lon))\
.replace('{zoom}', str(zoom)) .replace('{zoom}', str(zoom))
return url return url
def get_wikilink(result, wikiid): def get_wikilink(result, wikiid):
url = result.get('sitelinks', {}).get(wikiid, {}).get('url', None) url = result.xpath(wikilink_xpath.replace('{wikiid}', wikiid))
if url is None: if not url:
return url return None
elif url.startswith('http://'): url = url[0]
if url.startswith('http://'):
url = url.replace('http://', 'https://') url = url.replace('http://', 'https://')
elif url.startswith('//'): elif url.startswith('//'):
url = 'https:' + url url = 'https:' + url
return url return url
def get_wiki_firstlanguage(result, wikipatternid):
for k in result.get('sitelinks', {}).keys():
if k.endswith(wikipatternid) and len(k) == (2 + len(wikipatternid)):
return k[0:2]
return None

View File

@ -99,9 +99,8 @@ def response(resp):
return [] return []
# link to wikipedia article # link to wikipedia article
# parenthesis are not quoted to make infobox mergeable with wikidata's
wikipedia_link = url_lang(resp.search_params['language']) \ wikipedia_link = url_lang(resp.search_params['language']) \
+ 'wiki/' + quote(title.replace(' ', '_').encode('utf8')).replace('%28', '(').replace('%29', ')') + 'wiki/' + quote(title.replace(' ', '_').encode('utf8'))
results.append({'url': wikipedia_link, 'title': title}) results.append({'url': wikipedia_link, 'title': title})

View File

@ -8,11 +8,9 @@
# @stable no # @stable no
# @parse url, infobox # @parse url, infobox
from cgi import escape
from json import loads from json import loads
from time import time from time import time
from urllib import urlencode from urllib import urlencode
from lxml.etree import XML
from searx.poolrequests import get as http_get from searx.poolrequests import get as http_get
@ -36,7 +34,7 @@ search_url = url + 'input/json.jsp'\
referer_url = url + 'input/?{query}' referer_url = url + 'input/?{query}'
token = {'value': '', token = {'value': '',
'last_updated': None} 'last_updated': 0}
# pods to display as image in infobox # pods to display as image in infobox
# this pods do return a plaintext, but they look better and are more useful as images # this pods do return a plaintext, but they look better and are more useful as images

View File

@ -41,7 +41,7 @@ def response(resp):
results = [] results = []
dom = html.fromstring(resp.text) dom = html.fromstring(resp.text)
regex = re.compile('3\.jpg.*$') regex = re.compile(r'3\.jpg.*$')
# parse results # parse results
for result in dom.xpath('//div[@class="photo"]'): for result in dom.xpath('//div[@class="photo"]'):

View File

@ -87,7 +87,7 @@ def request(query, params):
fp = {'query': query} fp = {'query': query}
if paging and search_url.find('{pageno}') >= 0: if paging and search_url.find('{pageno}') >= 0:
fp['pageno'] = (params['pageno'] + first_page_num - 1) * page_size fp['pageno'] = (params['pageno'] - 1) * page_size + first_page_num
params['url'] = search_url.format(**fp) params['url'] = search_url.format(**fp)
params['query'] = query params['query'] = query

View File

@ -20,10 +20,12 @@ from searx.engines.xpath import extract_text, extract_url
categories = ['general'] categories = ['general']
paging = True paging = True
language_support = True language_support = True
time_range_support = True
# search-url # search-url
base_url = 'https://search.yahoo.com/' base_url = 'https://search.yahoo.com/'
search_url = 'search?{query}&b={offset}&fl=1&vl=lang_{lang}' search_url = 'search?{query}&b={offset}&fl=1&vl=lang_{lang}'
search_url_with_time = 'search?{query}&b={offset}&fl=1&vl=lang_{lang}&age={age}&btf={btf}&fr2=time'
# specific xpath variables # specific xpath variables
results_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' Sr ')]" results_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' Sr ')]"
@ -32,6 +34,10 @@ title_xpath = './/h3/a'
content_xpath = './/div[@class="compText aAbs"]' content_xpath = './/div[@class="compText aAbs"]'
suggestion_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' AlsoTry ')]//a" suggestion_xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' AlsoTry ')]//a"
time_range_dict = {'day': ['1d', 'd'],
'week': ['1w', 'w'],
'month': ['1m', 'm']}
# remove yahoo-specific tracking-url # remove yahoo-specific tracking-url
def parse_url(url_string): def parse_url(url_string):
@ -51,18 +57,30 @@ def parse_url(url_string):
return unquote(url_string[start:end]) return unquote(url_string[start:end])
def _get_url(query, offset, language, time_range):
if time_range in time_range_dict:
return base_url + search_url_with_time.format(offset=offset,
query=urlencode({'p': query}),
lang=language,
age=time_range_dict[time_range][0],
btf=time_range_dict[time_range][1])
return base_url + search_url.format(offset=offset,
query=urlencode({'p': query}),
lang=language)
def _get_language(params):
if params['language'] == 'all':
return 'en'
return params['language'].split('_')[0]
# do search-request # do search-request
def request(query, params): def request(query, params):
offset = (params['pageno'] - 1) * 10 + 1 offset = (params['pageno'] - 1) * 10 + 1
language = _get_language(params)
if params['language'] == 'all': params['url'] = _get_url(query, offset, language, params['time_range'])
language = 'en'
else:
language = params['language'].split('_')[0]
params['url'] = base_url + search_url.format(offset=offset,
query=urlencode({'p': query}),
lang=language)
# TODO required? # TODO required?
params['cookies']['sB'] = 'fl=1&vl=lang_{lang}&sh=1&rw=new&v=1'\ params['cookies']['sB'] = 'fl=1&vl=lang_{lang}&sh=1&rw=new&v=1'\

View File

@ -55,7 +55,7 @@ def request(query, params):
def sanitize_url(url): def sanitize_url(url):
if ".yahoo.com/" in url: if ".yahoo.com/" in url:
return re.sub(u"\;\_ylt\=.+$", "", url) return re.sub(u"\\;\\_ylt\\=.+$", "", url)
else: else:
return url return url

View File

@ -19,15 +19,17 @@ from searx import logger
logger = logger.getChild('plugins') logger = logger.getChild('plugins')
from searx.plugins import (https_rewrite, from searx.plugins import (doai_rewrite,
https_rewrite,
infinite_scroll,
open_results_on_new_tab, open_results_on_new_tab,
self_info, self_info,
search_on_category_select, search_on_category_select,
tracker_url_remover, tracker_url_remover,
vim_hotkeys) vim_hotkeys)
required_attrs = (('name', str), required_attrs = (('name', (str, unicode)),
('description', str), ('description', (str, unicode)),
('default_on', bool)) ('default_on', bool))
optional_attrs = (('js_dependencies', tuple), optional_attrs = (('js_dependencies', tuple),
@ -73,7 +75,9 @@ class PluginStore():
plugins = PluginStore() plugins = PluginStore()
plugins.register(doai_rewrite)
plugins.register(https_rewrite) plugins.register(https_rewrite)
plugins.register(infinite_scroll)
plugins.register(open_results_on_new_tab) plugins.register(open_results_on_new_tab)
plugins.register(self_info) plugins.register(self_info)
plugins.register(search_on_category_select) plugins.register(search_on_category_select)

View File

@ -0,0 +1,31 @@
from flask_babel import gettext
import re
from urlparse import urlparse, parse_qsl
regex = re.compile(r'10\.\d{4,9}/[^\s]+')
name = gettext('DOAI rewrite')
description = gettext('Avoid paywalls by redirecting to open-access versions of publications when available')
default_on = False
def extract_doi(url):
match = regex.search(url.path)
if match:
return match.group(0)
for _, v in parse_qsl(url.query):
match = regex.search(v)
if match:
return match.group(0)
return None
def on_result(request, ctx):
doi = extract_doi(ctx['result']['parsed_url'])
if doi and len(doi) < 50:
for suffix in ('/', '.pdf', '/full', '/meta', '/abstract'):
if doi.endswith(suffix):
doi = doi[:-len(suffix)]
ctx['result']['url'] = 'http://doai.io/' + doi
ctx['result']['parsed_url'] = urlparse(ctx['result']['url'])
return True

View File

@ -21,7 +21,7 @@ from lxml import etree
from os import listdir, environ from os import listdir, environ
from os.path import isfile, isdir, join from os.path import isfile, isdir, join
from searx.plugins import logger from searx.plugins import logger
from flask.ext.babel import gettext from flask_babel import gettext
from searx import searx_dir from searx import searx_dir
@ -87,7 +87,7 @@ def load_single_https_ruleset(rules_path):
# convert host-rule to valid regex # convert host-rule to valid regex
host = ruleset.attrib.get('host')\ host = ruleset.attrib.get('host')\
.replace('.', '\.').replace('*', '.*') .replace('.', r'\.').replace('*', '.*')
# append to host list # append to host list
hosts.append(host) hosts.append(host)

View File

@ -0,0 +1,8 @@
from flask_babel import gettext
name = gettext('Infinite scroll')
description = gettext('Automatically load next page when scrolling to bottom of current page')
default_on = False
js_dependencies = ('plugins/js/infinite_scroll.js',)
css_dependencies = ('plugins/css/infinite_scroll.css',)

View File

@ -14,7 +14,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
(C) 2016 by Adam Tauber, <asciimoo@gmail.com> (C) 2016 by Adam Tauber, <asciimoo@gmail.com>
''' '''
from flask.ext.babel import gettext from flask_babel import gettext
name = gettext('Open result links on new browser tabs') name = gettext('Open result links on new browser tabs')
description = gettext('Results are opened in the same window by default. ' description = gettext('Results are opened in the same window by default. '
'This plugin overwrites the default behaviour to open links on new tabs/windows. ' 'This plugin overwrites the default behaviour to open links on new tabs/windows. '

View File

@ -14,7 +14,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
(C) 2015 by Adam Tauber, <asciimoo@gmail.com> (C) 2015 by Adam Tauber, <asciimoo@gmail.com>
''' '''
from flask.ext.babel import gettext from flask_babel import gettext
name = gettext('Search on category select') name = gettext('Search on category select')
description = gettext('Perform search immediately if a category selected. ' description = gettext('Perform search immediately if a category selected. '
'Disable to select multiple categories. (JavaScript required)') 'Disable to select multiple categories. (JavaScript required)')

View File

@ -14,7 +14,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
(C) 2015 by Adam Tauber, <asciimoo@gmail.com> (C) 2015 by Adam Tauber, <asciimoo@gmail.com>
''' '''
from flask.ext.babel import gettext from flask_babel import gettext
import re import re
name = "Self Informations" name = "Self Informations"
description = gettext('Displays your IP if the query is "ip" and your user agent if the query contains "user agent".') description = gettext('Displays your IP if the query is "ip" and your user agent if the query contains "user agent".')
@ -29,6 +29,8 @@ p = re.compile('.*user[ -]agent.*', re.IGNORECASE)
# request: flask request object # request: flask request object
# ctx: the whole local context of the pre search hook # ctx: the whole local context of the pre search hook
def post_search(request, ctx): def post_search(request, ctx):
if ctx['search'].pageno > 1:
return True
if ctx['search'].query == 'ip': if ctx['search'].query == 'ip':
x_forwarded_for = request.headers.getlist("X-Forwarded-For") x_forwarded_for = request.headers.getlist("X-Forwarded-For")
if x_forwarded_for: if x_forwarded_for:

View File

@ -15,7 +15,7 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
(C) 2015 by Adam Tauber, <asciimoo@gmail.com> (C) 2015 by Adam Tauber, <asciimoo@gmail.com>
''' '''
from flask.ext.babel import gettext from flask_babel import gettext
import re import re
from urlparse import urlunparse from urlparse import urlunparse

View File

@ -1,4 +1,4 @@
from flask.ext.babel import gettext from flask_babel import gettext
name = gettext('Vim-like hotkeys') name = gettext('Vim-like hotkeys')
description = gettext('Navigate search results with Vim-like hotkeys ' description = gettext('Navigate search results with Vim-like hotkeys '

View File

@ -166,6 +166,7 @@ class SwitchableSetting(Setting):
class EnginesSetting(SwitchableSetting): class EnginesSetting(SwitchableSetting):
def _post_init(self): def _post_init(self):
super(EnginesSetting, self)._post_init() super(EnginesSetting, self)._post_init()
transformed_choices = [] transformed_choices = []
@ -191,6 +192,7 @@ class EnginesSetting(SwitchableSetting):
class PluginsSetting(SwitchableSetting): class PluginsSetting(SwitchableSetting):
def _post_init(self): def _post_init(self):
super(PluginsSetting, self)._post_init() super(PluginsSetting, self)._post_init()
transformed_choices = [] transformed_choices = []
@ -225,7 +227,8 @@ class Preferences(object):
'safesearch': MapSetting(settings['search']['safe_search'], map={'0': 0, 'safesearch': MapSetting(settings['search']['safe_search'], map={'0': 0,
'1': 1, '1': 1,
'2': 2}), '2': 2}),
'theme': EnumStringSetting(settings['ui']['default_theme'], choices=themes)} 'theme': EnumStringSetting(settings['ui']['default_theme'], choices=themes),
'results_on_new_tab': MapSetting(False, map={'0': False, '1': True})}
self.engines = EnginesSetting('engines', choices=engines) self.engines = EnginesSetting('engines', choices=engines)
self.plugins = PluginsSetting('plugins', choices=plugins) self.plugins = PluginsSetting('plugins', choices=plugins)

View File

@ -5,7 +5,7 @@ from threading import RLock
from urlparse import urlparse, unquote from urlparse import urlparse, unquote
from searx.engines import engines from searx.engines import engines
CONTENT_LEN_IGNORED_CHARS_REGEX = re.compile('[,;:!?\./\\\\ ()-_]', re.M | re.U) CONTENT_LEN_IGNORED_CHARS_REGEX = re.compile(r'[,;:!?\./\\\\ ()-_]', re.M | re.U)
WHITESPACE_REGEX = re.compile('( |\t|\n)+', re.M | re.U) WHITESPACE_REGEX = re.compile('( |\t|\n)+', re.M | re.U)
@ -18,7 +18,17 @@ def result_content_len(content):
def compare_urls(url_a, url_b): def compare_urls(url_a, url_b):
if url_a.netloc != url_b.netloc or url_a.query != url_b.query: # ignore www. in comparison
if url_a.netloc.startswith('www.'):
host_a = url_a.netloc.replace('www.', '', 1)
else:
host_a = url_a.netloc
if url_b.netloc.startswith('www.'):
host_b = url_b.netloc.replace('www.', '', 1)
else:
host_b = url_b.netloc
if host_a != host_b or url_a.query != url_b.query or url_a.fragment != url_b.fragment:
return False return False
# remove / from the end of the url if required # remove / from the end of the url if required
@ -33,25 +43,42 @@ def compare_urls(url_a, url_b):
def merge_two_infoboxes(infobox1, infobox2): def merge_two_infoboxes(infobox1, infobox2):
# get engines weights
if hasattr(engines[infobox1['engine']], 'weight'):
weight1 = engines[infobox1['engine']].weight
else:
weight1 = 1
if hasattr(engines[infobox2['engine']], 'weight'):
weight2 = engines[infobox2['engine']].weight
else:
weight2 = 1
if weight2 > weight1:
infobox1['engine'] = infobox2['engine']
if 'urls' in infobox2: if 'urls' in infobox2:
urls1 = infobox1.get('urls', None) urls1 = infobox1.get('urls', None)
if urls1 is None: if urls1 is None:
urls1 = [] urls1 = []
infobox1['urls'] = urls1
urlSet = set() for url2 in infobox2.get('urls', []):
for url in infobox1.get('urls', []): unique_url = True
urlSet.add(url.get('url', None)) for url1 in infobox1.get('urls', []):
if compare_urls(urlparse(url1.get('url', '')), urlparse(url2.get('url', ''))):
unique_url = False
break
if unique_url:
urls1.append(url2)
for url in infobox2.get('urls', []): infobox1['urls'] = urls1
if url.get('url', None) not in urlSet:
urls1.append(url)
if 'img_src' in infobox2: if 'img_src' in infobox2:
img1 = infobox1.get('img_src', None) img1 = infobox1.get('img_src', None)
img2 = infobox2.get('img_src') img2 = infobox2.get('img_src')
if img1 is None: if img1 is None:
infobox1['img_src'] = img2 infobox1['img_src'] = img2
elif weight2 > weight1:
infobox1['img_src'] = img2
if 'attributes' in infobox2: if 'attributes' in infobox2:
attributes1 = infobox1.get('attributes', None) attributes1 = infobox1.get('attributes', None)
@ -65,7 +92,8 @@ def merge_two_infoboxes(infobox1, infobox2):
attributeSet.add(attribute.get('label', None)) attributeSet.add(attribute.get('label', None))
for attribute in infobox2.get('attributes', []): for attribute in infobox2.get('attributes', []):
attributes1.append(attribute) if attribute.get('label', None) not in attributeSet:
attributes1.append(attribute)
if 'content' in infobox2: if 'content' in infobox2:
content1 = infobox1.get('content', None) content1 = infobox1.get('content', None)
@ -91,15 +119,15 @@ def result_score(result):
class ResultContainer(object): class ResultContainer(object):
"""docstring for ResultContainer""" """docstring for ResultContainer"""
def __init__(self): def __init__(self):
super(ResultContainer, self).__init__() super(ResultContainer, self).__init__()
self.results = defaultdict(list) self.results = defaultdict(list)
self._merged_results = [] self._merged_results = []
self.infoboxes = [] self.infoboxes = []
self._infobox_ids = {}
self.suggestions = set() self.suggestions = set()
self.answers = set() self.answers = set()
self.number_of_results = 0 self._number_of_results = []
def extend(self, engine_name, results): def extend(self, engine_name, results):
for result in list(results): for result in list(results):
@ -113,7 +141,7 @@ class ResultContainer(object):
self._merge_infobox(result) self._merge_infobox(result)
results.remove(result) results.remove(result)
elif 'number_of_results' in result: elif 'number_of_results' in result:
self.number_of_results = max(self.number_of_results, result['number_of_results']) self._number_of_results.append(result['number_of_results'])
results.remove(result) results.remove(result)
with RLock(): with RLock():
@ -137,14 +165,13 @@ class ResultContainer(object):
add_infobox = True add_infobox = True
infobox_id = infobox.get('id', None) infobox_id = infobox.get('id', None)
if infobox_id is not None: if infobox_id is not None:
existingIndex = self._infobox_ids.get(infobox_id, None) for existingIndex in self.infoboxes:
if existingIndex is not None: if compare_urls(urlparse(existingIndex.get('id', '')), urlparse(infobox_id)):
merge_two_infoboxes(self.infoboxes[existingIndex], infobox) merge_two_infoboxes(existingIndex, infobox)
add_infobox = False add_infobox = False
if add_infobox: if add_infobox:
self.infoboxes.append(infobox) self.infoboxes.append(infobox)
self._infobox_ids[infobox_id] = len(self.infoboxes) - 1
def _merge_result(self, result, position): def _merge_result(self, result, position):
result['parsed_url'] = urlparse(result['url']) result['parsed_url'] = urlparse(result['url'])
@ -154,11 +181,6 @@ class ResultContainer(object):
result['parsed_url'] = result['parsed_url']._replace(scheme="http") result['parsed_url'] = result['parsed_url']._replace(scheme="http")
result['url'] = result['parsed_url'].geturl() result['url'] = result['parsed_url'].geturl()
result['host'] = result['parsed_url'].netloc
if result['host'].startswith('www.'):
result['host'] = result['host'].replace('www.', '', 1)
result['engines'] = [result['engine']] result['engines'] = [result['engine']]
# strip multiple spaces and cariage returns from content # strip multiple spaces and cariage returns from content
@ -252,3 +274,9 @@ class ResultContainer(object):
def results_length(self): def results_length(self):
return len(self._merged_results) return len(self._merged_results)
def results_number(self):
resultnum_sum = sum(self._number_of_results)
if not resultnum_sum or not self._number_of_results:
return 0
return resultnum_sum / len(self._number_of_results)

View File

@ -15,14 +15,15 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
(C) 2013- by Adam Tauber, <asciimoo@gmail.com> (C) 2013- by Adam Tauber, <asciimoo@gmail.com>
''' '''
import gc
import threading import threading
import searx.poolrequests as requests_lib from thread import start_new_thread
from time import time from time import time
from searx import settings from uuid import uuid4
import searx.poolrequests as requests_lib
from searx.engines import ( from searx.engines import (
categories, engines categories, engines
) )
from searx.languages import language_codes
from searx.utils import gen_useragent from searx.utils import gen_useragent
from searx.query import Query from searx.query import Query
from searx.results import ResultContainer from searx.results import ResultContainer
@ -56,19 +57,20 @@ def search_request_wrapper(fn, url, engine_name, **kwargs):
def threaded_requests(requests): def threaded_requests(requests):
timeout_limit = max(r[2]['timeout'] for r in requests) timeout_limit = max(r[2]['timeout'] for r in requests)
search_start = time() search_start = time()
search_id = uuid4().__str__()
for fn, url, request_args, engine_name in requests: for fn, url, request_args, engine_name in requests:
request_args['timeout'] = timeout_limit request_args['timeout'] = timeout_limit
th = threading.Thread( th = threading.Thread(
target=search_request_wrapper, target=search_request_wrapper,
args=(fn, url, engine_name), args=(fn, url, engine_name),
kwargs=request_args, kwargs=request_args,
name='search_request', name=search_id,
) )
th._engine_name = engine_name th._engine_name = engine_name
th.start() th.start()
for th in threading.enumerate(): for th in threading.enumerate():
if th.name == 'search_request': if th.name == search_id:
remaining_time = max(0.0, timeout_limit - (time() - search_start)) remaining_time = max(0.0, timeout_limit - (time() - search_start))
th.join(remaining_time) th.join(remaining_time)
if th.isAlive(): if th.isAlive():
@ -138,6 +140,8 @@ class Search(object):
self.paging = False self.paging = False
self.pageno = 1 self.pageno = 1
self.lang = 'all' self.lang = 'all'
self.time_range = None
self.is_advanced = None
# set blocked engines # set blocked engines
self.disabled_engines = request.preferences.engines.get_disabled() self.disabled_engines = request.preferences.engines.get_disabled()
@ -178,9 +182,10 @@ class Search(object):
if len(query_obj.languages): if len(query_obj.languages):
self.lang = query_obj.languages[-1] self.lang = query_obj.languages[-1]
self.engines = query_obj.engines self.time_range = self.request_data.get('time_range')
self.is_advanced = self.request_data.get('advanced_search')
self.categories = [] self.engines = query_obj.engines
# if engines are calculated from query, # if engines are calculated from query,
# set categories by using that informations # set categories by using that informations
@ -279,6 +284,9 @@ class Search(object):
if self.lang != 'all' and not engine.language_support: if self.lang != 'all' and not engine.language_support:
continue continue
if self.time_range and not engine.time_range_support:
continue
# set default request parameters # set default request parameters
request_params = default_request_params() request_params = default_request_params()
request_params['headers']['User-Agent'] = user_agent request_params['headers']['User-Agent'] = user_agent
@ -293,6 +301,8 @@ class Search(object):
# 0 = None, 1 = Moderate, 2 = Strict # 0 = None, 1 = Moderate, 2 = Strict
request_params['safesearch'] = request.preferences.get_value('safesearch') request_params['safesearch'] = request.preferences.get_value('safesearch')
request_params['time_range'] = self.time_range
request_params['advanced_search'] = self.is_advanced
# update request parameters dependent on # update request parameters dependent on
# search-engine (contained in engines folder) # search-engine (contained in engines folder)
@ -339,6 +349,7 @@ class Search(object):
return self return self
# send all search-request # send all search-request
threaded_requests(requests) threaded_requests(requests)
start_new_thread(gc.collect, tuple())
# return results, suggestions, answers and infoboxes # return results, suggestions, answers and infoboxes
return self return self

View File

@ -25,7 +25,7 @@ outgoing: # communication with search engines
pool_maxsize : 10 # Number of simultaneous requests by host pool_maxsize : 10 # Number of simultaneous requests by host
# uncomment below section if you want to use a proxy # uncomment below section if you want to use a proxy
# see http://docs.python-requests.org/en/latest/user/advanced/#proxies # see http://docs.python-requests.org/en/latest/user/advanced/#proxies
# SOCKS proxies are not supported : see https://github.com/kennethreitz/requests/pull/478 # SOCKS proxies are also supported: see http://docs.python-requests.org/en/master/user/advanced/#socks
# proxies : # proxies :
# http : http://127.0.0.1:8080 # http : http://127.0.0.1:8080
# https: http://127.0.0.1:8080 # https: http://127.0.0.1:8080
@ -84,9 +84,15 @@ engines:
disabled : True disabled : True
shortcut : bb shortcut : bb
- name : btdigg - name : crossref
engine : btdigg engine : json_engine
shortcut : bt paging : True
search_url : http://search.crossref.org/dois?q={query}&page={pageno}
url_query : doi
title_query : title
content_query : fullCitation
categories : science
shortcut : cr
- name : currency - name : currency
engine : currency_convert engine : currency_convert
@ -105,6 +111,13 @@ engines:
- name : ddg definitions - name : ddg definitions
engine : duckduckgo_definitions engine : duckduckgo_definitions
shortcut : ddd shortcut : ddd
weight : 2
disabled : True
- name : digbt
engine : digbt
shortcut : dbt
timeout : 6.0
disabled : True disabled : True
- name : digg - name : digg
@ -127,10 +140,12 @@ engines:
- name : wikidata - name : wikidata
engine : wikidata engine : wikidata
shortcut : wd shortcut : wd
weight : 2
- name : duckduckgo - name : duckduckgo
engine : duckduckgo engine : duckduckgo
shortcut : ddg shortcut : ddg
disabled : True
# api-key required: http://www.faroo.com/hp/api/api.html#key # api-key required: http://www.faroo.com/hp/api/api.html#key
# - name : faroo # - name : faroo
@ -200,6 +215,20 @@ engines:
engine : google_news engine : google_news
shortcut : gon shortcut : gon
- name : google scholar
engine : xpath
paging : True
search_url : https://scholar.google.com/scholar?start={pageno}&q={query}&hl=en&as_sdt=0,5&as_vis=1
results_xpath : //div[@class="gs_r"]/div[@class="gs_ri"]
url_xpath : .//h3/a/@href
title_xpath : .//h3/a
content_xpath : .//div[@class="gs_rs"]
suggestion_xpath : //div[@id="gs_qsuggest"]/ul/li
page_size : 10
first_page_num : 0
categories : science
shortcut : gos
- name : google play apps - name : google play apps
engine : xpath engine : xpath
search_url : https://play.google.com/store/search?q={query}&c=apps search_url : https://play.google.com/store/search?q={query}&c=apps
@ -254,6 +283,37 @@ engines:
disabled : True disabled : True
shortcut : habr shortcut : habr
- name : hoogle
engine : json_engine
paging : True
search_url : https://www.haskell.org/hoogle/?mode=json&hoogle={query}&start={pageno}
results_query : results
url_query : location
title_query : self
content_query : docs
page_size : 20
categories : it
shortcut : ho
- name : ina
engine : ina
shortcut : in
timeout : 6.0
disabled : True
- name : microsoft academic
engine : json_engine
paging : True
search_url : https://academic.microsoft.com/api/search/GetEntityResults?query=%40{query}%40&filters=&offset={pageno}&limit=8&correlationId=undefined
results_query : results
url_query : u
title_query : dn
content_query : d
page_size : 8
first_page_num : 0
categories : science
shortcut : ma
- name : mixcloud - name : mixcloud
engine : mixcloud engine : mixcloud
shortcut : mc shortcut : mc
@ -267,6 +327,18 @@ engines:
engine : openstreetmap engine : openstreetmap
shortcut : osm shortcut : osm
- name : openrepos
engine : xpath
paging : True
search_url : https://openrepos.net/search/node/{query}?page={pageno}
url_xpath : //li[@class="search-result"]//h3[@class="title"]/a/@href
title_xpath : //li[@class="search-result"]//h3[@class="title"]/a
content_xpath : //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"]
categories : files
timeout : 4.0
disabled : True
shortcut : or
- name : photon - name : photon
engine : photon engine : photon
shortcut : ph shortcut : ph
@ -274,7 +346,8 @@ engines:
- name : piratebay - name : piratebay
engine : piratebay engine : piratebay
shortcut : tpb shortcut : tpb
disabled : True url: https://pirateproxy.red/
timeout : 3.0
- name : qwant - name : qwant
engine : qwant engine : qwant
@ -304,9 +377,10 @@ engines:
timeout : 10.0 timeout : 10.0
disabled : True disabled : True
- name : kickass - name : scanr_structures
engine : kickass shortcut: scs
shortcut : ka engine : scanr_structures
disabled : True
- name : soundcloud - name : soundcloud
engine : soundcloud engine : soundcloud
@ -361,11 +435,6 @@ engines:
timeout : 6.0 timeout : 6.0
disabled : True disabled : True
- name : torrentz
engine : torrentz
timeout : 5.0
shortcut : to
- name : twitter - name : twitter
engine : twitter engine : twitter
shortcut : tw shortcut : tw
@ -426,6 +495,19 @@ engines:
timeout: 6.0 timeout: 6.0
categories : science categories : science
- name : dictzone
engine : dictzone
shortcut : dc
- name : mymemory translated
engine : translated
shortcut : tl
timeout : 5.0
disabled : True
# You can use without an API key, but you are limited to 1000 words/day
# See : http://mymemory.translated.net/doc/usagelimits.php
# api_key : ''
#The blekko technology and team have joined IBM Watson! -> https://blekko.com/ #The blekko technology and team have joined IBM Watson! -> https://blekko.com/
# - name : blekko images # - name : blekko images
# engine : blekko_images # engine : blekko_images

View File

@ -0,0 +1,16 @@
@keyframes rotate-forever {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
}
.loading-spinner {
animation-duration: 0.75s;
animation-iteration-count: infinite;
animation-name: rotate-forever;
animation-timing-function: linear;
height: 30px;
width: 30px;
border: 8px solid #666;
border-right-color: transparent;
border-radius: 50% !important;
margin: 0 auto;
}

View File

@ -0,0 +1,18 @@
$(document).ready(function() {
var win = $(window);
win.scroll(function() {
if ($(document).height() - win.height() == win.scrollTop()) {
var formData = $('#pagination form:last').serialize();
if (formData) {
$('#pagination').html('<div class="loading-spinner"></div>');
$.post('/', formData, function (data) {
var body = $(data);
$('#pagination').remove();
$('#main_results').append('<hr/>');
$('#main_results').append(body.find('.result'));
$('#main_results').append(body.find('#pagination'));
});
}
}
});
});

View File

@ -4,13 +4,16 @@ $(document).ready(function() {
$('#categories input[type="checkbox"]').each(function(i, checkbox) { $('#categories input[type="checkbox"]').each(function(i, checkbox) {
$(checkbox).prop('checked', false); $(checkbox).prop('checked', false);
}); });
$('#categories label').removeClass('btn-primary').removeClass('active').addClass('btn-default'); $(document.getElementById($(this).attr("for"))).prop('checked', true);
$(this).removeClass('btn-default').addClass('btn-primary').addClass('active');
$($(this).children()[0]).prop('checked', 'checked');
if($('#q').val()) { if($('#q').val()) {
$('#search_form').submit(); $('#search_form').submit();
} }
return false; return false;
}); });
$('#time-range > option').click(function(e) {
if($('#q').val()) {
$('#search_form').submit();
}
});
} }
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -122,17 +122,13 @@ $(document).ready(function(){
var map = L.map(leaflet_target); var map = L.map(leaflet_target);
// create the tile layer with correct attribution // create the tile layer with correct attribution
var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmMapnikAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'; var osmMapnikAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib}); var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib});
var osmMapquestUrl='http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg'; var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png';
var osmMapquestAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors | Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">'; var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
var osmMapquest = new L.TileLayer(osmMapquestUrl, {minZoom: 1, maxZoom: 18, subdomains: '1234', attribution: osmMapquestAttrib}); var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
var osmMapquestOpenAerialUrl='http://otile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg';
var osmMapquestOpenAerialAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors | Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="https://developer.mapquest.com/content/osm/mq_logo.png"> | Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency';
var osmMapquestOpenAerial = new L.TileLayer(osmMapquestOpenAerialUrl, {minZoom: 1, maxZoom: 11, subdomains: '1234', attribution: osmMapquestOpenAerialAttrib});
// init map view // init map view
if(map_bounds) { if(map_bounds) {
@ -149,12 +145,11 @@ $(document).ready(function(){
map.setView(new L.LatLng(map_lat, map_lon),8); map.setView(new L.LatLng(map_lat, map_lon),8);
} }
map.addLayer(osmMapquest); map.addLayer(osmMapnik);
var baseLayers = { var baseLayers = {
"OSM Mapnik": osmMapnik, "OSM Mapnik": osmMapnik/*,
"MapQuest": osmMapquest/*, "OSM Wikimedia": osmWikimedia*/
"MapQuest Open Aerial": osmMapquestOpenAerial*/
}; };
L.control.layers(baseLayers).addTo(map); L.control.layers(baseLayers).addTo(map);

View File

@ -0,0 +1,72 @@
#advanced-search-container {
display: none;
text-align: left;
margin-bottom: 1rem;
clear: both;
label, .input-group-addon {
font-size: 1.2rem;
font-weight:normal;
background-color: white;
border: @mild-gray 1px solid;
border-right: none;
color: @dark-gray;
padding-bottom: 0.4rem;
padding-right: 0.7rem;
padding-left: 0.7rem;
}
label:last-child, .input-group-addon:last-child {
border-right: @mild-gray 1px solid;
}
input[type="radio"] {
display: none;
}
input[type="radio"]:checked + label{
color: @black;
font-weight: bold;
border-bottom: @light-green 5px solid;
}
select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
font-size: 1.2rem;
font-weight:normal;
background-color: white;
border: @mild-gray 1px solid;
color: @dark-gray;
padding-bottom: 0.4rem;
padding-top: 0.4rem;
padding-left: 1rem;
padding-right: 5rem;
margin-right: 0.5rem;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZ
cwAABFkAAARZAVnbJUkAAAAHdElNRQfgBxgLDwB20OFsAAAAbElEQVQY073OsQ3CMAAEwJMYwJGn
sAehpoXJItltBkmcdZBYgIIiQoLglnz3ui+eP+bk5uneteTMZJa6OJuIqvYzSJoqwqBq8gdmTTW8
6/dghxAUq4xsVYT9laBYXCw93Aajh7GPEF23t4fkBYevGFTANkPRAAAAJXRFWHRkYXRlOmNyZWF0
ZQAyMDE2LTA3LTI0VDExOjU1OjU4KzAyOjAwRFqFOQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0w
Ny0yNFQxMToxNTowMCswMjowMP7RDgQAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb
7jwaAAAAAElFTkSuQmCC) 96% no-repeat;
}
}
#check-advanced {
display: none;
}
#check-advanced:checked ~ #advanced-search-container {
display: block;
}
.advanced {
padding: 0;
margin-top: 0.3rem;
text-align: right;
label, select {
cursor: pointer;
}
}

View File

@ -39,7 +39,7 @@
padding: 0 30px; padding: 0 30px;
margin: 0; margin: 0;
} }
z-index: 1; z-index: 10;
} }
// Hover color // Hover color

View File

@ -0,0 +1,57 @@
.onoff-checkbox {
width:15%;
}
.onoffswitch {
position: relative;
width: 110px;
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block;
overflow: hidden;
cursor: pointer;
border: 2px solid #FFFFFF !important;
border-radius: 50px !important;
}
.onoffswitch-inner {
display: block;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
display: block;
float: left;
width: 50%;
height: 30px;
padding: 0;
line-height: 40px;
font-size: 20px;
box-sizing: border-box;
content: "";
background-color: #EEEEEE;
}
.onoffswitch-switch {
display: block;
width: 37px;
background-color: @light-green;
position: absolute;
top: 0;
bottom: 0;
right: 0px;
border: 2px solid #FFFFFF !important;
border-radius: 50px !important;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-right: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 71px;
background-color: #A1A1A1;
}

View File

@ -6,12 +6,16 @@
@import "checkbox.less"; @import "checkbox.less";
@import "onoff.less";
@import "results.less"; @import "results.less";
@import "infobox.less"; @import "infobox.less";
@import "search.less"; @import "search.less";
@import "advanced.less";
@import "cursor.less"; @import "cursor.less";
@import "code.less"; @import "code.less";

View File

@ -6,7 +6,7 @@
.favicon { .favicon {
margin-bottom:-3px; margin-bottom:-3px;
} }
a { a {
color: @black; color: @black;
text-decoration: none; text-decoration: none;
@ -18,7 +18,7 @@
&:visited{ &:visited{
color: @violet; color: @violet;
} }
.highlight { .highlight {
background-color: @dim-gray; background-color: @dim-gray;
// Chrome hack: bold is different size than normal // Chrome hack: bold is different size than normal
@ -64,10 +64,9 @@
float: left !important; float: left !important;
width: 24%; width: 24%;
margin: .5%; margin: .5%;
a{ a {
display: block; display: block;
width: 100%; width: 100%;
height: 170px;
background-size: cover; background-size: cover;
} }
} }
@ -148,3 +147,21 @@
color: @gray; color: @gray;
background: transparent; background: transparent;
} }
.result .text-muted small {
word-wrap: break-word;
}
.modal-wrapper {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.modal-wrapper {
background-clip: padding-box;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
outline: 0 none;
position: relative;
}

View File

@ -1,36 +1,33 @@
.search_categories, #categories { .search_categories, #categories {
margin: 10px 0 4px 0;
text-transform: capitalize; text-transform: capitalize;
margin-bottom: 0.5rem;
label{ display: flex;
border: none; flex-wrap: wrap;
box-shadow: none; flex-flow: row wrap;
font-size: 13px; align-content: stretch;
padding-bottom: 2px;
color: @gray;
margin-bottom: 5px;
&:hover{ label, .input-group-addon {
color: @black; flex-grow: 1;
background-color: transparent; flex-basis: auto;
} font-size: 1.2rem;
font-weight: normal;
&:active{ background-color: white;
box-shadow: none; border: @mild-gray 1px solid;
} border-right: none;
color: @dark-gray;
padding-bottom: 0.4rem;
padding-top: 0.4rem;
text-align: center;
}
label:last-child, .input-group-addon:last-child {
border-right: @mild-gray 1px solid;
} }
.active, .btn-primary{ input[type="checkbox"]:checked + label {
color: @black; color: @black;
font-weight: 700; font-weight: bold;
border-bottom: 5px solid @light-green; border-bottom: @light-green 5px solid;
background-color: transparent;
} }
}
#categories{
margin: 0;
} }
#main-logo{ #main-logo{

View File

@ -2,7 +2,9 @@
@gray: #A4A4A4; @gray: #A4A4A4;
@dim-gray: #F6F9FA; @dim-gray: #F6F9FA;
@dark-gray: #666; @dark-gray: #666;
@blue: #0088CC; @middle-gray: #F5F5F5;
@mild-gray: #DDD;
@blue: #0088CC;
@red: #F35E77; @red: #F35E77;
@violet: #684898; @violet: #684898;
@green: #2ecc71; @green: #2ecc71;

View File

@ -0,0 +1,49 @@
#advanced-search-container {
display: none;
text-align: center;
margin-bottom: 1rem;
clear: both;
label, .input-group-addon {
font-size: 1.3rem;
font-weight:normal;
background-color: white;
border: #DDD 1px solid;
border-right: none;
color: #333;
padding-bottom: 0.8rem;
padding-left: 1.2rem;
padding-right: 1.2rem;
}
label:last-child, .input-group-addon:last-child {
border-right: #DDD 1px solid;
}
input[type="radio"] {
display: none;
}
input[type="radio"]:checked + label {
color: black;
font-weight: bold;
background-color: #EEE;
}
}
#check-advanced {
display: none;
}
#check-advanced:checked ~ #advanced-search-container {
display: block;
}
.advanced {
padding: 0;
margin-top: 0.3rem;
text-align: right;
label, select {
cursor: pointer;
}
}

View File

@ -0,0 +1,57 @@
.onoff-checkbox {
width:15%;
}
.onoffswitch {
position: relative;
width: 110px;
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block;
overflow: hidden;
cursor: pointer;
border: 2px solid #FFFFFF !important;
border-radius: 50px !important;
}
.onoffswitch-inner {
display: block;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
display: block;
float: left;
width: 50%;
height: 30px;
padding: 0;
line-height: 40px;
font-size: 20px;
box-sizing: border-box;
content: "";
background-color: #EEEEEE;
}
.onoffswitch-switch {
display: block;
width: 37px;
background-color: #00CC00;
position: absolute;
top: 0;
bottom: 0;
right: 0px;
border: 2px solid #FFFFFF !important;
border-radius: 50px !important;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-right: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 71px;
background-color: #A1A1A1;
}

View File

@ -2,12 +2,16 @@
@import "checkbox.less"; @import "checkbox.less";
@import "onoff.less";
@import "results.less"; @import "results.less";
@import "infobox.less"; @import "infobox.less";
@import "search.less"; @import "search.less";
@import "advanced.less";
@import "cursor.less"; @import "cursor.less";
@import "code.less"; @import "code.less";

View File

@ -6,10 +6,10 @@
.favicon { .favicon {
margin-bottom:-3px; margin-bottom:-3px;
} }
a { a {
vertical-align: bottom; vertical-align: bottom;
.highlight { .highlight {
font-weight:bold; font-weight:bold;
} }
@ -81,3 +81,21 @@
color: #AAA; color: #AAA;
background: #FFF; background: #FFF;
} }
.result .text-muted small {
word-wrap: break-word;
}
.modal-wrapper {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
}
.modal-wrapper {
background-clip: padding-box;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
outline: 0 none;
position: relative;
}

View File

@ -1,4 +1,32 @@
.search_categories { .search_categories, #categories {
margin:10px 0; text-transform: capitalize;
text-transform: capitalize; margin-bottom: 1.5rem;
margin-top: 1.5rem;
display: flex;
flex-wrap: wrap;
align-content: stretch;
label, .input-group-addon {
flex-grow: 1;
flex-basis: auto;
font-size: 1.3rem;
font-weight: normal;
background-color: white;
border: #DDD 1px solid;
border-right: none;
color: #333;
padding-bottom: 0.8rem;
padding-top: 0.8rem;
text-align: center;
}
label:last-child, .input-group-addon:last-child {
border-right: #DDD 1px solid;
}
input[type="checkbox"]:checked + label{
color: black;
font-weight: bold;
background-color: #EEE;
}
} }

View File

@ -0,0 +1,9 @@
{% extends "courgette/base.html" %}
{% block content %}
<div class="center">
<h1>{{ _('Page not found') }}</h1>
{% autoescape false %}
<p>{{ _('Go to %(search_page)s.', search_page='<a href="{}">{}</a>'.decode('utf-8').format(url_for('index'), _('search page'))) }}</p>
{% endautoescape %}
</div>
{% endblock %}

View File

@ -6,20 +6,20 @@
<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>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> </p>
<h2>Why use Searx?</h2> <h2>Why use searx?</h2>
<ul> <ul>
<li>Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you</li> <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 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> <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> </ul>
<p>If you do care about privacy, want to be a conscious user, or otherwise believe <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> 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> <h2>Technical details - How does it work?</h2>
<p>Searx is a <a href="https://en.wikipedia.org/wiki/Metasearch_engine">metasearch engine</a>, <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 /> 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, Searx uses the search bar to perform GET requests.<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, searx uses the search bar to perform GET requests.<br />
Searx can be added to your browser's search bar; moreover, it can be set as the default search engine. Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.
</p> </p>

View File

@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="description" content="Searx - a privacy-respecting, hackable metasearch engine" /> <meta name="description" content="searx - a privacy-respecting, hackable metasearch engine" />
<meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> <meta name="keywords" content="searx, search, search engine, metasearch, meta search" />
<meta name="generator" content="searx/{{ searx_version }}"> <meta name="generator" content="searx/{{ searx_version }}">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">

View File

@ -1,8 +1,8 @@
<div class="result {{ result.class }}"> <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 }}" rel="noreferrer">{{ 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 }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %} {% 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> <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 }}" rel="noreferrer">{{ result.repository }}</a></p>{% endif %} {% if result.repository %}<p class="content"><a href="{{ result.repository|safe }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %}
<div dir="ltr"> <div dir="ltr">
{{ result.codelines|code_highlighter(result.code_language)|safe }} {{ result.codelines|code_highlighter(result.code_language)|safe }}
</div> </div>

View File

@ -5,7 +5,7 @@
{% endif %} {% endif %}
<div> <div>
<h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %} {% 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="content">{% if result.content %}{{ result.content|safe }}<br />{% endif %}</p>
<p class="url">{{ result.pretty_url }}&lrm;</p> <p class="url">{{ result.pretty_url }}&lrm;</p>

View File

@ -1,6 +1,6 @@
<div class="image_result"> <div class="image_result">
<p> <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> <a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><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> <span class="url"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="small_font">{{ _('original context') }}</a></span>
</p> </p>
</div> </div>

View File

@ -5,7 +5,7 @@
{% endif %} {% endif %}
<div> <div>
<h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %} {% 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="content">{% if result.content %}{{ result.content|safe }}<br />{% endif %}</p>
<p class="url">{{ result.pretty_url }}&lrm;</p> <p class="url">{{ result.pretty_url }}&lrm;</p>

View File

@ -2,12 +2,12 @@
{% if "icon_"~result.engine~".ico" in favicons %} {% 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}}" /> <img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />
{% endif %} {% endif %}
<h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
{% if result.content %}<span class="content">{{ result.content|safe }}</span><br />{% endif %} {% if result.content %}<span class="content">{{ result.content|safe }}</span><br />{% endif %}
<span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span><br /> <span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span><br />
<span> <span>
{% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %} {% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %}
{% if result.torrentfile %}<a href="{{ result.torrentfile }}" class="torrentfile" rel="noreferrer">{{ _('torrent file') }}</a>{% endif %} {% if result.torrentfile %}<a href="{{ result.torrentfile }}" class="torrentfile" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ _('torrent file') }}</a>{% endif %}
</span> </span>
<p class="url">{{ result.pretty_url }}&lrm;</p> <p class="url">{{ result.pretty_url }}&lrm;</p>
</div> </div>

View File

@ -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}}" /> <img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />
{% endif %} {% endif %}
<h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span><br />{% endif %} {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span><br />{% endif %}
<a href="{{ result.url }}" rel="noreferrer"><img width="400" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><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> <p class="url">{{ result.pretty_url }}&lrm;</p>
</div> </div>

View File

@ -0,0 +1,9 @@
{% extends "default/base.html" %}
{% block content %}
<div class="center">
<h1>{{ _('Page not found') }}</h1>
{% autoescape false %}
<p>{{ _('Go to %(search_page)s.', search_page='<a href="{}">{}</a>'.decode('utf-8').format(url_for('index'), _('search page'))) }}</p>
{% endautoescape %}
</div>
{% endblock %}

View File

@ -6,20 +6,20 @@
<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>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> </p>
<h2>Why use Searx?</h2> <h2>Why use searx?</h2>
<ul> <ul>
<li>Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you</li> <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 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> <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> </ul>
<p>If you do care about privacy, want to be a conscious user, or otherwise believe <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> 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> <h2>Technical details - How does it work?</h2>
<p>Searx is a <a href="https://en.wikipedia.org/wiki/Metasearch_engine">metasearch engine</a>, <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 /> 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 /> 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. Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.
</p> </p>

View File

@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="description" content="Searx - a privacy-respecting, hackable metasearch engine" /> <meta name="description" content="searx - a privacy-respecting, hackable metasearch engine" />
<meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> <meta name="keywords" content="searx, search, search engine, metasearch, meta search" />
<meta name="generator" content="searx/{{ searx_version }}"> <meta name="generator" content="searx/{{ searx_version }}">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">

View File

@ -1,18 +1,18 @@
<div class="infobox"> <div class="infobox">
<h2>{{ infobox.infobox }}</h2> <h2><bdi>{{ infobox.infobox }}</bdi></h2>
{% if infobox.img_src %}<img src="{{ image_proxify(infobox.img_src) }}" title="{{ infobox.infobox|striptags }}" alt="{{ infobox.infobox|striptags }}" />{% endif %} {% if infobox.img_src %}<img src="{{ image_proxify(infobox.img_src) }}" title="{{ infobox.infobox|striptags }}" alt="{{ infobox.infobox|striptags }}" />{% endif %}
<p>{{ infobox.entity }}</p> <p><bdi>{{ infobox.entity }}</bdi></p>
<p>{{ infobox.content | safe }}</p> <p><bdi>{{ infobox.content | safe }}</bdi></p>
{% if infobox.attributes %} {% if infobox.attributes %}
<div class="attributes"> <div class="attributes">
<table> <table>
{% for attribute in infobox.attributes %} {% for attribute in infobox.attributes %}
<tr> <tr>
<td>{{ attribute.label }}</td> <td><bdi>{{ attribute.label }}</bdi></td>
{% if attribute.image %} {% if attribute.image %}
<td><img src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td> <td><img src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
{% else %} {% else %}
<td>{{ attribute.value }}</td> <td><bdi>{{ attribute.value }}</bdi></td>
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
@ -24,7 +24,7 @@
<div class="urls"> <div class="urls">
<ul> <ul>
{% for url in infobox.urls %} {% for url in infobox.urls %}
<li class="url"><a href="{{ url.url }}" rel="noreferrer">{{ url.title }}</a></li> <li class="url"><bdi><a href="{{ url.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ url.title }}</a></bdi></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -34,7 +34,7 @@
<div class="relatedTopics"> <div class="relatedTopics">
{% for topic in infobox.relatedTopics %} {% for topic in infobox.relatedTopics %}
<div> <div>
<h3>{{ topic.name }}</h3> <h3><bdi>{{ topic.name }}</bdi></h3>
{% for suggestion in topic.suggestions %} {% for suggestion in topic.suggestions %}
<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}">
<input type="hidden" name="q" value="{{ suggestion }}"> <input type="hidden" name="q" value="{{ suggestion }}">

View File

@ -80,6 +80,15 @@
</select> </select>
</p> </p>
</fieldset> </fieldset>
<fieldset>
<legend>{{ _('Results on new tabs') }}</legend>
<p>
<select name='results_on_new_tab'>
<option value="1" {% if results_on_new_tab %}selected="selected"{% endif %}>{{ _('On') }}</option>
<option value="0" {% if not results_on_new_tab %}selected="selected"{% endif %}>{{ _('Off')}}</option>
</select>
</p>
</fieldset>
<fieldset> <fieldset>
<legend>{{ _('Currently used search engines') }}</legend> <legend>{{ _('Currently used search engines') }}</legend>

View File

@ -1,9 +1,9 @@
<div class="result {{ result.class }}"> <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 }}" rel="noreferrer">{{ 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 }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ 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> <p class="url">{{ result.pretty_url }}&lrm; <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ _('cached') }}</a></p>
{% if result.publishedDate %}<p class="published_date">{{ result.publishedDate }}</p>{% endif %} {% 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> <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 }}" rel="noreferrer">{{ result.repository }}</a></p>{% endif %} {% if result.repository %}<p class="result-content"><a href="{{ result.repository|safe }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %}
<div dir="ltr"> <div dir="ltr">
{{ result.codelines|code_highlighter(result.code_language)|safe }} {{ result.codelines|code_highlighter(result.code_language)|safe }}

View File

@ -1,6 +1,6 @@
<div class="result {{ result.class }}"> <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 }}" rel="noreferrer">{{ 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 }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ 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 class="url">{{ result.pretty_url }}&lrm; <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ _('cached') }}</a>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %}</p> {% 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> <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> </div>

View File

@ -1,6 +1,6 @@
<div class="image_result"> <div class="image_result">
<p> <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> <a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><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> <span class="url"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="small_font">{{ _('original context') }}</a></span>
</p> </p>
</div> </div>

View File

@ -5,8 +5,8 @@
{% endif %} {% endif %}
<div> <div>
<h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ 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 class="url">{{ result.pretty_url }}&lrm; <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ _('cached') }}</a>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span>{% endif %}</p> {% 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> <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> </div>

View File

@ -2,12 +2,12 @@
{% if "icon_"~result.engine~".ico" in favicons %} {% 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}}" /> <img width="14" height="14" class="favicon" src="{{ url_for('static', filename='img/icons/icon_'+result.engine+'.ico') }}" alt="{{result.engine}}" />
{% endif %} {% endif %}
<h3 class="result_title"><a href="{{ result.url }}" rel="noreferrer">{{ result.title|safe }}</a></h3> <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
<p class="url">{{ result.pretty_url }}&lrm;</p> <p class="url">{{ result.pretty_url }}&lrm;</p>
{% if result.content %}<p class="content">{{ result.content|safe }}</p>{% endif %} {% if result.content %}<p class="content">{{ result.content|safe }}</p>{% endif %}
<p> <p>
{% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %} {% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %}
{% if result.torrentfile %}<a href="{{ result.torrentfile }}" rel="noreferrer" class="torrentfile">{{ _('torrent file') }}</a>{% endif %} - {% if result.torrentfile %}<a href="{{ result.torrentfile }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="torrentfile">{{ _('torrent file') }}</a>{% endif %} -
<span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span> <span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span>
</p> </p>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="result"> <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 }}" rel="noreferrer">{{ 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 }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3>
{% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span><br />{% endif %} {% if result.publishedDate %}<span class="published_date">{{ result.publishedDate }}</span><br />{% endif %}
<a href="{{ result.url }}" rel="noreferrer"><img class="thumbnail" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a> <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><img class="thumbnail" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" alt="{{ result.title|striptags }}"/></a>
<p class="url">{{ result.url }}&lrm;</p> <p class="url">{{ result.url }}&lrm;</p>
</div> </div>

View File

@ -0,0 +1,9 @@
{% extends "oscar/base.html" %}
{% block content %}
<div class="text-center">
<h1>{{ _('Page not found') }}</h1>
{% autoescape false %}
<p>{{ _('Go to %(search_page)s.', search_page='<a href="{}">{}</a>'.decode('utf-8').format(url_for('index'), _('search page'))) }}</p>
{% endautoescape %}
</div>
{% endblock %}

View File

@ -7,27 +7,27 @@
<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>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> </p>
<h2>Why use Searx?</h2> <h2>Why use searx?</h2>
<ul> <ul>
<li>Searx may not offer you as personalised results as Google, but it doesn't generate a profile about you</li> <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 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> <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> </ul>
<p>If you do care about privacy, want to be a conscious user, or otherwise believe <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> 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> <h2>Technical details - How does it work?</h2>
<p>Searx is a <a href="https://en.wikipedia.org/wiki/Metasearch_engine">metasearch engine</a>, <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 /> 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, Searx uses the search bar to perform GET requests.<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, searx uses the search bar to perform GET requests.<br />
Searx can be added to your browser's search bar; moreover, it can be set as the default search engine. Searx can be added to your browser's search bar; moreover, it can be set as the default search engine.
</p> </p>
<h2>How can I make it my own?</h2> <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! <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 the Internet, is the more freedom we have!</p> <br />The more decentralized the Internet is, the more freedom we have!</p>
<h2>More about searx</h2> <h2>More about searx</h2>

View File

@ -0,0 +1,9 @@
<input type="checkbox" name="advanced_search" id="check-advanced" {% if advanced_search %} checked="checked"{% endif %}>
<label for="check-advanced">
<span class="glyphicon glyphicon-cog"></span>
{{ _('Advanced settings') }}
</label>
<div id="advanced-search-container">
{% include 'oscar/categories.html' %}
{% include 'oscar/time-range.html' %}
</div>

View File

@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"{% if rtl %} dir="rtl"{% endif %}>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="description" content="Searx - a privacy-respecting, hackable metasearch engine" /> <meta name="description" content="searx - a privacy-respecting, hackable metasearch engine" />
<meta name="keywords" content="searx, search, search engine, metasearch, meta search" /> <meta name="keywords" content="searx, search, search engine, metasearch, meta search" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="generator" content="searx/{{ searx_version }}"> <meta name="generator" content="searx/{{ searx_version }}">
@ -90,8 +90,5 @@
{% for script in scripts %} {% for script in scripts %}
<script src="{{ url_for('static', filename=script) }}"></script> <script src="{{ url_for('static', filename=script) }}"></script>
{% endfor %} {% endfor %}
<script type="text/javascript">
$(function() { $('a[data-toggle="modal"]').attr('href', '#'); });
</script>
</body> </body>
</html> </html>

View File

@ -1,42 +1,14 @@
<!-- used if scripts are disabled --> <div id="categories">
<noscript>
<div id="categories" class="btn-group btn-toggle">
{% if rtl %} {% if rtl %}
{% for category in categories | reverse %} {% for category in categories | reverse %}
<!--<div class="checkbox">--> <input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />
<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}_nojs" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} /> <label for="checkbox_{{ category|replace(' ', '_') }}">{{ _(category) }}</label>
<label class="btn btn-sm btn-primary active label_hide_if_not_checked" for="checkbox_{{ category|replace(' ', '_') }}_nojs">{{ _(category) }}</label> </label>
<label class="btn btn-sm btn-default label_hide_if_checked" for="checkbox_{{ category|replace(' ', '_') }}_nojs">{{ _(category) }}</label> {% endfor %}
<!--</div>-->
{% if category in selected_categories %}<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}_dis_activation" name="category_{{ category }}" value="off" checked="checked"/>{% endif %}
{% endfor %}
{% else %} {% else %}
{% for category in categories %} {% for category in categories %}
<!--<div class="checkbox">--> <input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />
<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}_nojs" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} /> <label for="checkbox_{{ category|replace(' ', '_') }}">{{ _(category) }}</label>
<label class="btn btn-sm btn-primary active label_hide_if_not_checked" for="checkbox_{{ category|replace(' ', '_') }}_nojs">{{ _(category) }}</label> {% endfor %}
<label class="btn btn-sm btn-default label_hide_if_checked" for="checkbox_{{ category|replace(' ', '_') }}_nojs">{{ _(category) }}</label>
<!--</div>-->
{% if category in selected_categories %}<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}_dis_activation" name="category_{{ category }}" value="off" checked="checked"/>{% endif %}
{% endfor %}
{% endif %} {% endif %}
</div> </div>
</noscript>
<div id="categories" class="btn-group btn-toggle hide_if_nojs" data-toggle="buttons">
{% if rtl %}
{% for category in categories | reverse %}
<label class="btn btn-sm {% if category in selected_categories %}btn-primary active{% else %}btn-default{% endif %}" data-btn-class="primary">
<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />{{ _(category) }}
</label>
{% endfor %}
{% else %}
{% for category in categories %}
<label class="btn btn-sm {% if category in selected_categories %}btn-primary active{% else %}btn-default{% endif %}" data-btn-class="primary">
<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />{{ _(category) }}
</label>
{% endfor %}
{% endif %}
</div>

View File

@ -1,21 +1,21 @@
{% from 'oscar/macros.html' import result_link with context %}
<div class="panel panel-default infobox"> <div class="panel panel-default infobox">
<div class="panel-heading"> <div class="panel-heading">
<bdi><h4 class="panel-title infobox_part">{{ infobox.infobox }}</h4></bdi> <h4 class="panel-title infobox_part"><bdi>{{ infobox.infobox }}</bdi></h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<bdi>
{% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %} {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
{% if infobox.content %}<p class="infobox_part">{{ infobox.content }}</p>{% endif %} {% if infobox.content %}<bdi><p class="infobox_part">{{ infobox.content }}</bdi></p>{% endif %}
{% if infobox.attributes %} {% if infobox.attributes %}
<table class="table table-striped infobox_part"> <table class="table table-striped infobox_part">
{% for attribute in infobox.attributes %} {% for attribute in infobox.attributes %}
<tr> <tr>
<td>{{ attribute.label }}</td> <td><bdi>{{ attribute.label }}</bdi></td>
{% if attribute.image %} {% if attribute.image %}
<td><img class="img-responsive" src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td> <td><img class="img-responsive" src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
{% else %} {% else %}
<td>{{ attribute.value }}</td> <td><bdi>{{ attribute.value }}</bdi></td>
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
@ -24,11 +24,12 @@
{% if infobox.urls %} {% if infobox.urls %}
<div class="infobox_part"> <div class="infobox_part">
<bdi>
{% for url in infobox.urls %} {% for url in infobox.urls %}
<p class="btn btn-default btn-xs"><a href="{{ url.url }}" rel="noreferrer">{{ url.title }}</a></p> <p class="btn btn-default btn-xs">{{ result_link(url.url, url.title) }}</a></p>
{% endfor %} {% endfor %}
</bdi>
</div> </div>
{% endif %} {% endif %}
</bdi>
</div> </div>
</div> </div>

View File

@ -9,16 +9,20 @@
<img width="32" height="32" class="favicon" src="static/themes/oscar/img/icons/{{ favicon }}.png" alt="{{ favicon }}" /> <img width="32" height="32" class="favicon" src="static/themes/oscar/img/icons/{{ favicon }}.png" alt="{{ favicon }}" />
{%- endmacro %} {%- endmacro %}
{%- macro result_link(url, title, classes='') -%}
<a href="{{ url }}" {% if classes %}class="{{ classes }} "{% endif %}{% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ title }}</a>
{%- endmacro -%}
<!-- Draw result header --> <!-- Draw result header -->
{% macro result_header(result, favicons) -%} {% macro result_header(result, favicons) -%}
<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> <h4 class="result_header">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result_link(result.url, result.title|safe) }}</h4>
{%- endmacro %} {%- endmacro %}
<!-- Draw result sub header --> <!-- Draw result sub header -->
{% macro result_sub_header(result) -%} {% macro result_sub_header(result) -%}
{% if result.publishedDate %}<time class="text-muted" datetime="{{ result.pubdate }}" >{{ result.publishedDate }}</time>{% endif %} {% if result.publishedDate %}<time class="text-muted" datetime="{{ result.pubdate }}" >{{ result.publishedDate }}</time>{% endif %}
{% if result.magnetlink %}<small> &bull; <a href="{{ result.magnetlink }}" class="magnetlink">{{ icon('magnet') }} {{ _('magnet link') }}</a></small>{% endif %} {% if result.magnetlink %}<small> &bull; {{ result_link(result.magnetlink, icon('magnet') + _('magnet link'), "magnetlink") }}</small>{% endif %}
{% if result.torrentfile %}<small> &bull; <a href="{{ result.torrentfile }}" class="torrentfile" rel="noreferrer">{{ icon('download-alt') }} {{ _('torrent file') }}</a></small>{% endif %} {% if result.torrentfile %}<small> &bull; {{ result_link(result.torrentfile, icon('download-alt') + _('torrent file'), "torrentfile") }}</small>{% endif %}
{%- endmacro %} {%- endmacro %}
<!-- Draw result footer --> <!-- Draw result footer -->
@ -28,7 +32,7 @@
{% for engine in result.engines %} {% for engine in result.engines %}
<span class="label label-default">{{ engine }}</span> <span class="label label-default">{{ engine }}</span>
{% endfor %} {% endfor %}
<small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ icon('link') }} {{ _('cached') }}</a></small> <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small>
</div> </div>
<div class="text-muted"><small>{{ result.pretty_url }}</small></div> <div class="text-muted"><small>{{ result.pretty_url }}</small></div>
{%- endmacro %} {%- endmacro %}
@ -39,7 +43,7 @@
{% for engine in result.engines %} {% for engine in result.engines %}
<span class="label label-default">{{ engine }}</span> <span class="label label-default">{{ engine }}</span>
{% endfor %} {% endfor %}
<small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ icon('link') }} {{ _('cached') }}</a></small> <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small>
<div class="text-muted"><small>{{ result.pretty_url }}</small></div> <div class="text-muted"><small>{{ result.pretty_url }}</small></div>
{%- endmacro %} {%- endmacro %}
@ -68,9 +72,11 @@
{%- endmacro %} {%- endmacro %}
{% macro checkbox_toggle(id, blocked) -%} {% macro checkbox_toggle(id, blocked) -%}
<div class="checkbox"> <div class="onoffswitch">
<input class="hidden" type="checkbox" id="{{ id }}" name="{{ id }}"{% if blocked %} checked="checked"{% endif %} /> <input type="checkbox" id="{{ id }}" name="{{ id }}"{% if blocked %} checked="checked"{% endif %} class="onoffswitch-checkbox">
<label class="btn btn-success label_hide_if_checked" for="{{ id }}">{{ _('Block') }}</label> <label class="onoffswitch-label" for="{{ id }}">
<label class="btn btn-danger label_hide_if_not_checked" for="{{ id }}">{{ _('Allow') }}</label> <span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div> </div>
{%- endmacro %} {%- endmacro %}

View File

@ -36,7 +36,7 @@
<label class="col-sm-3 col-md-2">{{ _('Default categories') }}</label> <label class="col-sm-3 col-md-2">{{ _('Default categories') }}</label>
{% else %} {% else %}
<label class="col-sm-3 col-md-2">{{ _('Default categories') }}</label> <label class="col-sm-3 col-md-2">{{ _('Default categories') }}</label>
<div class="col-sm-11 col-md-10"> <div class="col-sm-11 col-md-10 search-categories">
{% include 'oscar/categories.html' %} {% include 'oscar/categories.html' %}
</div> </div>
{% endif %} {% endif %}
@ -117,6 +117,15 @@
<option value="pointhi" {% if cookies['oscar-style'] == 'pointhi' %}selected="selected"{% endif %}>Pointhi</option> <option value="pointhi" {% if cookies['oscar-style'] == 'pointhi' %}selected="selected"{% endif %}>Pointhi</option>
</select> </select>
{{ preferences_item_footer(_('Choose style for this theme'), _('Style'), rtl) }} {{ preferences_item_footer(_('Choose style for this theme'), _('Style'), rtl) }}
{% set label = _('Results on new tabs') %}
{% set info = _('Open result links on new browser tabs') %}
{{ preferences_item_header(info, label, rtl) }}
<select class="form-control" name='results_on_new_tab'>
<option value="1" {% if results_on_new_tab %}selected="selected"{% endif %}>{{ _('On') }}</option>
<option value="0" {% if not results_on_new_tab %}selected="selected"{% endif %}>{{ _('Off')}}</option>
</select>
{{ preferences_item_footer(info, label, rtl) }}
</div> </div>
</fieldset> </fieldset>
</div> </div>
@ -164,7 +173,9 @@
{% if not search_engine.private %} {% if not search_engine.private %}
<tr> <tr>
{% if not rtl %} {% if not rtl %}
<td>{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }}</td> <td class="onoff-checkbox">
{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }}
</td>
<th>{{ search_engine.name }}</th> <th>{{ search_engine.name }}</th>
<td>{{ shortcuts[search_engine.name] }}</td> <td>{{ shortcuts[search_engine.name] }}</td>
<td><input type="checkbox" {{ "checked" if search_engine.safesearch==True else ""}} readonly="readonly" disabled="disabled"></td> <td><input type="checkbox" {{ "checked" if search_engine.safesearch==True else ""}} readonly="readonly" disabled="disabled"></td>
@ -176,7 +187,9 @@
<td><input type="checkbox" {{ "checked" if search_engine.safesearch==True else ""}} readonly="readonly" disabled="disabled"></td> <td><input type="checkbox" {{ "checked" if search_engine.safesearch==True else ""}} readonly="readonly" disabled="disabled"></td>
<td>{{ shortcuts[search_engine.name] }}</td> <td>{{ shortcuts[search_engine.name] }}</td>
<th>{{ search_engine.name }}</th> <th>{{ search_engine.name }}</th>
<td>{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }}</td> <td class="onoff-checkbox">
{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }}
</td>
{% endif %} {% endif %}
</tr> </tr>
{% endif %} {% endif %}
@ -203,7 +216,9 @@
<div class="panel-body"> <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">{{ _(plugin.description) }}</div>
<div class="col-xs-6 col-sm-4 col-md-6"> <div class="col-xs-6 col-sm-4 col-md-6">
<div class="onoff-checkbox">
{{ checkbox_toggle('plugin_' + plugin.id, plugin.id not in allowed_plugins) }} {{ checkbox_toggle('plugin_' + plugin.id, plugin.id not in allowed_plugins) }}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@
{% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %} {% 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 }}" rel="noreferrer">{{ result.repository }}</a></p>{% endif %} {% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %}
<div dir="ltr"> <div dir="ltr">
{{ result.codelines|code_highlighter(result.code_language)|safe }} {{ result.codelines|code_highlighter(result.code_language)|safe }}

View File

@ -1,4 +1,4 @@
{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %} {% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon with context %}
{{ result_header(result, favicons) }} {{ result_header(result, favicons) }}
{{ result_sub_header(result) }} {{ result_sub_header(result) }}

View File

@ -1,12 +1,12 @@
{% from 'oscar/macros.html' import draw_favicon %} {% from 'oscar/macros.html' import draw_favicon %}
<a href="{{ result.img_src }}" rel="noreferrer" data-toggle="modal" data-target="#modal-{{ index }}"> <a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} 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"> <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> </a>
<div class="modal fade" id="modal-{{ index }}" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal fade" id="modal-{{ index }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-wrapper">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result.title|striptags }}</h4> <h4 class="modal-title">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result.title|striptags }}</h4>
@ -20,8 +20,14 @@
<span class="label label-default pull-right">{{ result.engine }}</span> <span class="label label-default pull-right">{{ result.engine }}</span>
<p class="text-muted pull-left">{{ result.pretty_url }}</p> <p class="text-muted pull-left">{{ result.pretty_url }}</p>
<div class="clearfix"></div> <div class="clearfix"></div>
<a href="{{ result.img_src }}" rel="noreferrer" class="btn btn-default">{{ _('Get image') }}</a> <div class="row">
<a href="{{ result.url }}" rel="noreferrer" class="btn btn-default">{{ _('View source') }}</a> <div class="col-md-6">
<a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('Get image') }}</a>
</div>
<div class="col-md-6">
<a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('View source') }}</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -15,7 +15,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<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> <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><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 %} {% if result.content %}<p class="col-xs-12 col-sm-8 col-md-8 result-content">{{ result.content|safe }}</p>{% endif %}
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
{% extends "oscar/base.html" %} {% extends "oscar/base.html" %}
{% block title %}{{ q }} - {% endblock %} {% block title %}{{ q }} - {% endblock %}
{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q }}" href="{{ url_for('index') }}?q={{ q|urlencode }}&amp;format=rss&amp;{% for category in selected_categories %}category_{{ category }}=1&amp;{% endfor %}pageno={{ pageno }}">{% endblock %} {% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q }}" href="{{ url_for('index') }}?q={{ q|urlencode }}&amp;format=rss&amp;{% for category in selected_categories %}category_{{ category }}=1&amp;{% endfor %}pageno={{ pageno }}&amp;time_range={{ time_range }}">{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-sm-8" id="main_results"> <div class="col-sm-8" id="main_results">
@ -41,6 +41,7 @@
{% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %} {% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %}
<input type="hidden" name="q" value="{{ q }}" /> <input type="hidden" name="q" value="{{ q }}" />
<input type="hidden" name="pageno" value="{{ pageno+1 }}" /> <input type="hidden" name="pageno" value="{{ pageno+1 }}" />
<input type="hidden" name="time_range" value="{{ time_range }}" />
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-backward"></span> {{ _('next page') }}</button> <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-backward"></span> {{ _('next page') }}</button>
</form> </form>
</div> </div>
@ -48,6 +49,7 @@
<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left">
{% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %} {% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %}
<input type="hidden" name="pageno" value="{{ pageno-1 }}" /> <input type="hidden" name="pageno" value="{{ pageno-1 }}" />
<input type="hidden" name="time_range" value="{{ time_range }}" />
<button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-forward"></span> {{ _('previous page') }}</button> <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-forward"></span> {{ _('previous page') }}</button>
</form> </form>
</div> </div>
@ -60,6 +62,7 @@
<input type="hidden" name="q" value="{{ q }}" /> <input type="hidden" name="q" value="{{ q }}" />
{% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %} {% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %}
<input type="hidden" name="pageno" value="{{ pageno-1 }}" /> <input type="hidden" name="pageno" value="{{ pageno-1 }}" />
<input type="hidden" name="time_range" value="{{ time_range }}" />
<button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-backward"></span> {{ _('previous page') }}</button> <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-backward"></span> {{ _('previous page') }}</button>
</form> </form>
</div> </div>
@ -68,6 +71,7 @@
{% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %} {% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %}
<input type="hidden" name="q" value="{{ q }}" /> <input type="hidden" name="q" value="{{ q }}" />
<input type="hidden" name="pageno" value="{{ pageno+1 }}" /> <input type="hidden" name="pageno" value="{{ pageno+1 }}" />
<input type="hidden" name="time_range" value="{{ time_range }}" />
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-forward"></span> {{ _('next page') }}</button> <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-forward"></span> {{ _('next page') }}</button>
</form> </form>
</div> </div>
@ -118,7 +122,7 @@
<form role="form"> <form role="form">
<div class="form-group"> <div class="form-group">
<label for="search_url">{{ _('Search URL') }}</label> <label for="search_url">{{ _('Search URL') }}</label>
<input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}" readonly> <input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}{% if time_range %}&amp;time_range={{ time_range }}{% endif %}" readonly>
</div> </div>
</form> </form>
@ -130,6 +134,7 @@
<input type="hidden" name="format" value="{{ output_type }}"> <input type="hidden" name="format" value="{{ output_type }}">
{% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1">{% endfor %} {% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1">{% endfor %}
<input type="hidden" name="pageno" value="{{ pageno }}"> <input type="hidden" name="pageno" value="{{ pageno }}">
<input type="hidden" name="time_range" value="{{ time_range }}" />
<button type="submit" class="btn btn-default">{{ output_type }}</button> <button type="submit" class="btn btn-default">{{ output_type }}</button>
</form> </form>
{% endfor %} {% endfor %}

Some files were not shown because too many files have changed in this diff Show More