summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile25
-rw-r--r--searx/autocomplete.py23
-rw-r--r--searx/engines/__init__.py3
-rw-r--r--searx/engines/bing_images.py2
-rw-r--r--searx/engines/bing_news.py111
-rw-r--r--searx/engines/currency_convert.py6
-rw-r--r--searx/engines/flickr_noapi.py17
-rw-r--r--searx/engines/google.py271
-rw-r--r--searx/engines/qwant.py98
-rw-r--r--searx/engines/swisscows.py108
-rw-r--r--searx/engines/vimeo.py12
-rw-r--r--searx/engines/www1x.py4
-rw-r--r--searx/engines/youtube_api.py83
-rw-r--r--searx/engines/youtube_noapi.py81
-rw-r--r--searx/plugins/__init__.py8
-rw-r--r--searx/plugins/self_info.py (renamed from searx/plugins/self_ip.py)13
-rw-r--r--searx/plugins/tracker_url_remover.py44
-rw-r--r--searx/search.py11
-rw-r--r--searx/settings.yml48
-rw-r--r--searx/static/js/search_on_category_select.js6
-rw-r--r--searx/static/themes/courgette/img/favicon.pngbin3208 -> 2060 bytes
-rw-r--r--searx/static/themes/courgette/img/github_ribbon.pngbin7791 -> 5213 bytes
-rw-r--r--searx/static/themes/courgette/img/preference-icon.pngbin1574 -> 1315 bytes
-rw-r--r--searx/static/themes/courgette/img/search-icon.pngbin3442 -> 3270 bytes
-rw-r--r--searx/static/themes/courgette/img/searx-mobile.pngbin10208 -> 9568 bytes
-rw-r--r--searx/static/themes/courgette/img/searx.pngbin7647 -> 3902 bytes
-rw-r--r--searx/static/themes/default/img/favicon.pngbin3064 -> 2060 bytes
-rw-r--r--searx/static/themes/default/img/github_ribbon.pngbin7791 -> 5213 bytes
-rw-r--r--searx/static/themes/default/img/preference-icon.pngbin603 -> 532 bytes
-rw-r--r--searx/static/themes/default/img/searx.pngbin4909 -> 3902 bytes
-rw-r--r--searx/static/themes/oscar/img/favicon.pngbin3208 -> 2060 bytes
-rw-r--r--searx/static/themes/oscar/img/icons/github.pngbin4449 -> 4336 bytes
-rw-r--r--searx/static/themes/oscar/img/icons/searchcode code.pngbin4371 -> 4242 bytes
-rw-r--r--searx/static/themes/oscar/img/icons/searchcode doc.pngbin4371 -> 4242 bytes
-rw-r--r--searx/static/themes/oscar/img/icons/wikipedia.pngbin3993 -> 3960 bytes
-rw-r--r--searx/static/themes/oscar/img/icons/youtube.pngbin4156 -> 4153 bytes
-rw-r--r--searx/static/themes/oscar/img/map/layers-2x.pngbin2898 -> 1763 bytes
-rw-r--r--searx/static/themes/oscar/img/map/layers.pngbin1502 -> 1142 bytes
-rw-r--r--searx/static/themes/oscar/img/map/marker-icon-2x-green.pngbin4203 -> 3753 bytes
-rw-r--r--searx/static/themes/oscar/img/map/marker-icon-2x-orange.pngbin4167 -> 3691 bytes
-rw-r--r--searx/static/themes/oscar/img/map/marker-icon-2x-red.pngbin4230 -> 3692 bytes
-rw-r--r--searx/static/themes/oscar/img/map/marker-icon-green.pngbin1822 -> 1696 bytes
-rw-r--r--searx/static/themes/oscar/img/map/marker-icon-orange.pngbin1862 -> 1714 bytes
-rw-r--r--searx/static/themes/oscar/img/map/marker-icon-red.pngbin1870 -> 1690 bytes
-rw-r--r--searx/static/themes/oscar/img/searx_logo.pngbin23498 -> 16775 bytes
-rw-r--r--searx/static/themes/pix-art/img/favicon.pngbin3064 -> 2060 bytes
-rw-r--r--searx/static/themes/pix-art/img/searx-pixel-small.pngbin238 -> 236 bytes
-rw-r--r--searx/templates/courgette/preferences.html2
-rw-r--r--searx/templates/default/preferences.html2
-rw-r--r--searx/templates/oscar/messages/no_cookies.html5
-rw-r--r--searx/templates/oscar/preferences.html75
-rw-r--r--searx/tests/engines/test_bing_images.py2
-rw-r--r--searx/tests/engines/test_bing_news.py274
-rw-r--r--searx/tests/engines/test_flickr_noapi.py250
-rw-r--r--searx/tests/engines/test_google.py29
-rw-r--r--searx/tests/engines/test_qwant.py317
-rw-r--r--searx/tests/engines/test_swisscows.py128
-rw-r--r--searx/tests/engines/test_vimeo.py53
-rw-r--r--searx/tests/engines/test_www1x.py4
-rw-r--r--searx/tests/engines/test_yahoo_news.py19
-rw-r--r--searx/tests/engines/test_youtube_api.py111
-rw-r--r--searx/tests/engines/test_youtube_noapi.py154
-rw-r--r--searx/tests/test_engines.py4
-rw-r--r--searx/tests/test_plugins.py19
-rw-r--r--searx/tests/test_search.py25
-rw-r--r--searx/translations/pt/LC_MESSAGES/messages.mobin0 -> 6350 bytes
-rw-r--r--searx/translations/pt/LC_MESSAGES/messages.po574
-rw-r--r--searx/utils.py8
-rw-r--r--searx/webapp.py68
-rw-r--r--versions.cfg80
70 files changed, 2586 insertions, 591 deletions
diff --git a/Dockerfile b/Dockerfile
index 831a429e2..543c74d0e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,21 +1,22 @@
-FROM debian:stable
+FROM python:2.7-slim
+
+WORKDIR /app
+
+RUN useradd searx
+
+EXPOSE 5000
+CMD ["/usr/local/bin/uwsgi", "--uid", "searx", "--gid", "searx", "--http", ":5000", "-w", "searx.webapp"]
RUN apt-get update && \
apt-get install -y --no-install-recommends \
- python-dev python2.7-minimal python-virtualenv \
- python-pybabel python-pip zlib1g-dev \
- libxml2-dev libxslt1-dev build-essential \
- openssl
+ zlib1g-dev libxml2-dev libxslt1-dev libffi-dev build-essential \
+ libssl-dev openssl && \
+ rm -rf /var/lib/apt/lists/*
-RUN useradd searx
+RUN pip install --no-cache uwsgi
-WORKDIR /app
-RUN pip install uwsgi
COPY requirements.txt /app/requirements.txt
-RUN pip install -r requirements.txt
+RUN pip install --no-cache -r requirements.txt
COPY . /app
RUN sed -i -e "s/ultrasecretkey/`openssl rand -hex 16`/g" searx/settings.yml
-
-EXPOSE 5000
-CMD ["/usr/local/bin/uwsgi", "--uid", "searx", "--gid", "searx", "--http", ":5000", "-w", "searx.webapp"]
diff --git a/searx/autocomplete.py b/searx/autocomplete.py
index f5775bc63..1a324b8a9 100644
--- a/searx/autocomplete.py
+++ b/searx/autocomplete.py
@@ -57,17 +57,17 @@ def searx_bang(full_query):
# check if query starts with categorie name
for categorie in categories:
if categorie.startswith(engine_query):
- results.append(first_char+'{categorie}'.format(categorie=categorie))
+ results.append(first_char + '{categorie}'.format(categorie=categorie))
# check if query starts with engine name
for engine in engines:
if engine.startswith(engine_query.replace('_', ' ')):
- results.append(first_char+'{engine}'.format(engine=engine.replace(' ', '_')))
+ results.append(first_char + '{engine}'.format(engine=engine.replace(' ', '_')))
# check if query starts with engine shortcut
for engine_shortcut in engine_shortcuts:
if engine_shortcut.startswith(engine_query):
- results.append(first_char+'{engine_shortcut}'.format(engine_shortcut=engine_shortcut))
+ results.append(first_char + '{engine_shortcut}'.format(engine_shortcut=engine_shortcut))
# check if current query stats with :bang
elif first_char == ':':
@@ -112,7 +112,7 @@ def searx_bang(full_query):
def dbpedia(query):
# dbpedia autocompleter, no HTTPS
- autocomplete_url = 'http://lookup.dbpedia.org/api/search.asmx/KeywordSearch?' # noqa
+ autocomplete_url = 'http://lookup.dbpedia.org/api/search.asmx/KeywordSearch?'
response = get(autocomplete_url
+ urlencode(dict(QueryString=query)))
@@ -139,7 +139,7 @@ def duckduckgo(query):
def google(query):
# google autocompleter
- autocomplete_url = 'https://suggestqueries.google.com/complete/search?client=toolbar&' # noqa
+ autocomplete_url = 'https://suggestqueries.google.com/complete/search?client=toolbar&'
response = get(autocomplete_url
+ urlencode(dict(q=query)))
@@ -153,9 +153,19 @@ def google(query):
return results
+def startpage(query):
+ # wikipedia autocompleter
+ url = 'https://startpage.com/do/suggest?{query}'
+
+ resp = get(url.format(query=urlencode({'query': query}))).text.split('\n')
+ if len(resp) > 1:
+ return resp
+ return []
+
+
def wikipedia(query):
# wikipedia autocompleter
- url = 'https://en.wikipedia.org/w/api.php?action=opensearch&{0}&limit=10&namespace=0&format=json' # noqa
+ url = 'https://en.wikipedia.org/w/api.php?action=opensearch&{0}&limit=10&namespace=0&format=json'
resp = loads(get(url.format(urlencode(dict(search=query)))).text)
if len(resp) > 1:
@@ -166,5 +176,6 @@ def wikipedia(query):
backends = {'dbpedia': dbpedia,
'duckduckgo': duckduckgo,
'google': google,
+ 'startpage': startpage,
'wikipedia': wikipedia
}
diff --git a/searx/engines/__init__.py b/searx/engines/__init__.py
index 18a45d851..42e1f08bc 100644
--- a/searx/engines/__init__.py
+++ b/searx/engines/__init__.py
@@ -71,6 +71,9 @@ def load_engine(engine_data):
if not hasattr(engine, 'language_support'):
engine.language_support = True
+ if not hasattr(engine, 'safesearch'):
+ engine.safesearch = False
+
if not hasattr(engine, 'timeout'):
engine.timeout = settings['server']['request_timeout']
diff --git a/searx/engines/bing_images.py b/searx/engines/bing_images.py
index b06a57edc..839b8e5be 100644
--- a/searx/engines/bing_images.py
+++ b/searx/engines/bing_images.py
@@ -28,7 +28,7 @@ safesearch = True
# search-url
base_url = 'https://www.bing.com/'
search_string = 'images/search?{query}&count=10&first={offset}'
-thumb_url = "http://ts1.mm.bing.net/th?id={ihk}" # no https, bad certificate
+thumb_url = "https://www.bing.com/th?id={ihk}"
# safesearch definitions
safesearch_types = {2: 'STRICT',
diff --git a/searx/engines/bing_news.py b/searx/engines/bing_news.py
index 1e5d361c1..a2397c48e 100644
--- a/searx/engines/bing_news.py
+++ b/searx/engines/bing_news.py
@@ -6,18 +6,17 @@
max. 5000 query/month
@using-api no (because of query limit)
- @results HTML (using search portal)
- @stable no (HTML can change)
- @parse url, title, content, publishedDate
+ @results RSS (using search portal)
+ @stable yes (except perhaps for the images)
+ @parse url, title, content, publishedDate, thumbnail
"""
from urllib import urlencode
-from cgi import escape
-from lxml import html
-from datetime import datetime, timedelta
+from urlparse import urlparse, parse_qsl
+from datetime import datetime
from dateutil import parser
-import re
-from searx.engines.xpath import extract_text
+from lxml import etree
+from searx.utils import list_get
# engine dependent config
categories = ['news']
@@ -26,7 +25,25 @@ language_support = True
# search-url
base_url = 'https://www.bing.com/'
-search_string = 'news/search?{query}&first={offset}'
+search_string = 'news/search?{query}&first={offset}&format=RSS'
+
+
+# remove click
+def url_cleanup(url_string):
+ parsed_url = urlparse(url_string)
+ if parsed_url.netloc == 'www.bing.com' and parsed_url.path == '/news/apiclick.aspx':
+ query = dict(parse_qsl(parsed_url.query))
+ return query.get('url', None)
+ return url_string
+
+
+# replace the http://*bing4.com/th?id=... by https://www.bing.com/th?id=...
+def image_url_cleanup(url_string):
+ parsed_url = urlparse(url_string)
+ if parsed_url.netloc.endswith('bing4.com') and parsed_url.path == '/th':
+ query = dict(parse_qsl(parsed_url.query))
+ return "https://www.bing.com/th?id=" + query.get('id')
+ return url_string
# do search-request
@@ -42,8 +59,6 @@ def request(query, params):
query=urlencode({'q': query, 'setmkt': language}),
offset=offset)
- params['cookies']['_FP'] = "ui=en-US"
-
params['url'] = base_url + search_path
return params
@@ -53,50 +68,44 @@ def request(query, params):
def response(resp):
results = []
- dom = html.fromstring(resp.content)
+ rss = etree.fromstring(resp.content)
+
+ ns = rss.nsmap
# parse results
- for result in dom.xpath('//div[@class="sn_r"]'):
- link = result.xpath('.//div[@class="newstitle"]/a')[0]
- url = link.attrib.get('href')
- title = extract_text(link)
- contentXPath = result.xpath('.//div[@class="sn_txt"]/div//span[@class="sn_snip"]')
- content = escape(extract_text(contentXPath))
-
- # parse publishedDate
- publishedDateXPath = result.xpath('.//div[@class="sn_txt"]/div'
- '//span[contains(@class,"sn_ST")]'
- '//span[contains(@class,"sn_tm")]')
-
- publishedDate = escape(extract_text(publishedDateXPath))
-
- if re.match("^[0-9]+ minute(s|) ago$", publishedDate):
- timeNumbers = re.findall(r'\d+', publishedDate)
- publishedDate = datetime.now() - timedelta(minutes=int(timeNumbers[0]))
- elif re.match("^[0-9]+ hour(s|) ago$", publishedDate):
- timeNumbers = re.findall(r'\d+', publishedDate)
- publishedDate = datetime.now() - timedelta(hours=int(timeNumbers[0]))
- elif re.match("^[0-9]+ hour(s|), [0-9]+ minute(s|) ago$", publishedDate):
- timeNumbers = re.findall(r'\d+', publishedDate)
- publishedDate = datetime.now()\
- - timedelta(hours=int(timeNumbers[0]))\
- - timedelta(minutes=int(timeNumbers[1]))
- elif re.match("^[0-9]+ day(s|) ago$", publishedDate):
- timeNumbers = re.findall(r'\d+', publishedDate)
- publishedDate = datetime.now() - timedelta(days=int(timeNumbers[0]))
- else:
- try:
- publishedDate = parser.parse(publishedDate, dayfirst=False)
- except TypeError:
- publishedDate = datetime.now()
- except ValueError:
- publishedDate = datetime.now()
+ for item in rss.xpath('./channel/item'):
+ # url / title / content
+ url = url_cleanup(item.xpath('./link/text()')[0])
+ title = list_get(item.xpath('./title/text()'), 0, url)
+ content = list_get(item.xpath('./description/text()'), 0, '')
+
+ # publishedDate
+ publishedDate = list_get(item.xpath('./pubDate/text()'), 0)
+ try:
+ publishedDate = parser.parse(publishedDate, dayfirst=False)
+ except TypeError:
+ publishedDate = datetime.now()
+ except ValueError:
+ publishedDate = datetime.now()
+
+ # thumbnail
+ thumbnail = list_get(item.xpath('./News:Image/text()', namespaces=ns), 0)
+ if thumbnail is not None:
+ thumbnail = image_url_cleanup(thumbnail)
# append result
- results.append({'url': url,
- 'title': title,
- 'publishedDate': publishedDate,
- 'content': content})
+ if thumbnail is not None:
+ results.append({'template': 'videos.html',
+ 'url': url,
+ 'title': title,
+ 'publishedDate': publishedDate,
+ 'content': content,
+ 'thumbnail': thumbnail})
+ else:
+ results.append({'url': url,
+ 'title': title,
+ 'publishedDate': publishedDate,
+ 'content': content})
# return results
return results
diff --git a/searx/engines/currency_convert.py b/searx/engines/currency_convert.py
index 1ba4575c5..26830a167 100644
--- a/searx/engines/currency_convert.py
+++ b/searx/engines/currency_convert.py
@@ -9,7 +9,7 @@ categories = []
url = 'https://download.finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s={query}=X'
weight = 100
-parser_re = re.compile(r'^\W*(\d+(?:\.\d+)?)\W*([^.0-9].+)\W*in?\W*([^\.]+)\W*$', re.I) # noqa
+parser_re = re.compile(u'^\W*(\d+(?:\.\d+)?)\W*([^.0-9].+)\W+in?\W+([^\.]+)\W*$', re.I) # noqa
db = 1
@@ -17,7 +17,7 @@ db = 1
def normalize_name(name):
name = name.lower().replace('-', ' ')
name = re.sub(' +', ' ', name)
- return unicodedata.normalize('NFKD', u"" + name).lower()
+ return unicodedata.normalize('NFKD', name).lower()
def name_to_iso4217(name):
@@ -35,7 +35,7 @@ def iso4217_to_name(iso4217, language):
def request(query, params):
- m = parser_re.match(query)
+ m = parser_re.match(unicode(query, 'utf8'))
if not m:
# wrong query
return params
diff --git a/searx/engines/flickr_noapi.py b/searx/engines/flickr_noapi.py
index 2071b8e36..87b912eb3 100644
--- a/searx/engines/flickr_noapi.py
+++ b/searx/engines/flickr_noapi.py
@@ -25,7 +25,7 @@ categories = ['images']
url = 'https://www.flickr.com/'
search_url = url + 'search?{query}&page={page}'
photo_url = 'https://www.flickr.com/photos/{userid}/{photoid}'
-regex = re.compile(r"\"search-photos-models\",\"photos\":(.*}),\"totalItems\":", re.DOTALL)
+regex = re.compile(r"\"search-photos-lite-models\",\"photos\":(.*}),\"totalItems\":", re.DOTALL)
image_sizes = ('o', 'k', 'h', 'b', 'c', 'z', 'n', 'm', 't', 'q', 's')
paging = True
@@ -38,6 +38,7 @@ def build_flickr_url(user_id, photo_id):
def request(query, params):
params['url'] = search_url.format(query=urlencode({'text': query}),
page=params['pageno'])
+
return params
@@ -75,10 +76,10 @@ def response(resp):
logger.debug('cannot find valid image size: {0}'.format(repr(photo)))
continue
- if 'id' not in photo['owner']:
+ if 'ownerNsid' not in photo:
continue
-# For a bigger thumbnail, keep only the url_z, not the url_n
+ # For a bigger thumbnail, keep only the url_z, not the url_n
if 'n' in photo['sizes']:
thumbnail_src = photo['sizes']['n']['url']
elif 'z' in photo['sizes']:
@@ -86,20 +87,14 @@ def response(resp):
else:
thumbnail_src = img_src
- url = build_flickr_url(photo['owner']['id'], photo['id'])
+ url = build_flickr_url(photo['ownerNsid'], photo['id'])
title = photo.get('title', '')
content = '<span class="photo-author">' +\
- photo['owner']['username'] +\
+ photo['username'] +\
'</span><br />'
- if 'description' in photo:
- content = content +\
- '<span class="description">' +\
- photo['description'] +\
- '</span>'
-
# append result
results.append({'url': url,
'title': title,
diff --git a/searx/engines/google.py b/searx/engines/google.py
index 807c58ed5..0e78a9e2c 100644
--- a/searx/engines/google.py
+++ b/searx/engines/google.py
@@ -8,39 +8,126 @@
# @stable no (HTML can change)
# @parse url, title, content, suggestion
+import re
from urllib import urlencode
from urlparse import urlparse, parse_qsl
from lxml import html
from searx.poolrequests import get
from searx.engines.xpath import extract_text, extract_url
+
# engine dependent config
categories = ['general']
paging = True
language_support = True
+use_locale_domain = True
+
+# based on https://en.wikipedia.org/wiki/List_of_Google_domains and tests
+default_hostname = 'www.google.com'
+
+country_to_hostname = {
+ 'BG': 'www.google.bg', # Bulgaria
+ 'CZ': 'www.google.cz', # Czech Republic
+ 'DE': 'www.google.de', # Germany
+ 'DK': 'www.google.dk', # Denmark
+ 'AT': 'www.google.at', # Austria
+ 'CH': 'www.google.ch', # Switzerland
+ 'GR': 'www.google.gr', # Greece
+ 'AU': 'www.google.com.au', # Australia
+ 'CA': 'www.google.ca', # Canada
+ 'GB': 'www.google.co.uk', # United Kingdom
+ 'ID': 'www.google.co.id', # Indonesia
+ 'IE': 'www.google.ie', # Ireland
+ 'IN': 'www.google.co.in', # India
+ 'MY': 'www.google.com.my', # Malaysia
+ 'NZ': 'www.google.co.nz', # New Zealand
+ 'PH': 'www.google.com.ph', # Philippines
+ 'SG': 'www.google.com.sg', # Singapore
+ # 'US': 'www.google.us', # United State, redirect to .com
+ 'ZA': 'www.google.co.za', # South Africa
+ 'AR': 'www.google.com.ar', # Argentina
+ 'CL': 'www.google.cl', # Chile
+ 'ES': 'www.google.es', # Span
+ 'MX': 'www.google.com.mx', # Mexico
+ 'EE': 'www.google.ee', # Estonia
+ 'FI': 'www.google.fi', # Finland
+ 'BE': 'www.google.be', # Belgium
+ 'FR': 'www.google.fr', # France
+ 'IL': 'www.google.co.il', # Israel
+ 'HR': 'www.google.hr', # Croatia
+ 'HU': 'www.google.hu', # Hungary
+ 'IT': 'www.google.it', # Italy
+ 'JP': 'www.google.co.jp', # Japan
+ 'KR': 'www.google.co.kr', # South Korean
+ 'LT': 'www.google.lt', # Lithuania
+ 'LV': 'www.google.lv', # Latvia
+ 'NO': 'www.google.no', # Norway
+ 'NL': 'www.google.nl', # Netherlands
+ 'PL': 'www.google.pl', # Poland
+ 'BR': 'www.google.com.br', # Brazil
+ 'PT': 'www.google.pt', # Portugal
+ 'RO': 'www.google.ro', # Romania
+ 'RU': 'www.google.ru', # Russia
+ 'SK': 'www.google.sk', # Slovakia
+ 'SL': 'www.google.si', # Slovenia (SL -> si)
+ 'SE': 'www.google.se', # Sweden
+ 'TH': 'www.google.co.th', # Thailand
+ 'TR': 'www.google.com.tr', # Turkey
+ 'UA': 'www.google.com.ua', # Ikraine
+ # 'CN': 'www.google.cn', # China, only from china ?
+ 'HK': 'www.google.com.hk', # Hong kong
+ 'TW': 'www.google.com.tw' # Taiwan
+}
+
+# osm
+url_map = 'https://www.openstreetmap.org/'\
+ + '?lat={latitude}&lon={longitude}&zoom={zoom}&layers=M'
# search-url
-google_hostname = 'www.google.com'
search_path = '/search'
-redirect_path = '/url'
-images_path = '/images'
-search_url = ('https://' +
- google_hostname +
+search_url = ('https://{hostname}' +
search_path +
'?{query}&start={offset}&gbv=1')
+# other URLs
+map_hostname_start = 'maps.google.'
+maps_path = '/maps'
+redirect_path = '/url'
+images_path = '/images'
+
# specific xpath variables
results_xpath = '//li[@class="g"]'
url_xpath = './/h3/a/@href'
title_xpath = './/h3'
content_xpath = './/span[@class="st"]'
+content_misc_xpath = './/div[@class="f slp"]'
suggestion_xpath = '//p[@class="_Bmc"]'
+# map : detail location
+map_address_xpath = './/div[@class="s"]//table//td[2]/span/text()'
+map_phone_xpath = './/div[@class="s"]//table//td[2]/span/span'
+map_website_url_xpath = 'h3[2]/a/@href'
+map_website_title_xpath = 'h3[2]'
+
+# map : near the location
+map_near = 'table[@class="ts"]//tr'
+map_near_title = './/h4'
+map_near_url = './/h4/a/@href'
+map_near_phone = './/span[@class="nobr"]'
+
+# images
images_xpath = './/div/a'
image_url_xpath = './@href'
image_img_src_xpath = './img/@src'
+# property names
+# FIXME : no translation
+property_address = "Address"
+property_phone = "Phone number"
+
+# cookies
pref_cookie = ''
+nid_cookie = {}
# see https://support.google.com/websearch/answer/873?hl=en
@@ -52,8 +139,21 @@ def get_google_pref_cookie():
return pref_cookie
+def get_google_nid_cookie(google_hostname):
+ global nid_cookie
+ if google_hostname not in nid_cookie:
+ resp = get('https://' + google_hostname)
+ nid_cookie[google_hostname] = resp.cookies.get("NID", None)
+ return nid_cookie[google_hostname]
+
+
# remove google-specific tracking-url
-def parse_url(url_string):
+def parse_url(url_string, google_hostname):
+ # sanity check
+ if url_string is None:
+ return url_string
+
+ # normal case
parsed_url = urlparse(url_string)
if (parsed_url.netloc in [google_hostname, '']
and parsed_url.path == redirect_path):
@@ -63,21 +163,45 @@ def parse_url(url_string):
return url_string
+# returns extract_text on the first result selected by the xpath or None
+def extract_text_from_dom(result, xpath):
+ r = result.xpath(xpath)
+ if len(r) > 0:
+ return extract_text(r[0])
+ return None
+
+
# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 10
if params['language'] == 'all':
language = 'en'
+ country = 'US'
else:
- language = params['language'].replace('_', '-').lower()
+ language_array = params['language'].lower().split('_')
+ if len(language_array) == 2:
+ country = language_array[1]
+ else:
+ country = 'US'
+ language = language_array[0] + ',' + language_array[0] + '-' + country
+
+ if use_locale_domain:
+ google_hostname = country_to_hostname.get(country.upper(), default_hostname)
+ else:
+ google_hostname = default_hostname
params['url'] = search_url.format(offset=offset,
- query=urlencode({'q': query}))
+ query=urlencode({'q': query}),
+ hostname=google_hostname)
params['headers']['Accept-Language'] = language
- if language.startswith('en'):
+ params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
+ if google_hostname == default_hostname:
params['cookies']['PREF'] = get_google_pref_cookie()
+ params['cookies']['NID'] = get_google_nid_cookie(google_hostname)
+
+ params['google_hostname'] = google_hostname
return params
@@ -86,33 +210,63 @@ def request(query, params):
def response(resp):
results = []
+ # detect google sorry
+ resp_url = urlparse(resp.url)
+ if resp_url.netloc == 'sorry.google.com' or resp_url.path == '/sorry/IndexRedirect':
+ raise RuntimeWarning('sorry.google.com')
+
+ # which hostname ?
+ google_hostname = resp.search_params.get('google_hostname')
+ google_url = "https://" + google_hostname
+
+ # convert the text to dom
dom = html.fromstring(resp.text)
# parse results
for result in dom.xpath(results_xpath):
title = extract_text(result.xpath(title_xpath)[0])
try:
- url = parse_url(extract_url(result.xpath(url_xpath), search_url))
- parsed_url = urlparse(url)
- if (parsed_url.netloc == google_hostname
- and parsed_url.path == search_path):
- # remove the link to google news
- continue
+ url = parse_url(extract_url(result.xpath(url_xpath), google_url), google_hostname)
+ parsed_url = urlparse(url, google_hostname)
+
+ # map result
+ if ((parsed_url.netloc == google_hostname and parsed_url.path.startswith(maps_path))
+ or (parsed_url.netloc.startswith(map_hostname_start))):
+ x = result.xpath(map_near)
+ if len(x) > 0:
+ # map : near the location
+ results = results + parse_map_near(parsed_url, x, google_hostname)
+ else:
+ # map : detail about a location
+ results = results + parse_map_detail(parsed_url, result, google_hostname)
+
+ # google news
+ elif (parsed_url.netloc == google_hostname
+ and parsed_url.path == search_path):
+ # skipping news results
+ pass
# images result
- if (parsed_url.netloc == google_hostname
- and parsed_url.path == images_path):
+ elif (parsed_url.netloc == google_hostname
+ and parsed_url.path == images_path):
# only thumbnail image provided,
# so skipping image results
- # results = results + parse_images(result)
+ # results = results + parse_images(result, google_hostname)
pass
+
else:
# normal result
- content = extract_text(result.xpath(content_xpath)[0])
+ content = extract_text_from_dom(result, content_xpath)
+ if content is None:
+ continue
+ content_misc = extract_text_from_dom(result, content_misc_xpath)
+ if content_misc is not None:
+ content = content_misc + "<br />" + content
# append result
results.append({'url': url,
'title': title,
- 'content': content})
+ 'content': content
+ })
except:
continue
@@ -125,10 +279,10 @@ def response(resp):
return results
-def parse_images(result):
+def parse_images(result, google_hostname):
results = []
for image in result.xpath(images_xpath):
- url = parse_url(extract_text(image.xpath(image_url_xpath)[0]))
+ url = parse_url(extract_text(image.xpath(image_url_xpath)[0]), google_hostname)
img_src = extract_text(image.xpath(image_img_src_xpath)[0])
# append result
@@ -136,6 +290,77 @@ def parse_images(result):
'title': '',
'content': '',
'img_src': img_src,
- 'template': 'images.html'})
+ 'template': 'images.html'
+ })
+
+ return results
+
+
+def parse_map_near(parsed_url, x, google_hostname):
+ results = []
+
+ for result in x:
+ title = extract_text_from_dom(result, map_near_title)
+ url = parse_url(extract_text_from_dom(result, map_near_url), google_hostname)
+ attributes = []
+ phone = extract_text_from_dom(result, map_near_phone)
+ add_attributes(attributes, property_phone, phone, 'tel:' + phone)
+ results.append({'title': title,
+ 'url': url,
+ 'content': attributes_to_html(attributes)
+ })
return results
+
+
+def parse_map_detail(parsed_url, result, google_hostname):
+ results = []
+
+ # try to parse the geoloc
+ m = re.search('@([0-9\.]+),([0-9\.]+),([0-9]+)', parsed_url.path)
+ if m is None:
+ m = re.search('ll\=([0-9\.]+),([0-9\.]+)\&z\=([0-9]+)', parsed_url.query)
+
+ if m is not None:
+ # geoloc found (ignored)
+ lon = float(m.group(2)) # noqa
+ lat = float(m.group(1)) # noqa
+ zoom = int(m.group(3)) # noqa
+
+ # attributes
+ attributes = []
+ address = extract_text_from_dom(result, map_address_xpath)
+ phone = extract_text_from_dom(result, map_phone_xpath)
+ add_attributes(attributes, property_address, address, 'geo:' + str(lat) + ',' + str(lon))
+ add_attributes(attributes, property_phone, phone, 'tel:' + phone)
+
+ # title / content / url
+ website_title = extract_text_from_dom(result, map_website_title_xpath)
+ content = extract_text_from_dom(result, content_xpath)
+ website_url = parse_url(extract_text_from_dom(result, map_website_url_xpath), google_hostname)
+
+ # add a result if there is a website
+ if website_url is not None:
+ results.append({'title': website_title,
+ 'content': (content + '<br />' if content is not None else '')
+ + attributes_to_html(attributes),
+ 'url': website_url
+ })
+
+ return results
+
+
+def add_attributes(attributes, name, value, url):
+ if value is not None and len(value) > 0:
+ attributes.append({'label': name, 'value': value, 'url': url})
+
+
+def attributes_to_html(attributes):
+ retval = '<table class="table table-striped">'
+ for a in attributes:
+ value = a.get('value')
+ if 'url' in a:
+ value = '<a href="' + a.get('url') + '">' + value + '</a>'
+ retval = retval + '<tr><th>' + a.get('label') + '</th><td>' + value + '</td></tr>'
+ retval = retval + '</table>'
+ return retval
diff --git a/searx/engines/qwant.py b/searx/engines/qwant.py
new file mode 100644
index 000000000..872bd4e95
--- /dev/null
+++ b/searx/engines/qwant.py
@@ -0,0 +1,98 @@
+"""
+ Qwant (Web, Images, News, Social)
+
+ @website https://qwant.com/
+ @provide-api not officially (https://api.qwant.com/api/search/)
+
+ @using-api yes
+ @results JSON
+ @stable yes
+ @parse url, title, content
+"""
+
+from urllib import urlencode
+from json import loads
+from datetime import datetime
+
+# engine dependent config
+categories = None
+paging = True
+language_support = True
+
+category_to_keyword = {'general': 'web',
+ 'images': 'images',
+ 'news': 'news',
+ 'social media': 'social'}
+
+# search-url
+url = 'https://api.qwant.com/api/search/{keyword}?count=10&offset={offset}&f=&{query}'
+
+
+# do search-request
+def request(query, params):
+ offset = (params['pageno'] - 1) * 10
+
+ if categories[0] and categories[0] in category_to_keyword:
+
+ params['url'] = url.format(keyword=category_to_keyword[categories[0]],
+ query=urlencode({'q': query}),
+ offset=offset)
+ else:
+ params['url'] = url.format(keyword='web',
+ query=urlencode({'q': query}),
+ offset=offset)
+
+ # add language tag if specified
+ if params['language'] != 'all':
+ params['url'] += '&locale=' + params['language'].lower()
+
+ return params
+
+
+# get response from search-request
+def response(resp):
+ results = []
+
+ search_results = loads(resp.text)
+
+ # return empty array if there are no results
+ if 'data' not in search_results:
+ return []
+
+ data = search_results.get('data', {})
+
+ res = data.get('result', {})
+
+ # parse results
+ for result in res.get('items', {}):
+
+ title = result['title']
+ res_url = result['url']
+ content = result['desc']
+
+ if category_to_keyword.get(categories[0], '') == 'web':
+ results.append({'title': title,
+ 'content': content,
+ 'url': res_url})
+
+ elif category_to_keyword.get(categories[0], '') == 'images':
+ thumbnail_src = result['thumbnail']
+ img_src = result['media']
+ results.append({'template': 'images.html',
+ 'url': res_url,
+ 'title': title,
+ 'content': '',
+ 'thumbnail_src': thumbnail_src,
+ 'img_src': img_src})
+
+ elif (category_to_keyword.get(categories[0], '') == 'news' or
+ category_to_keyword.get(categories[0], '') == 'social'):
+ published_date = datetime.fromtimestamp(result['date'], None)
+
+ results.append({'url': res_url,
+ 'title': title,
+ 'publishedDate': published_date,
+ 'content': content})
+
+ # return results
+ return results
diff --git a/searx/engines/swisscows.py b/searx/engines/swisscows.py
new file mode 100644
index 000000000..2d31264ca
--- /dev/null
+++ b/searx/engines/swisscows.py
@@ -0,0 +1,108 @@
+"""
+ Swisscows (Web, Images)
+
+ @website https://swisscows.ch
+ @provide-api no
+
+ @using-api no
+ @results HTML (using search portal)
+ @stable no (HTML can change)
+ @parse url, title, content
+"""
+
+from json import loads
+from urllib import urlencode, unquote
+import re
+
+# engine dependent config
+categories = ['general', 'images']
+paging = True
+language_support = True
+
+# search-url
+base_url = 'https://swisscows.ch/'
+search_string = '?{query}&page={page}'
+
+# regex
+regex_json = re.compile('initialData: {"Request":(.|\n)*},\s*environment')
+regex_json_remove_start = re.compile('^initialData:\s*')
+regex_json_remove_end = re.compile(',\s*environment$')
+regex_img_url_remove_start = re.compile('^https?://i\.swisscows\.ch/\?link=')
+
+
+# do search-request
+def request(query, params):
+ if params['language'] == 'all':
+ ui_language = 'browser'
+ region = 'browser'
+ else:
+ region = params['language'].replace('_', '-')
+ ui_language = params['language'].split('_')[0]
+
+ search_path = search_string.format(
+ query=urlencode({'query': query,
+ 'uiLanguage': ui_language,
+ 'region': region}),
+ page=params['pageno'])
+
+ # image search query is something like 'image?{query}&page={page}'
+ if params['category'] == 'images':
+ search_path = 'image' + search_path
+
+ params['url'] = base_url + search_path
+
+ return params
+
+
+# get response from search-request
+def response(resp):
+ results = []
+
+ json_regex = regex_json.search(resp.content)
+
+ # check if results are returned
+ if not json_regex:
+ return []
+
+ json_raw = regex_json_remove_end.sub('', regex_json_remove_start.sub('', json_regex.group()))
+ json = loads(json_raw)
+
+ # parse results
+ for result in json['Results'].get('items', []):
+ result_title = result['Title'].replace(u'\uE000', '').replace(u'\uE001', '')
+
+ # parse image results
+ if result.get('ContentType', '').startswith('image'):
+ img_url = unquote(regex_img_url_remove_start.sub('', result['Url']))
+
+ # append result
+ results.append({'url': result['SourceUrl'],
+ 'title': result['Title'],
+ 'content': '',
+ 'img_src': img_url,
+ 'template': 'images.html'})
+
+ # parse general results
+ else:
+ result_url = result['Url'].replace(u'\uE000', '').replace(u'\uE001', '')
+ result_content = result['Description'].replace(u'\uE000', '').replace(u'\uE001', '')
+
+ # append result
+ results.append({'url': result_url,
+ 'title': result_title,
+ 'content': result_content})
+
+ # parse images
+ for result in json.get('Images', []):
+ # decode image url
+ img_url = unquote(regex_img_url_remove_start.sub('', result['Url']))
+
+ # append result
+ results.append({'url': result['SourceUrl'],
+ 'title': result['Title'],
+ 'content': '',
+ 'img_src': img_url,
+ 'template': 'images.html'})
+
+ # return results
+ return results
diff --git a/searx/engines/vimeo.py b/searx/engines/vimeo.py
index 0dcc65b7c..517ac1c44 100644
--- a/searx/engines/vimeo.py
+++ b/searx/engines/vimeo.py
@@ -27,11 +27,11 @@ base_url = 'https://vimeo.com'
search_url = base_url + '/search/page:{pageno}?{query}'
# specific xpath variables
-results_xpath = '//div[@id="browse_content"]/ol/li'
-url_xpath = './a/@href'
-title_xpath = './a/div[@class="data"]/p[@class="title"]'
-content_xpath = './a/img/@src'
-publishedDate_xpath = './/p[@class="meta"]//attribute::datetime'
+results_xpath = '//div[contains(@class,"results_grid")]/ul/li'
+url_xpath = './/a/@href'
+title_xpath = './/span[@class="title"]'
+thumbnail_xpath = './/img[@class="js-clip_thumbnail_image"]/@src'
+publishedDate_xpath = './/time/attribute::datetime'
embedded_url = '<iframe data-src="//player.vimeo.com/video{videoid}" ' +\
'width="540" height="304" frameborder="0" ' +\
@@ -58,7 +58,7 @@ def response(resp):
videoid = result.xpath(url_xpath)[0]
url = base_url + videoid
title = p.unescape(extract_text(result.xpath(title_xpath)))
- thumbnail = extract_text(result.xpath(content_xpath)[0])
+ thumbnail = extract_text(result.xpath(thumbnail_xpath)[0])
publishedDate = parser.parse(extract_text(result.xpath(publishedDate_xpath)[0]))
embedded = embedded_url.format(videoid=videoid)
diff --git a/searx/engines/www1x.py b/searx/engines/www1x.py
index 12868ad22..ddb79bfea 100644
--- a/searx/engines/www1x.py
+++ b/searx/engines/www1x.py
@@ -20,8 +20,8 @@ import re
categories = ['images']
paging = False
-# search-url, no HTTPS (there is a valid certificate for https://api2.1x.com/ )
-base_url = 'http://1x.com'
+# search-url
+base_url = 'https://1x.com'
search_url = base_url+'/backend/search.php?{query}'
diff --git a/searx/engines/youtube_api.py b/searx/engines/youtube_api.py
new file mode 100644
index 000000000..8fd939a25
--- /dev/null
+++ b/searx/engines/youtube_api.py
@@ -0,0 +1,83 @@
+# Youtube (Videos)
+#
+# @website https://www.youtube.com/
+# @provide-api yes (https://developers.google.com/apis-explorer/#p/youtube/v3/youtube.search.list)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse url, title, content, publishedDate, thumbnail, embedded
+
+from json import loads
+from urllib import urlencode
+from dateutil import parser
+
+# engine dependent config
+categories = ['videos', 'music']
+paging = False
+language_support = True
+api_key = None
+
+# search-url
+base_url = 'https://www.googleapis.com/youtube/v3/search'
+search_url = base_url + '?part=snippet&{query}&maxResults=20&key={api_key}'
+
+embedded_url = '<iframe width="540" height="304" ' +\
+ 'data-src="//www.youtube-nocookie.com/embed/{videoid}" ' +\
+ 'frameborder="0" allowfullscreen></iframe>'
+
+base_youtube_url = 'https://www.youtube.com/watch?v='
+
+
+# do search-request
+def request(query, params):
+ params['url'] = search_url.format(query=urlencode({'q': query}),
+ api_key=api_key)
+
+ # add language tag if specified
+ if params['language'] != 'all':
+ params['url'] += '&relevanceLanguage=' + params['language'].split('_')[0]
+
+ return params
+
+
+# get response from search-request
+def response(resp):
+ results = []
+
+ search_results = loads(resp.text)
+
+ # return empty array if there are no results
+ if 'items' not in search_results:
+ return []
+
+ # parse results
+ for result in search_results['items']:
+ videoid = result['id']['videoId']
+
+ title = result['snippet']['title']
+ content = ''
+ thumbnail = ''
+
+ pubdate = result['snippet']['publishedAt']
+ publishedDate = parser.parse(pubdate)
+
+ thumbnail = result['snippet']['thumbnails']['high']['url']
+
+ content = result['snippet']['description']
+
+ url = base_youtube_url + videoid
+
+ embedded = embedded_url.format(videoid=videoid)
+
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content,
+ 'template': 'videos.html',
+ 'publishedDate': publishedDate,
+ 'embedded': embedded,
+ 'thumbnail': thumbnail})
+
+ # return results
+ return results
diff --git a/searx/engines/youtube_noapi.py b/searx/engines/youtube_noapi.py
new file mode 100644
index 000000000..401fca4c9
--- /dev/null
+++ b/searx/engines/youtube_noapi.py
@@ -0,0 +1,81 @@
+# Youtube (Videos)
+#
+# @website https://www.youtube.com/
+# @provide-api yes (https://developers.google.com/apis-explorer/#p/youtube/v3/youtube.search.list)
+#
+# @using-api no
+# @results HTML
+# @stable no
+# @parse url, title, content, publishedDate, thumbnail, embedded
+
+from urllib import quote_plus
+from lxml import html
+from searx.engines.xpath import extract_text
+from searx.utils import list_get
+
+# engine dependent config
+categories = ['videos', 'music']
+paging = True
+language_support = False
+
+# search-url
+base_url = 'https://www.youtube.com/results'
+search_url = base_url + '?search_query={query}&page={page}'
+
+embedded_url = '<iframe width="540" height="304" ' +\
+ 'data-src="//www.youtube-nocookie.com/embed/{videoid}" ' +\
+ 'frameborder="0" allowfullscreen></iframe>'
+
+base_youtube_url = 'https://www.youtube.com/watch?v='
+
+# specific xpath variables
+results_xpath = "//ol/li/div[contains(@class, 'yt-lockup yt-lockup-tile yt-lockup-video vve-check')]"
+url_xpath = './/h3/a/@href'
+title_xpath = './/div[@class="yt-lockup-content"]/h3/a'
+content_xpath = './/div[@class="yt-lockup-content"]/div[@class="yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2"]'
+
+
+# returns extract_text on the first result selected by the xpath or None
+def extract_text_from_dom(result, xpath):
+ r = result.xpath(xpath)
+ if len(r) > 0:
+ return extract_text(r[0])
+ return None
+
+
+# do search-request
+def request(query, params):
+ params['url'] = search_url.format(query=quote_plus(query),
+ page=params['pageno'])
+
+ return params
+
+
+# get response from search-request
+def response(resp):
+ results = []
+
+ dom = html.fromstring(resp.text)
+
+ # parse results
+ for result in dom.xpath(results_xpath):
+ videoid = list_get(result.xpath('@data-context-item-id'), 0)
+ if videoid is not None:
+ url = base_youtube_url + videoid
+ thumbnail = 'https://i.ytimg.com/vi/' + videoid + '/hqdefault.jpg'
+
+ title = extract_text_from_dom(result, title_xpath) or videoid
+ content = extract_text_from_dom(result, content_xpath)
+
+ embedded = embedded_url.format(videoid=videoid)
+
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content,
+ 'template': 'videos.html',
+ 'embedded': embedded,
+ 'thumbnail': thumbnail})
+
+ # return results
+ return results
diff --git a/searx/plugins/__init__.py b/searx/plugins/__init__.py
index 5ac3f447c..a4d7ad8a8 100644
--- a/searx/plugins/__init__.py
+++ b/searx/plugins/__init__.py
@@ -20,8 +20,9 @@ from searx import logger
logger = logger.getChild('plugins')
from searx.plugins import (https_rewrite,
- self_ip,
- search_on_category_select)
+ self_info,
+ search_on_category_select,
+ tracker_url_remover)
required_attrs = (('name', str),
('description', str),
@@ -71,5 +72,6 @@ class PluginStore():
plugins = PluginStore()
plugins.register(https_rewrite)
-plugins.register(self_ip)
+plugins.register(self_info)
plugins.register(search_on_category_select)
+plugins.register(tracker_url_remover)
diff --git a/searx/plugins/self_ip.py b/searx/plugins/self_info.py
index 5184ea4cf..5ca994526 100644
--- a/searx/plugins/self_ip.py
+++ b/searx/plugins/self_info.py
@@ -15,11 +15,16 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
(C) 2015 by Adam Tauber, <asciimoo@gmail.com>
'''
from flask.ext.babel import gettext
-name = "Self IP"
-description = gettext('Display your source IP address if the query expression is "ip"')
+import re
+name = "Self Informations"
+description = gettext('Displays your IP if the query is "ip" and your user agent if the query contains "user agent".')
default_on = True
+# Self User Agent regex
+p = re.compile('.*user[ -]agent.*', re.IGNORECASE)
+
+
# attach callback to the post search hook
# request: flask request object
# ctx: the whole local context of the pre search hook
@@ -32,4 +37,8 @@ def post_search(request, ctx):
ip = request.remote_addr
ctx['search'].answers.clear()
ctx['search'].answers.add(ip)
+ elif p.match(ctx['search'].query):
+ ua = request.user_agent
+ ctx['search'].answers.clear()
+ ctx['search'].answers.add(ua)
return True
diff --git a/searx/plugins/tracker_url_remover.py b/searx/plugins/tracker_url_remover.py
new file mode 100644
index 000000000..ed71c94d3
--- /dev/null
+++ b/searx/plugins/tracker_url_remover.py
@@ -0,0 +1,44 @@
+'''
+searx is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+searx is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with searx. If not, see < http://www.gnu.org/licenses/ >.
+
+(C) 2015 by Adam Tauber, <asciimoo@gmail.com>
+'''
+
+from flask.ext.babel import gettext
+import re
+from urlparse import urlunparse
+
+regexes = {re.compile(r'utm_[^&]+&?'),
+ re.compile(r'(wkey|wemail)[^&]+&?'),
+ re.compile(r'&$')}
+
+name = gettext('Tracker URL remover')
+description = gettext('Remove trackers arguments from the returned URL')
+default_on = True
+
+
+def on_result(request, ctx):
+ query = ctx['result']['parsed_url'].query
+
+ if query == "":
+ return True
+
+ for reg in regexes:
+ query = reg.sub('', query)
+
+ if query != ctx['result']['parsed_url'].query:
+ ctx['result']['parsed_url'] = ctx['result']['parsed_url']._replace(query=query)
+ ctx['result']['url'] = urlunparse(ctx['result']['parsed_url'])
+
+ return True
diff --git a/searx/search.py b/searx/search.py
index e7ac7bb66..bb440352b 100644
--- a/searx/search.py
+++ b/searx/search.py
@@ -237,7 +237,7 @@ def score_results(results):
for k in categoryPositions:
v = categoryPositions[k]['index']
if v >= index:
- categoryPositions[k]['index'] = v+1
+ categoryPositions[k]['index'] = v + 1
# update this category
current['count'] -= 1
@@ -306,7 +306,7 @@ def merge_infoboxes(infoboxes):
if add_infobox:
results.append(infobox)
- infoboxes_id[infobox_id] = len(results)-1
+ infoboxes_id[infobox_id] = len(results) - 1
return results
@@ -472,7 +472,12 @@ class Search(object):
request_params['category'] = selected_engine['category']
request_params['started'] = time()
request_params['pageno'] = self.pageno
- request_params['language'] = self.lang
+
+ if hasattr(engine, 'language'):
+ request_params['language'] = engine.language
+ else:
+ request_params['language'] = self.lang
+
try:
# 0 = None, 1 = Moderate, 2 = Strict
request_params['safesearch'] = int(request.cookies.get('safesearch', 1))
diff --git a/searx/settings.yml b/searx/settings.yml
index 98a2ed786..03d895363 100644
--- a/searx/settings.yml
+++ b/searx/settings.yml
@@ -169,6 +169,27 @@ engines:
shortcut : tpb
disabled : True
+ - name : qwant
+ engine : qwant
+ shortcut : qw
+ categories : general
+ disabled : True
+
+ - name : qwant images
+ engine : qwant
+ shortcut : qwi
+ categories : images
+
+ - name : qwant news
+ engine : qwant
+ shortcut : qwn
+ categories : news
+
+ - name : qwant social
+ engine : qwant
+ shortcut : qws
+ categories : social media
+
- name : kickass
engine : kickass
shortcut : ka
@@ -204,12 +225,21 @@ engines:
- name : startpage
engine : startpage
shortcut : sp
+ timeout : 6.0
+ disabled : True
+
+ - name : ixquick
+ engine : startpage
+ base_url : 'https://www.ixquick.com/'
+ search_url : 'https://www.ixquick.com/do/search'
+ shortcut : iq
+ timeout : 6.0
+ disabled : True
-# +30% page load time
-# - name : ixquick
-# engine : startpage
-# base_url : 'https://www.ixquick.com/'
-# search_url : 'https://www.ixquick.com/do/search'
+ - name : swisscows
+ engine : swisscows
+ shortcut : sw
+ disabled : True
- name : twitter
engine : twitter
@@ -240,8 +270,13 @@ engines:
shortcut : yhn
- name : youtube
- engine : youtube
shortcut : yt
+ # You can use the engine using the official stable API, but you need an API key
+ # See : https://console.developers.google.com/project
+ # engine : youtube_api
+ # api_key: 'apikey' # required!
+ # Or you can use the html non-stable engine, activated by default
+ engine : youtube_noapi
- name : dailymotion
engine : dailymotion
@@ -275,5 +310,6 @@ locales:
nl : Nederlands
ja : 日本語 (Japanese)
tr : Türkçe
+ pt: Português
ru : Russian
ro : Romanian
diff --git a/searx/static/js/search_on_category_select.js b/searx/static/js/search_on_category_select.js
index 6156ca4e8..5ecc2cdb9 100644
--- a/searx/static/js/search_on_category_select.js
+++ b/searx/static/js/search_on_category_select.js
@@ -1,5 +1,5 @@
$(document).ready(function() {
- if($('#q')) {
+ if($('#q').length) {
$('#categories label').click(function(e) {
$('#categories input[type="checkbox"]').each(function(i, checkbox) {
$(checkbox).prop('checked', false);
@@ -7,7 +7,9 @@ $(document).ready(function() {
$('#categories label').removeClass('btn-primary').removeClass('active').addClass('btn-default');
$(this).removeClass('btn-default').addClass('btn-primary').addClass('active');
$($(this).children()[0]).prop('checked', 'checked');
- $('#search_form').submit();
+ if($('#q').val()) {
+ $('#search_form').submit();
+ }
return false;
});
}
diff --git a/searx/static/themes/courgette/img/favicon.png b/searx/static/themes/courgette/img/favicon.png
index cefbac496..1a43d7fa6 100644
--- a/searx/static/themes/courgette/img/favicon.png
+++ b/searx/static/themes/courgette/img/favicon.png
Binary files differ
diff --git a/searx/static/themes/courgette/img/github_ribbon.png b/searx/static/themes/courgette/img/github_ribbon.png
index 146ef8a80..3799c2ea1 100644
--- a/searx/static/themes/courgette/img/github_ribbon.png
+++ b/searx/static/themes/courgette/img/github_ribbon.png
Binary files differ
diff --git a/searx/static/themes/courgette/img/preference-icon.png b/searx/static/themes/courgette/img/preference-icon.png
index 039db04a8..57e991cc6 100644
--- a/searx/static/themes/courgette/img/preference-icon.png
+++ b/searx/static/themes/courgette/img/preference-icon.png
Binary files differ
diff --git a/searx/static/themes/courgette/img/search-icon.png b/searx/static/themes/courgette/img/search-icon.png
index 52c267842..9bc7a222c 100644
--- a/searx/static/themes/courgette/img/search-icon.png
+++ b/searx/static/themes/courgette/img/search-icon.png
Binary files differ
diff --git a/searx/static/themes/courgette/img/searx-mobile.png b/searx/static/themes/courgette/img/searx-mobile.png
index 2b9383a5d..b5af3865c 100644
--- a/searx/static/themes/courgette/img/searx-mobile.png
+++ b/searx/static/themes/courgette/img/searx-mobile.png
Binary files differ
diff --git a/searx/static/themes/courgette/img/searx.png b/searx/static/themes/courgette/img/searx.png
index e162da502..68c2e4ffd 100644
--- a/searx/static/themes/courgette/img/searx.png
+++ b/searx/static/themes/courgette/img/searx.png
Binary files differ
diff --git a/searx/static/themes/default/img/favicon.png b/searx/static/themes/default/img/favicon.png
index 28afb0111..1a43d7fa6 100644
--- a/searx/static/themes/default/img/favicon.png
+++ b/searx/static/themes/default/img/favicon.png
Binary files differ
diff --git a/searx/static/themes/default/img/github_ribbon.png b/searx/static/themes/default/img/github_ribbon.png
index 146ef8a80..3799c2ea1 100644
--- a/searx/static/themes/default/img/github_ribbon.png
+++ b/searx/static/themes/default/img/github_ribbon.png
Binary files differ
diff --git a/searx/static/themes/default/img/preference-icon.png b/searx/static/themes/default/img/preference-icon.png
index 300279d24..8bdee641d 100644
--- a/searx/static/themes/default/img/preference-icon.png
+++ b/searx/static/themes/default/img/preference-icon.png
Binary files differ
diff --git a/searx/static/themes/default/img/searx.png b/searx/static/themes/default/img/searx.png
index e69e9eef9..a98f12a1d 100644
--- a/searx/static/themes/default/img/searx.png
+++ b/searx/static/themes/default/img/searx.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/favicon.png b/searx/static/themes/oscar/img/favicon.png
index cefbac496..1a43d7fa6 100644
--- a/searx/static/themes/oscar/img/favicon.png
+++ b/searx/static/themes/oscar/img/favicon.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/icons/github.png b/searx/static/themes/oscar/img/icons/github.png
index bf09bae55..e6439715b 100644
--- a/searx/static/themes/oscar/img/icons/github.png
+++ b/searx/static/themes/oscar/img/icons/github.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/icons/searchcode code.png b/searx/static/themes/oscar/img/icons/searchcode code.png
index 884c2660d..968a7ce27 100644
--- a/searx/static/themes/oscar/img/icons/searchcode code.png
+++ b/searx/static/themes/oscar/img/icons/searchcode code.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/icons/searchcode doc.png b/searx/static/themes/oscar/img/icons/searchcode doc.png
index 884c2660d..968a7ce27 100644
--- a/searx/static/themes/oscar/img/icons/searchcode doc.png
+++ b/searx/static/themes/oscar/img/icons/searchcode doc.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/icons/wikipedia.png b/searx/static/themes/oscar/img/icons/wikipedia.png
index 37997fbc7..f77168382 100644
--- a/searx/static/themes/oscar/img/icons/wikipedia.png
+++ b/searx/static/themes/oscar/img/icons/wikipedia.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/icons/youtube.png b/searx/static/themes/oscar/img/icons/youtube.png
index ba2484d0f..de64dff7b 100644
--- a/searx/static/themes/oscar/img/icons/youtube.png
+++ b/searx/static/themes/oscar/img/icons/youtube.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/layers-2x.png b/searx/static/themes/oscar/img/map/layers-2x.png
index a2cf7f9ef..0b30da678 100644
--- a/searx/static/themes/oscar/img/map/layers-2x.png
+++ b/searx/static/themes/oscar/img/map/layers-2x.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/layers.png b/searx/static/themes/oscar/img/map/layers.png
index bca0a0e42..4297fd9ee 100644
--- a/searx/static/themes/oscar/img/map/layers.png
+++ b/searx/static/themes/oscar/img/map/layers.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/marker-icon-2x-green.png b/searx/static/themes/oscar/img/map/marker-icon-2x-green.png
index c359abb6c..7446bb031 100644
--- a/searx/static/themes/oscar/img/map/marker-icon-2x-green.png
+++ b/searx/static/themes/oscar/img/map/marker-icon-2x-green.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/marker-icon-2x-orange.png b/searx/static/themes/oscar/img/map/marker-icon-2x-orange.png
index c3c863211..ecd67736f 100644
--- a/searx/static/themes/oscar/img/map/marker-icon-2x-orange.png
+++ b/searx/static/themes/oscar/img/map/marker-icon-2x-orange.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/marker-icon-2x-red.png b/searx/static/themes/oscar/img/map/marker-icon-2x-red.png
index 1c26e9fc2..1d2e197c6 100644
--- a/searx/static/themes/oscar/img/map/marker-icon-2x-red.png
+++ b/searx/static/themes/oscar/img/map/marker-icon-2x-red.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/marker-icon-green.png b/searx/static/themes/oscar/img/map/marker-icon-green.png
index 56db5ea9f..f48ef41df 100644
--- a/searx/static/themes/oscar/img/map/marker-icon-green.png
+++ b/searx/static/themes/oscar/img/map/marker-icon-green.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/marker-icon-orange.png b/searx/static/themes/oscar/img/map/marker-icon-orange.png
index fbbce7b2a..d0d22205c 100644
--- a/searx/static/themes/oscar/img/map/marker-icon-orange.png
+++ b/searx/static/themes/oscar/img/map/marker-icon-orange.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/map/marker-icon-red.png b/searx/static/themes/oscar/img/map/marker-icon-red.png
index 3e64e06d1..7a92b9e04 100644
--- a/searx/static/themes/oscar/img/map/marker-icon-red.png
+++ b/searx/static/themes/oscar/img/map/marker-icon-red.png
Binary files differ
diff --git a/searx/static/themes/oscar/img/searx_logo.png b/searx/static/themes/oscar/img/searx_logo.png
index 26be6def7..ea4837b05 100644
--- a/searx/static/themes/oscar/img/searx_logo.png
+++ b/searx/static/themes/oscar/img/searx_logo.png
Binary files differ
diff --git a/searx/static/themes/pix-art/img/favicon.png b/searx/static/themes/pix-art/img/favicon.png
index 28afb0111..1a43d7fa6 100644
--- a/searx/static/themes/pix-art/img/favicon.png
+++ b/searx/static/themes/pix-art/img/favicon.png
Binary files differ
diff --git a/searx/static/themes/pix-art/img/searx-pixel-small.png b/searx/static/themes/pix-art/img/searx-pixel-small.png
index 76f381c5c..75b476c7f 100644
--- a/searx/static/themes/pix-art/img/searx-pixel-small.png
+++ b/searx/static/themes/pix-art/img/searx-pixel-small.png
Binary files differ
diff --git a/searx/templates/courgette/preferences.html b/searx/templates/courgette/preferences.html
index 2afb74d11..f89915d8d 100644
--- a/searx/templates/courgette/preferences.html
+++ b/searx/templates/courgette/preferences.html
@@ -101,7 +101,7 @@
<th>{{ _('Category') }}</th>
<th>{{ _('Allow') }} / {{ _('Block') }}</th>
</tr>
- {% for categ in categories %}
+ {% for categ in all_categories %}
{% for search_engine in engines_by_category[categ] %}
{% if not search_engine.private %}
diff --git a/searx/templates/default/preferences.html b/searx/templates/default/preferences.html
index 0afe9f7d0..90006c029 100644
--- a/searx/templates/default/preferences.html
+++ b/searx/templates/default/preferences.html
@@ -89,7 +89,7 @@
<th>{{ _('Category') }}</th>
<th>{{ _('Allow') }} / {{ _('Block') }}</th>
</tr>
- {% for categ in categories %}
+ {% for categ in all_categories %}
{% for search_engine in engines_by_category[categ] %}
{% if not search_engine.private %}
diff --git a/searx/templates/oscar/messages/no_cookies.html b/searx/templates/oscar/messages/no_cookies.html
new file mode 100644
index 000000000..9bebc8ad1
--- /dev/null
+++ b/searx/templates/oscar/messages/no_cookies.html
@@ -0,0 +1,5 @@
+{% from 'oscar/macros.html' import icon %}
+<div class="alert alert-info fade in" role="alert">
+ <strong class="lead">{{ icon('info-sign') }} {{ _('Information!') }}</strong>
+ {{ _('currently, there are no cookies defined.') }}
+</div>
diff --git a/searx/templates/oscar/preferences.html b/searx/templates/oscar/preferences.html
index 693167807..ea36a14b9 100644
--- a/searx/templates/oscar/preferences.html
+++ b/searx/templates/oscar/preferences.html
@@ -117,7 +117,7 @@
<!-- Nav tabs -->
<ul class="nav nav-tabs nav-justified hide_if_nojs" role="tablist" style="margin-bottom:20px;">
- {% for categ in categories %}
+ {% for categ in all_categories %}
<li{% if loop.first %} class="active"{% endif %}><a href="#tab_engine_{{ categ|replace(' ', '_') }}" role="tab" data-toggle="tab">{{ _(categ) }}</a></li>
{% endfor %}
</ul>
@@ -128,27 +128,54 @@
<!-- Tab panes -->
<div class="tab-content">
- {% for categ in categories %}
+ {% for categ in all_categories %}
<noscript><label>{{ _(categ) }}</label>
</noscript>
<div class="tab-pane{% if loop.first %} active{% endif %} active_if_nojs" id="tab_engine_{{ categ|replace(' ', '_') }}">
<div class="container-fluid">
<fieldset>
+ <div class="table-responsive">
+ <table class="table table-hover table-condensed table-striped">
+ <tr>
+ {% if not rtl %}
+ <th>{{ _("Allow") }}</th>
+ <th>{{ _("Engine name") }}</th>
+ <th>{{ _("Shortcut") }}</th>
+ <th>{{ _("SafeSearch") }}</th>
+ <th>{{ _("Avg. time") }}</th>
+ <th>{{ _("Max time") }}</th>
+ {% else %}
+ <th>{{ _("Max time") }}</th>
+ <th>{{ _("Avg. time") }}</th>
+ <th>{{ _("SafeSearch") }}</th>
+ <th>{{ _("Shortcut") }}</th>
+ <th>{{ _("Engine name") }}</th>
+ <th>{{ _("Allow") }}</th>
+ {% endif %}
+ </tr>
{% for search_engine in engines_by_category[categ] %}
{% if not search_engine.private %}
- <div class="row">
+ <tr>
{% if not rtl %}
- <div class="col-xs-6 col-sm-4 col-md-4">{{ search_engine.name }} ({{ shortcuts[search_engine.name] }})</div>
+ <td>{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in blocked_engines) }}</td>
+ <th>{{ search_engine.name }}</th>
+ <td>{{ shortcuts[search_engine.name] }}</td>
+ <td><input type="checkbox" {{ "checked" if search_engine.safesearch==True else ""}} readonly="readonly" disabled="disabled"></td>
+ <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td>
+ <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td>
+ {% else %}
+ <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td>
+ <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td>
+ <td><input type="checkbox" {{ "checked" if search_engine.safesearch==True else ""}} readonly="readonly" disabled="disabled"></td>
+ <td>{{ shortcuts[search_engine.name] }}</td>
+ <th>{{ search_engine.name }}</th>
+ <td>{{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in blocked_engines) }}</td>
{% endif %}
- <div class="col-xs-6 col-sm-4 col-md-4">
- {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in blocked_engines) }}
- </div>
- {% if rtl %}
- <div class="col-xs-6 col-sm-4 col-md-4">{{ search_engine.name }} ({{ shortcuts[search_engine.name] }})&lrm;</div>
- {% endif %}
- </div>
+ </tr>
{% endif %}
{% endfor %}
+ </table>
+ </div>
</fieldset>
</div>
</div>
@@ -186,21 +213,23 @@
{{ _('This is the list of cookies and their values searx is storing on your computer.') }}<br />
{{ _('With that list, you can assess searx transparency.') }}<br />
</p>
- <div class="container-fluid">
- <fieldset>
- <div class="row">
- <div class="col-xs-6 col-sm-4 col-md-4 text-muted"><label>{{ _('Cookie name') }}</label></div>
- <div class="col-xs-6 col-sm-4 col-md-4 text-muted"><label>{{ _('Value') }}</label></div>
- </div>
+ {% if cookies %}
+ <table class="table table-striped">
+ <tr>
+ <th class="text-muted" style="padding-right:40px;">{{ _('Cookie name') }}</th>
+ <th class="text-muted">{{ _('Value') }}</th>
+ </tr>
{% for cookie in cookies %}
- <div class="row">
- <div class="col-xs-6 col-sm-4 col-md-4 text-muted">{{ cookie }}</div>
- <div class="col-xs-6 col-sm-4 col-md-4 text-muted">{{ cookies[cookie] }}</div>
- </div>
+ <tr>
+ <td class="text-muted" style="padding-right:40px;">{{ cookie }}</td>
+ <td class="text-muted">{{ cookies[cookie] }}</td>
+ </tr>
{% endfor %}
- </fieldset>
- </div>
+ </table>
+ {% else %}
+ {% include 'oscar/messages/no_cookies.html' %}
+ {% endif %}
</div>
</div>
<p class="text-muted" style="margin:20px 0;">{{ _('These settings are stored in your cookies, this allows us not to store this data about you.') }}
diff --git a/searx/tests/engines/test_bing_images.py b/searx/tests/engines/test_bing_images.py
index a1d96b06e..f869da79d 100644
--- a/searx/tests/engines/test_bing_images.py
+++ b/searx/tests/engines/test_bing_images.py
@@ -59,7 +59,7 @@ oh:&quot;238&quot;,tft:&quot;0&quot;,oi:&quot;http://www.image.url/Images/Test%2
self.assertEqual(results[0]['title'], 'Test Query')
self.assertEqual(results[0]['url'], 'http://www.page.url/')
self.assertEqual(results[0]['content'], '')
- self.assertEqual(results[0]['thumbnail_src'], 'http://ts1.mm.bing.net/th?id=HN.608003696942779811')
+ self.assertEqual(results[0]['thumbnail_src'], 'https://www.bing.com/th?id=HN.608003696942779811')
self.assertEqual(results[0]['img_src'], 'http://test.url/Test%20Query.jpg')
html = """
diff --git a/searx/tests/engines/test_bing_news.py b/searx/tests/engines/test_bing_news.py
index f22b80e87..a64d59b7b 100644
--- a/searx/tests/engines/test_bing_news.py
+++ b/searx/tests/engines/test_bing_news.py
@@ -2,6 +2,7 @@ from collections import defaultdict
import mock
from searx.engines import bing_news
from searx.testing import SearxTestCase
+import lxml
class TestBingNewsEngine(SearxTestCase):
@@ -16,14 +17,10 @@ class TestBingNewsEngine(SearxTestCase):
self.assertIn(query, params['url'])
self.assertIn('bing.com', params['url'])
self.assertIn('fr', params['url'])
- self.assertIn('_FP', params['cookies'])
- self.assertIn('en', params['cookies']['_FP'])
dicto['language'] = 'all'
params = bing_news.request(query, dicto)
self.assertIn('en', params['url'])
- self.assertIn('_FP', params['cookies'])
- self.assertIn('en', params['cookies']['_FP'])
def test_response(self):
self.assertRaises(AttributeError, bing_news.response, None)
@@ -37,200 +34,105 @@ class TestBingNewsEngine(SearxTestCase):
response = mock.Mock(content='<html></html>')
self.assertEqual(bing_news.response(response), [])
- html = """
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">44 minutes ago</span>
- </span>
- </div>
- </div>
- </div>
- """
+ html = """<?xml version="1.0" encoding="utf-8" ?>
+<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
+ <channel>
+ <title>python - Bing News</title>
+ <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ <description>Search results</description>
+ <image>
+ <url>http://10.53.64.9/rsslogo.gif</url>
+ <title>test</title>
+ <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ </image>
+ <copyright>Copyright</copyright>
+ <item>
+ <title>Title</title>
+ <link>https://www.bing.com/news/apiclick.aspx?ref=FexRss&amp;aid=&amp;tid=c237eccc50bd4758b106a5e3c94fce09&amp;url=http%3a%2f%2furl.of.article%2f&amp;c=xxxxxxxxx&amp;mkt=en-us</link>
+ <description>Article Content</description>
+ <pubDate>Tue, 02 Jun 2015 13:37:00 GMT</pubDate>
+ <News:Source>Infoworld</News:Source>
+ <News:Image>http://a1.bing4.com/th?id=ON.13371337133713371337133713371337&amp;pid=News</News:Image>
+ <News:ImageSize>w={0}&amp;h={1}&amp;c=7</News:ImageSize>
+ <News:ImageKeepOriginalRatio></News:ImageKeepOriginalRatio>
+ <News:ImageMaxWidth>620</News:ImageMaxWidth>
+ <News:ImageMaxHeight>413</News:ImageMaxHeight>
+ </item>
+ <item>
+ <title>Another Title</title>
+ <link>https://www.bing.com/news/apiclick.aspx?ref=FexRss&amp;aid=&amp;tid=c237eccc50bd4758b106a5e3c94fce09&amp;url=http%3a%2f%2fanother.url.of.article%2f&amp;c=xxxxxxxxx&amp;mkt=en-us</link>
+ <description>Another Article Content</description>
+ <pubDate>Tue, 02 Jun 2015 13:37:00 GMT</pubDate>
+ </item>
+ </channel>
+</rss>""" # noqa
response = mock.Mock(content=html)
results = bing_news.response(response)
self.assertEqual(type(results), list)
- self.assertEqual(len(results), 1)
+ self.assertEqual(len(results), 2)
self.assertEqual(results[0]['title'], 'Title')
self.assertEqual(results[0]['url'], 'http://url.of.article/')
self.assertEqual(results[0]['content'], 'Article Content')
+ self.assertEqual(results[0]['thumbnail'], 'https://www.bing.com/th?id=ON.13371337133713371337133713371337')
+ self.assertEqual(results[1]['title'], 'Another Title')
+ self.assertEqual(results[1]['url'], 'http://another.url.of.article/')
+ self.assertEqual(results[1]['content'], 'Another Article Content')
+ self.assertNotIn('thumbnail', results[1])
- html = """
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">44 minutes ago</span>
- </span>
- </div>
- </div>
- </div>
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">3 hours, 44 minutes ago</span>
- </span>
- </div>
- </div>
- </div>
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">44 hours ago</span>
- </span>
- </div>
- </div>
- </div>
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">2 days ago</span>
- </span>
- </div>
- </div>
- </div>
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">27/01/2015</span>
- </span>
- </div>
- </div>
- </div>
- <div class="sn_r">
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">Il y a 3 heures</span>
- </span>
- </div>
- </div>
- </div>
- """
+ html = """<?xml version="1.0" encoding="utf-8" ?>
+<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
+ <channel>
+ <title>python - Bing News</title>
+ <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ <description>Search results</description>
+ <image>
+ <url>http://10.53.64.9/rsslogo.gif</url>
+ <title>test</title>
+ <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ </image>
+ <copyright>Copyright</copyright>
+ <item>
+ <title>Title</title>
+ <link>http://another.url.of.article/</link>
+ <description>Article Content</description>
+ <pubDate>garbage</pubDate>
+ <News:Source>Infoworld</News:Source>
+ <News:Image>http://another.bing.com/image</News:Image>
+ <News:ImageSize>w={0}&amp;h={1}&amp;c=7</News:ImageSize>
+ <News:ImageKeepOriginalRatio></News:ImageKeepOriginalRatio>
+ <News:ImageMaxWidth>620</News:ImageMaxWidth>
+ <News:ImageMaxHeight>413</News:ImageMaxHeight>
+ </item>
+ </channel>
+</rss>""" # noqa
response = mock.Mock(content=html)
results = bing_news.response(response)
self.assertEqual(type(results), list)
- self.assertEqual(len(results), 6)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://another.url.of.article/')
+ self.assertEqual(results[0]['content'], 'Article Content')
+ self.assertEqual(results[0]['thumbnail'], 'http://another.bing.com/image')
+
+ html = """<?xml version="1.0" encoding="utf-8" ?>
+<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
+ <channel>
+ <title>python - Bing News</title>
+ <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ <description>Search results</description>
+ <image>
+ <url>http://10.53.64.9/rsslogo.gif</url>
+ <title>test</title>
+ <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ </image>
+ </channel>
+</rss>""" # noqa
- html = """
- <div class="newstitle">
- <a href="http://url.of.article/" target="_blank" h="ID=news,5022.1">
- Title
- </a>
- </div>
- <div class="sn_img">
- <a href="http://url.of.article2/" target="_blank" h="ID=news,5024.1">
- <img class="rms_img" height="80" id="emb1" src="/image.src" title="Title" width="80" />
- </a>
- </div>
- <div class="sn_txt">
- <div class="sn_oi">
- <span class="sn_snip">Article Content</span>
- <span class="sn_ST">
- <cite class="sn_src">metronews.fr</cite>
- &nbsp;&#0183;&#32;
- <span class="sn_tm">44 minutes ago</span>
- </span>
- </div>
- </div>
- """
response = mock.Mock(content=html)
results = bing_news.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 0)
+
+ html = """<?xml version="1.0" encoding="utf-8" ?>gabarge"""
+ response = mock.Mock(content=html)
+ self.assertRaises(lxml.etree.XMLSyntaxError, bing_news.response, response)
diff --git a/searx/tests/engines/test_flickr_noapi.py b/searx/tests/engines/test_flickr_noapi.py
index a1de3a5e4..3b337a2d8 100644
--- a/searx/tests/engines/test_flickr_noapi.py
+++ b/searx/tests/engines/test_flickr_noapi.py
@@ -26,19 +26,29 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertRaises(AttributeError, flickr_noapi.response, '')
self.assertRaises(AttributeError, flickr_noapi.response, '[]')
- response = mock.Mock(text='"search-photos-models","photos":{},"totalItems":')
+ response = mock.Mock(text='"search-photos-lite-models","photos":{},"totalItems":')
self.assertEqual(flickr_noapi.response(response), [])
- response = mock.Mock(text='search-photos-models","photos":{"data": []},"totalItems":')
+ response = mock.Mock(text='search-photos-lite-models","photos":{"data": []},"totalItems":')
self.assertEqual(flickr_noapi.response(response), [])
+ # everthing is ok test
json = """
- "search-photos-models","photos":
+ "search-photos-lite-models","photos":
{
"_data": [
{
- "_flickrModelRegistry": "photo-models",
+ "_flickrModelRegistry": "photo-lite-models",
"title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
"sizes": {
"c": {
"displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_c.jpg",
@@ -117,40 +127,7 @@ class TestFlickrNoapiEngine(SearxTestCase):
"url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_z.jpg",
"key": "z"
}
- },
- "canComment": false,
- "rotation": 0,
- "owner": {
- "_flickrModelRegistry": "person-models",
- "pathAlias": "klink692",
- "username": "Owner",
- "buddyicon": {
- "retina": null,
- "large": null,
- "medium": null,
- "small": null,
- "default": "//c1.staticflickr.com/9/8108/buddyicons/59729010@N00.jpg?1361642376#59729010@N00"
- },
- "isPro": true,
- "id": "59729010@N00"
- },
- "engagement": {
- "_flickrModelRegistry": "photo-engagement-models",
- "ownerNsid": "59729010@N00",
- "faveCount": 21,
- "commentCount": 14,
- "viewCount": 10160,
- "id": "14001294434"
- },
- "description": "Description",
- "isHD": false,
- "secret": "410f653777",
- "canAddMeta": false,
- "license": 0,
- "oWidth": 1803,
- "oHeight": 2669,
- "safetyLevel": 0,
- "id": "14001294434"
+ }
}
],
"fetchedStart": true,
@@ -168,15 +145,24 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertIn('k.jpg', results[0]['img_src'])
self.assertIn('n.jpg', results[0]['thumbnail_src'])
self.assertIn('Owner', results[0]['content'])
- self.assertIn('Description', results[0]['content'])
+ # no n size, only the z size
json = """
- "search-photos-models","photos":
+ "search-photos-lite-models","photos":
{
"_data": [
{
- "_flickrModelRegistry": "photo-models",
+ "_flickrModelRegistry": "photo-lite-models",
"title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
"sizes": {
"z": {
"displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_z.jpg",
@@ -185,40 +171,7 @@ class TestFlickrNoapiEngine(SearxTestCase):
"url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_z.jpg",
"key": "z"
}
- },
- "canComment": false,
- "rotation": 0,
- "owner": {
- "_flickrModelRegistry": "person-models",
- "pathAlias": "klink692",
- "username": "Owner",
- "buddyicon": {
- "retina": null,
- "large": null,
- "medium": null,
- "small": null,
- "default": "//c1.staticflickr.com/9/8108/buddyicons/59729010@N00.jpg?1361642376#59729010@N00"
- },
- "isPro": true,
- "id": "59729010@N00"
- },
- "engagement": {
- "_flickrModelRegistry": "photo-engagement-models",
- "ownerNsid": "59729010@N00",
- "faveCount": 21,
- "commentCount": 14,
- "viewCount": 10160,
- "id": "14001294434"
- },
- "description": "Description",
- "isHD": false,
- "secret": "410f653777",
- "canAddMeta": false,
- "license": 0,
- "oWidth": 1803,
- "oHeight": 2669,
- "safetyLevel": 0,
- "id": "14001294434"
+ }
}
],
"fetchedStart": true,
@@ -235,15 +188,24 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertIn('z.jpg', results[0]['img_src'])
self.assertIn('z.jpg', results[0]['thumbnail_src'])
self.assertIn('Owner', results[0]['content'])
- self.assertIn('Description', results[0]['content'])
+ # no z or n size
json = """
- "search-photos-models","photos":
+ "search-photos-lite-models","photos":
{
"_data": [
{
- "_flickrModelRegistry": "photo-models",
+ "_flickrModelRegistry": "photo-lite-models",
"title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
"sizes": {
"o": {
"displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_o.jpg",
@@ -252,39 +214,7 @@ class TestFlickrNoapiEngine(SearxTestCase):
"url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_o.jpg",
"key": "o"
}
- },
- "canComment": false,
- "rotation": 0,
- "owner": {
- "_flickrModelRegistry": "person-models",
- "pathAlias": "klink692",
- "username": "Owner",
- "buddyicon": {
- "retina": null,
- "large": null,
- "medium": null,
- "small": null,
- "default": "//c1.staticflickr.com/9/8108/buddyicons/59729010@N00.jpg?1361642376#59729010@N00"
- },
- "isPro": true,
- "id": "59729010@N00"
- },
- "engagement": {
- "_flickrModelRegistry": "photo-engagement-models",
- "ownerNsid": "59729010@N00",
- "faveCount": 21,
- "commentCount": 14,
- "viewCount": 10160,
- "id": "14001294434"
- },
- "isHD": false,
- "secret": "410f653777",
- "canAddMeta": false,
- "license": 0,
- "oWidth": 1803,
- "oHeight": 2669,
- "safetyLevel": 0,
- "id": "14001294434"
+ }
}
],
"fetchedStart": true,
@@ -302,48 +232,25 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertIn('o.jpg', results[0]['thumbnail_src'])
self.assertIn('Owner', results[0]['content'])
+ # no image test
json = """
- "search-photos-models","photos":
+ "search-photos-lite-models","photos":
{
"_data": [
{
- "_flickrModelRegistry": "photo-models",
+ "_flickrModelRegistry": "photo-lite-models",
"title": "This is the title",
- "sizes": {
- },
- "canComment": false,
- "rotation": 0,
- "owner": {
- "_flickrModelRegistry": "person-models",
- "pathAlias": "klink692",
- "username": "Owner",
- "buddyicon": {
- "retina": null,
- "large": null,
- "medium": null,
- "small": null,
- "default": "//c1.staticflickr.com/9/8108/buddyicons/59729010@N00.jpg?1361642376#59729010@N00"
- },
- "isPro": true,
- "id": "59729010@N00"
- },
- "engagement": {
- "_flickrModelRegistry": "photo-engagement-models",
- "ownerNsid": "59729010@N00",
- "faveCount": 21,
- "commentCount": 14,
- "viewCount": 10160,
- "id": "14001294434"
- },
- "description": "Description",
- "isHD": false,
- "secret": "410f653777",
- "canAddMeta": false,
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
"license": 0,
- "oWidth": 1803,
- "oHeight": 2669,
- "safetyLevel": 0,
- "id": "14001294434"
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
+ "sizes": {
+ }
}
],
"fetchedStart": true,
@@ -356,6 +263,7 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertEqual(type(results), list)
self.assertEqual(len(results), 0)
+ # null test
json = """
"search-photos-models","photos":
{
@@ -370,13 +278,22 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertEqual(type(results), list)
self.assertEqual(len(results), 0)
+ # no ownerNsid test
json = """
- "search-photos-models","photos":
+ "search-photos-lite-models","photos":
{
"_data": [
{
- "_flickrModelRegistry": "photo-models",
+ "_flickrModelRegistry": "photo-lite-models",
"title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
"sizes": {
"o": {
"displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_o.jpg",
@@ -385,39 +302,7 @@ class TestFlickrNoapiEngine(SearxTestCase):
"url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_o.jpg",
"key": "o"
}
- },
- "canComment": false,
- "rotation": 0,
- "owner": {
- "_flickrModelRegistry": "person-models",
- "pathAlias": "klink692",
- "username": "Owner",
- "buddyicon": {
- "retina": null,
- "large": null,
- "medium": null,
- "small": null,
- "default": "//c1.staticflickr.com/9/8108/buddyicons/59729010@N00.jpg?1361642376#59729010@N00"
- },
- "isPro": true
- },
- "engagement": {
- "_flickrModelRegistry": "photo-engagement-models",
- "ownerNsid": "59729010@N00",
- "faveCount": 21,
- "commentCount": 14,
- "viewCount": 10160,
- "id": "14001294434"
- },
- "description": "Description",
- "isHD": false,
- "secret": "410f653777",
- "canAddMeta": false,
- "license": 0,
- "oWidth": 1803,
- "oHeight": 2669,
- "safetyLevel": 0,
- "id": "14001294434"
+ }
}
],
"fetchedStart": true,
@@ -430,6 +315,7 @@ class TestFlickrNoapiEngine(SearxTestCase):
self.assertEqual(type(results), list)
self.assertEqual(len(results), 0)
+ # garbage test
json = """
{"toto":[
{"id":200,"name":"Artist Name",
diff --git a/searx/tests/engines/test_google.py b/searx/tests/engines/test_google.py
index 2a90fc5ec..b706e511d 100644
--- a/searx/tests/engines/test_google.py
+++ b/searx/tests/engines/test_google.py
@@ -8,6 +8,12 @@ from searx.testing import SearxTestCase
class TestGoogleEngine(SearxTestCase):
+ def mock_response(self, text):
+ response = mock.Mock(text=text, url='https://www.google.com/search?q=test&start=0&gbv=1')
+ response.search_params = mock.Mock()
+ response.search_params.get = mock.Mock(return_value='www.google.com')
+ return response
+
def test_request(self):
query = 'test_query'
dicto = defaultdict(dict)
@@ -16,14 +22,17 @@ class TestGoogleEngine(SearxTestCase):
params = google.request(query, dicto)
self.assertIn('url', params)
self.assertIn(query, params['url'])
- self.assertIn('google.com', params['url'])
+ self.assertIn('google.fr', params['url'])
self.assertNotIn('PREF', params['cookies'])
+ self.assertIn('NID', params['cookies'])
self.assertIn('fr', params['headers']['Accept-Language'])
dicto['language'] = 'all'
params = google.request(query, dicto)
+ self.assertIn('google.com', params['url'])
self.assertIn('en', params['headers']['Accept-Language'])
self.assertIn('PREF', params['cookies'])
+ self.assertIn('NID', params['cookies'])
def test_response(self):
self.assertRaises(AttributeError, google.response, None)
@@ -31,7 +40,7 @@ class TestGoogleEngine(SearxTestCase):
self.assertRaises(AttributeError, google.response, '')
self.assertRaises(AttributeError, google.response, '[]')
- response = mock.Mock(text='<html></html>')
+ response = self.mock_response('<html></html>')
self.assertEqual(google.response(response), [])
html = """
@@ -124,7 +133,7 @@ class TestGoogleEngine(SearxTestCase):
</a>
</p>
"""
- response = mock.Mock(text=html)
+ response = self.mock_response(html)
results = google.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 2)
@@ -137,11 +146,21 @@ class TestGoogleEngine(SearxTestCase):
<li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
</li>
"""
- response = mock.Mock(text=html)
+ response = self.mock_response(html)
results = google.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 0)
+ response = mock.Mock(text='<html></html>', url='https://sorry.google.com')
+ response.search_params = mock.Mock()
+ response.search_params.get = mock.Mock(return_value='www.google.com')
+ self.assertRaises(RuntimeWarning, google.response, response)
+
+ response = mock.Mock(text='<html></html>', url='https://www.google.com/sorry/IndexRedirect')
+ response.search_params = mock.Mock()
+ response.search_params.get = mock.Mock(return_value='www.google.com')
+ self.assertRaises(RuntimeWarning, google.response, response)
+
def test_parse_images(self):
html = """
<li>
@@ -154,7 +173,7 @@ class TestGoogleEngine(SearxTestCase):
</li>
"""
dom = lxml.html.fromstring(html)
- results = google.parse_images(dom)
+ results = google.parse_images(dom, 'www.google.com')
self.assertEqual(type(results), list)
self.assertEqual(len(results), 1)
self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
diff --git a/searx/tests/engines/test_qwant.py b/searx/tests/engines/test_qwant.py
new file mode 100644
index 000000000..7d79d13d8
--- /dev/null
+++ b/searx/tests/engines/test_qwant.py
@@ -0,0 +1,317 @@
+from collections import defaultdict
+import mock
+from searx.engines import qwant
+from searx.testing import SearxTestCase
+
+
+class TestQwantEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr_FR'
+ qwant.categories = ['']
+ params = qwant.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('web', params['url'])
+ self.assertIn('qwant.com', params['url'])
+ self.assertIn('fr_fr', params['url'])
+
+ dicto['language'] = 'all'
+ qwant.categories = ['news']
+ params = qwant.request(query, dicto)
+ self.assertFalse('fr' in params['url'])
+ self.assertIn('news', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, qwant.response, None)
+ self.assertRaises(AttributeError, qwant.response, [])
+ self.assertRaises(AttributeError, qwant.response, '')
+ self.assertRaises(AttributeError, qwant.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(qwant.response(response), [])
+
+ response = mock.Mock(text='{"data": {}}')
+ self.assertEqual(qwant.response(response), [])
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": "",
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['general']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], 'Description')
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "media": "http://image.jpg",
+ "desc": "",
+ "thumbnail": "http://thumbnail.jpg",
+ "date": "",
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['images']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['thumbnail_src'], 'http://thumbnail.jpg')
+ self.assertEqual(results[0]['img_src'], 'http://image.jpg')
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": 1433260920,
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['news']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertIn('publishedDate', results[0])
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": 1433260920,
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['social media']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertIn('publishedDate', results[0])
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": 1433260920,
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "status": "success"
+ }
+ """
+ response = mock.Mock(text=json)
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/searx/tests/engines/test_swisscows.py b/searx/tests/engines/test_swisscows.py
new file mode 100644
index 000000000..3b4ce7b0f
--- /dev/null
+++ b/searx/tests/engines/test_swisscows.py
@@ -0,0 +1,128 @@
+from collections import defaultdict
+import mock
+from searx.engines import swisscows
+from searx.testing import SearxTestCase
+
+
+class TestSwisscowsEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'de_DE'
+ params = swisscows.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('swisscows.ch' in params['url'])
+ self.assertTrue('uiLanguage=de' in params['url'])
+ self.assertTrue('region=de-DE' in params['url'])
+
+ dicto['language'] = 'all'
+ params = swisscows.request(query, dicto)
+ self.assertTrue('uiLanguage=browser' in params['url'])
+ self.assertTrue('region=browser' in params['url'])
+
+ dicto['category'] = 'images'
+ params = swisscows.request(query, dicto)
+ self.assertIn('image', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, swisscows.response, None)
+ self.assertRaises(AttributeError, swisscows.response, [])
+ self.assertRaises(AttributeError, swisscows.response, '')
+ self.assertRaises(AttributeError, swisscows.response, '[]')
+
+ response = mock.Mock(content='<html></html>')
+ self.assertEqual(swisscows.response(response), [])
+
+ response = mock.Mock(content='<html></html>')
+ self.assertEqual(swisscows.response(response), [])
+
+ html = u"""
+ <script>
+ App.Dispatcher.dispatch("initialize", {
+ html5history: true,
+ initialData: {"Request":
+ {"Page":1,
+ "ItemsCount":1,
+ "Query":"This should ",
+ "NormalizedQuery":"This should ",
+ "Region":"de-AT",
+ "UILanguage":"de"},
+ "Results":{"items":[
+ {"Title":"\uE000This should\uE001 be the title",
+ "Description":"\uE000This should\uE001 be the content.",
+ "Url":"http://this.should.be.the.link/",
+ "DisplayUrl":"www.\uE000this.should.be.the\uE001.link",
+ "Id":"782ef287-e439-451c-b380-6ebc14ba033d"},
+ {"Title":"Datei:This should1.svg",
+ "Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should1.png",
+ "SourceUrl":"http://de.wikipedia.org/wiki/Datei:This should1.svg",
+ "DisplayUrl":"de.wikipedia.org/wiki/Datei:This should1.svg",
+ "Width":950,
+ "Height":534,
+ "FileSize":92100,
+ "ContentType":"image/jpeg",
+ "Thumbnail":{
+ "Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should1.png",
+ "ContentType":"image/jpeg",
+ "Width":300,
+ "Height":168,
+ "FileSize":9134},
+ "Id":"6a97a542-8f65-425f-b7f6-1178c3aba7be"
+ }
+ ],"TotalCount":55300,
+ "Query":"This should "
+ },
+ "Images":[{"Title":"Datei:This should.svg",
+ "Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should.png",
+ "SourceUrl":"http://de.wikipedia.org/wiki/Datei:This should.svg",
+ "DisplayUrl":"de.wikipedia.org/wiki/Datei:This should.svg",
+ "Width":1280,
+ "Height":677,
+ "FileSize":50053,
+ "ContentType":"image/png",
+ "Thumbnail":{"Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should.png",
+ "ContentType":"image/png",
+ "Width":300,
+ "Height":158,
+ "FileSize":8023},
+ "Id":"ae230fd8-a06a-47d6-99d5-e74766d8143a"}]},
+ environment: "production"
+ }).then(function (options) {
+ $('#Search_Form').on('submit', function (e) {
+ if (!Modernizr.history) return;
+ e.preventDefault();
+
+ var $form = $(this),
+ $query = $('#Query'),
+ query = $.trim($query.val()),
+ path = App.Router.makePath($form.attr('action'), null, $form.serializeObject())
+
+ if (query.length) {
+ options.html5history ?
+ ReactRouter.HistoryLocation.push(path) :
+ ReactRouter.RefreshLocation.push(path);
+ }
+ else $('#Query').trigger('blur');
+ });
+
+ });
+ </script>
+ """
+ response = mock.Mock(content=html)
+ results = swisscows.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+ self.assertEqual(results[1]['title'], 'Datei:This should1.svg')
+ self.assertEqual(results[1]['url'], 'http://de.wikipedia.org/wiki/Datei:This should1.svg')
+ self.assertEqual(results[1]['img_src'], 'http://ts2.mm.This/should1.png')
+ self.assertEqual(results[1]['template'], 'images.html')
+ self.assertEqual(results[2]['title'], 'Datei:This should.svg')
+ self.assertEqual(results[2]['url'], 'http://de.wikipedia.org/wiki/Datei:This should.svg')
+ self.assertEqual(results[2]['img_src'], 'http://ts2.mm.This/should.png')
+ self.assertEqual(results[2]['template'], 'images.html')
diff --git a/searx/tests/engines/test_vimeo.py b/searx/tests/engines/test_vimeo.py
index dad7239b4..50b1cb563 100644
--- a/searx/tests/engines/test_vimeo.py
+++ b/searx/tests/engines/test_vimeo.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
from collections import defaultdict
import mock
from searx.engines import vimeo
@@ -25,26 +26,42 @@ class TestVimeoEngine(SearxTestCase):
self.assertEqual(vimeo.response(response), [])
html = """
- <div id="browse_content" class="" data-search-id="696d5f8366914ec4ffec33cf7652de384976d4f4">
- <ol class="js-browse_list clearfix browse browse_videos browse_videos_thumbnails kane"
+ <div id="browse_content" class="results_grid" data-search-id="696d5f8366914ec4ffec33cf7652de384976d4f4">
+ <ul class="js-browse_list clearfix browse browse_videos browse_videos_thumbnails kane"
data-stream="c2VhcmNoOjo6ZGVzYzp7InF1ZXJ5IjoidGVzdCJ9">
- <li id="clip_100785455" data-start-page="/search/page:1/sort:relevant/" data-position="1">
- <a href="/videoid" title="Futurama 3d (test shot)">
- <img src="http://image.url.webp"
- srcset="http://i.vimeocdn.com/video/482375085_590x332.webp 2x" alt=""
- class="thumbnail thumbnail_lg_wide">
- <div class="data">
- <p class="title">
- This is the title
- </p>
- <p class="meta">
- <time datetime="2014-07-15T04:16:27-04:00"
- title="mardi 15 juillet 2014 04:16">Il y a 6 mois</time>
- </p>
- </div>
- </a>
+ <li data-position="7" data-result-id="clip_79600943">
+ <div class="clip_thumbnail">
+ <a href="/videoid" class="js-result_url">
+ <div class="thumbnail_wrapper">
+ <img src="http://image.url.webp" class="js-clip_thumbnail_image">
+ <div class="overlay overlay_clip_meta">
+ <div class="meta_data_footer">
+ <span class="clip_upload_date">
+ <time datetime="2013-11-17T08:49:09-05:00"
+ title="dimanche 17 novembre 2013 08:49">Il y a 1 an</time>
+ </span>
+ <span class="clip_likes">
+ <img src="https://f.vimeocdn.com/images_v6/svg/heart-icon.svg">2 215
+ </span>
+ <span class="clip_comments">
+ <img src="https://f.vimeocdn.com/images_v6/svg/comment-icon.svg">75
+ </span>
+ <span class="overlay meta_data_footer clip_duration">01:12</span>
+ </div>
+ </div>
+ </div>
+ <span class="title">This is the title</span>
+ </a>
+ </div>
+ <div class="clip_thumbnail_attribution">
+ <a href="/fedorshmidt">
+ <img src="https://i.vimeocdn.com/portrait/6628061_100x100.jpg" class="avatar">
+ <span class="display_name">Fedor Shmidt</span>
+ </a>
+ <span class="plays">2,1M lectures</span>
+ </div>
</li>
- </ol>
+ </ul>
</div>
"""
response = mock.Mock(text=html)
diff --git a/searx/tests/engines/test_www1x.py b/searx/tests/engines/test_www1x.py
index ab4f282c1..9df8de6bf 100644
--- a/searx/tests/engines/test_www1x.py
+++ b/searx/tests/engines/test_www1x.py
@@ -51,7 +51,7 @@ class TestWww1xEngine(SearxTestCase):
results = www1x.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 1)
- self.assertEqual(results[0]['url'], 'http://1x.com/photo/123456')
- self.assertEqual(results[0]['thumbnail_src'], 'http://1x.com/images/user/testimage-123456.jpg')
+ self.assertEqual(results[0]['url'], 'https://1x.com/photo/123456')
+ self.assertEqual(results[0]['thumbnail_src'], 'https://1x.com/images/user/testimage-123456.jpg')
self.assertEqual(results[0]['content'], '')
self.assertEqual(results[0]['template'], 'images.html')
diff --git a/searx/tests/engines/test_yahoo_news.py b/searx/tests/engines/test_yahoo_news.py
index 94d819d61..4d7fc0a10 100644
--- a/searx/tests/engines/test_yahoo_news.py
+++ b/searx/tests/engines/test_yahoo_news.py
@@ -29,6 +29,13 @@ class TestYahooNewsEngine(SearxTestCase):
self.assertIn('en', params['cookies']['sB'])
self.assertIn('en', params['url'])
+ def test_sanitize_url(self):
+ url = "test.url"
+ self.assertEqual(url, yahoo_news.sanitize_url(url))
+
+ url = "www.yahoo.com/;_ylt=test"
+ self.assertEqual("www.yahoo.com/", yahoo_news.sanitize_url(url))
+
def test_response(self):
self.assertRaises(AttributeError, yahoo_news.response, None)
self.assertRaises(AttributeError, yahoo_news.response, [])
@@ -57,7 +64,17 @@ class TestYahooNewsEngine(SearxTestCase):
This is the content
</div>
</li>
- </div>
+ <li class="first">
+ <div class="compTitle">
+ <h3>
+ <a class="yschttl spt" target="_blank">
+ </a>
+ </h3>
+ </div>
+ <div class="compText">
+ </div>
+ </li>
+ </ol>
"""
response = mock.Mock(text=html)
results = yahoo_news.response(response)
diff --git a/searx/tests/engines/test_youtube_api.py b/searx/tests/engines/test_youtube_api.py
new file mode 100644
index 000000000..0d4d478c3
--- /dev/null
+++ b/searx/tests/engines/test_youtube_api.py
@@ -0,0 +1,111 @@
+from collections import defaultdict
+import mock
+from searx.engines import youtube_api
+from searx.testing import SearxTestCase
+
+
+class TestYoutubeAPIEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr_FR'
+ params = youtube_api.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertIn('googleapis.com', params['url'])
+ self.assertIn('youtube', params['url'])
+ self.assertIn('fr', params['url'])
+
+ dicto['language'] = 'all'
+ params = youtube_api.request(query, dicto)
+ self.assertFalse('fr' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, youtube_api.response, None)
+ self.assertRaises(AttributeError, youtube_api.response, [])
+ self.assertRaises(AttributeError, youtube_api.response, '')
+ self.assertRaises(AttributeError, youtube_api.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(youtube_api.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(youtube_api.response(response), [])
+
+ json = """
+ {
+ "kind": "youtube#searchListResponse",
+ "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/YJQDcTBCDcaBvl-sRZJoXdvy1ME",
+ "nextPageToken": "CAUQAA",
+ "pageInfo": {
+ "totalResults": 1000000,
+ "resultsPerPage": 20
+ },
+ "items": [
+ {
+ "kind": "youtube#searchResult",
+ "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/IbLO64BMhbHIgWLwLw7MDYe7Hs4",
+ "id": {
+ "kind": "youtube#video",
+ "videoId": "DIVZCPfAOeM"
+ },
+ "snippet": {
+ "publishedAt": "2015-05-29T22:41:04.000Z",
+ "channelId": "UCNodmx1ERIjKqvcJLtdzH5Q",
+ "title": "Title",
+ "description": "Description",
+ "thumbnails": {
+ "default": {
+ "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/default.jpg"
+ },
+ "medium": {
+ "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
+ },
+ "high": {
+ "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg"
+ }
+ },
+ "channelTitle": "MinecraftUniverse",
+ "liveBroadcastContent": "none"
+ }
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = youtube_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'https://www.youtube.com/watch?v=DIVZCPfAOeM')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertEqual(results[0]['thumbnail'], 'https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg')
+ self.assertTrue('DIVZCPfAOeM' in results[0]['embedded'])
+
+ json = """
+ {
+ "kind": "youtube#searchListResponse",
+ "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/YJQDcTBCDcaBvl-sRZJoXdvy1ME",
+ "nextPageToken": "CAUQAA",
+ "pageInfo": {
+ "totalResults": 1000000,
+ "resultsPerPage": 20
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = youtube_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {"toto":{"entry":[]
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = youtube_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/searx/tests/engines/test_youtube_noapi.py b/searx/tests/engines/test_youtube_noapi.py
new file mode 100644
index 000000000..9fa8fd20e
--- /dev/null
+++ b/searx/tests/engines/test_youtube_noapi.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import youtube_noapi
+from searx.testing import SearxTestCase
+
+
+class TestYoutubeNoAPIEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = youtube_noapi.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('youtube.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, youtube_noapi.response, None)
+ self.assertRaises(AttributeError, youtube_noapi.response, [])
+ self.assertRaises(AttributeError, youtube_noapi.response, '')
+ self.assertRaises(AttributeError, youtube_noapi.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(youtube_noapi.response(response), [])
+
+ html = """
+ <ol id="item-section-063864" class="item-section">
+ <li>
+ <div class="yt-lockup yt-lockup-tile yt-lockup-video vve-check clearfix yt-uix-tile"
+ data-context-item-id="DIVZCPfAOeM"
+ data-visibility-tracking="CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JECx_-GK5uqMpcIB">
+ <div class="yt-lockup-dismissable"><div class="yt-lockup-thumbnail contains-addto">
+ <a aria-hidden="true" href="/watch?v=DIVZCPfAOeM" class=" yt-uix-sessionlink pf-link"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JFIEdGVzdA">
+ <div class="yt-thumb video-thumb"><img src="//i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
+ width="196" height="110"/></div><span class="video-time" aria-hidden="true">11:35</span></a>
+ <span class="thumb-menu dark-overflow-action-menu video-actions">
+ </span>
+ </div>
+ <div class="yt-lockup-content">
+ <h3 class="yt-lockup-title">
+ <a href="/watch?v=DIVZCPfAOeM"
+ class="yt-uix-tile-link yt-ui-ellipsis yt-ui-ellipsis-2 yt-uix-sessionlink spf-link"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JFIEdGVzdA"
+ title="Top Speed Test Kawasaki Ninja H2 (Thailand) By. MEHAY SUPERBIKE"
+ aria-describedby="description-id-259079" rel="spf-prefetch" dir="ltr">
+ Title
+ </a>
+ <span class="accessible-description" id="description-id-259079"> - Durée : 11:35.</span>
+ </h3>
+ <div class="yt-lockup-byline">de
+ <a href="/user/mheejapan" class=" yt-uix-sessionlink spf-link g-hovercard"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JA" data-ytid="UCzEesu54Hjs0uRKmpy66qeA"
+ data-name="">MEHAY SUPERBIKE</a></div><div class="yt-lockup-meta">
+ <ul class="yt-lockup-meta-info">
+ <li>il y a 20 heures</li>
+ <li>8 424 vues</li>
+ </ul>
+ </div>
+ <div class="yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2" dir="ltr">
+ Description
+ </div>
+ <div class="yt-lockup-badges">
+ <ul class="yt-badge-list ">
+ <li class="yt-badge-item" >
+ <span class="yt-badge">Nouveauté</span>
+ </li>
+ <li class="yt-badge-item" ><span class="yt-badge " >HD</span></li>
+ </ul>
+ </div>
+ <div class="yt-lockup-action-menu yt-uix-menu-container">
+ <div class="yt-uix-menu yt-uix-videoactionmenu hide-until-delayloaded"
+ data-video-id="DIVZCPfAOeM" data-menu-content-id="yt-uix-videoactionmenu-menu">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = youtube_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'https://www.youtube.com/watch?v=DIVZCPfAOeM')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertEqual(results[0]['thumbnail'], 'https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg')
+ self.assertTrue('DIVZCPfAOeM' in results[0]['embedded'])
+
+ html = """
+ <ol id="item-section-063864" class="item-section">
+ <li>
+ <div class="yt-lockup yt-lockup-tile yt-lockup-video vve-check clearfix yt-uix-tile"
+ data-context-item-id="DIVZCPfAOeM"
+ data-visibility-tracking="CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JECx_-GK5uqMpcIB">
+ <div class="yt-lockup-dismissable"><div class="yt-lockup-thumbnail contains-addto">
+ <a aria-hidden="true" href="/watch?v=DIVZCPfAOeM" class=" yt-uix-sessionlink pf-link"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JFIEdGVzdA">
+ <div class="yt-thumb video-thumb"><img src="//i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
+ width="196" height="110"/></div><span class="video-time" aria-hidden="true">11:35</span></a>
+ <span class="thumb-menu dark-overflow-action-menu video-actions">
+ </span>
+ </div>
+ <div class="yt-lockup-content">
+ <h3 class="yt-lockup-title">
+ <span class="accessible-description" id="description-id-259079"> - Durée : 11:35.</span>
+ </h3>
+ <div class="yt-lockup-byline">de
+ <a href="/user/mheejapan" class=" yt-uix-sessionlink spf-link g-hovercard"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JA" data-ytid="UCzEesu54Hjs0uRKmpy66qeA"
+ data-name="">MEHAY SUPERBIKE</a></div><div class="yt-lockup-meta">
+ <ul class="yt-lockup-meta-info">
+ <li>il y a 20 heures</li>
+ <li>8 424 vues</li>
+ </ul>
+ </div>
+ <div class="yt-lockup-badges">
+ <ul class="yt-badge-list ">
+ <li class="yt-badge-item" >
+ <span class="yt-badge">Nouveauté</span>
+ </li>
+ <li class="yt-badge-item" ><span class="yt-badge " >HD</span></li>
+ </ul>
+ </div>
+ <div class="yt-lockup-action-menu yt-uix-menu-container">
+ <div class="yt-uix-menu yt-uix-videoactionmenu hide-until-delayloaded"
+ data-video-id="DIVZCPfAOeM" data-menu-content-id="yt-uix-videoactionmenu-menu">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = youtube_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+
+ html = """
+ <ol id="item-section-063864" class="item-section">
+ <li>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = youtube_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/searx/tests/test_engines.py b/searx/tests/test_engines.py
index 5770458f3..dc062e95f 100644
--- a/searx/tests/test_engines.py
+++ b/searx/tests/test_engines.py
@@ -25,6 +25,7 @@ from searx.tests.engines.test_mixcloud import * # noqa
from searx.tests.engines.test_openstreetmap import * # noqa
from searx.tests.engines.test_photon import * # noqa
from searx.tests.engines.test_piratebay import * # noqa
+from searx.tests.engines.test_qwant import * # noqa
from searx.tests.engines.test_searchcode_code import * # noqa
from searx.tests.engines.test_searchcode_doc import * # noqa
from searx.tests.engines.test_soundcloud import * # noqa
@@ -32,6 +33,7 @@ from searx.tests.engines.test_spotify import * # noqa
from searx.tests.engines.test_stackoverflow import * # noqa
from searx.tests.engines.test_startpage import * # noqa
from searx.tests.engines.test_subtitleseeker import * # noqa
+from searx.tests.engines.test_swisscows import * # noqa
from searx.tests.engines.test_twitter import * # noqa
from searx.tests.engines.test_vimeo import * # noqa
from searx.tests.engines.test_www1x import * # noqa
@@ -39,4 +41,6 @@ from searx.tests.engines.test_www500px import * # noqa
from searx.tests.engines.test_yacy import * # noqa
from searx.tests.engines.test_yahoo import * # noqa
from searx.tests.engines.test_youtube import * # noqa
+from searx.tests.engines.test_youtube_api import * # noqa
+from searx.tests.engines.test_youtube_noapi import * # noqa
from searx.tests.engines.test_yahoo_news import * # noqa
diff --git a/searx/tests/test_plugins.py b/searx/tests/test_plugins.py
index 8dcad1142..c5171127c 100644
--- a/searx/tests/test_plugins.py
+++ b/searx/tests/test_plugins.py
@@ -38,10 +38,11 @@ class SelfIPTest(SearxTestCase):
def test_PluginStore_init(self):
store = plugins.PluginStore()
- store.register(plugins.self_ip)
+ store.register(plugins.self_info)
self.assertTrue(len(store.plugins) == 1)
+ # IP test
request = Mock(user_plugins=store.plugins,
remote_addr='127.0.0.1')
request.headers.getlist.return_value = []
@@ -49,3 +50,19 @@ class SelfIPTest(SearxTestCase):
query='ip')}
store.call('post_search', request, ctx)
self.assertTrue('127.0.0.1' in ctx['search'].answers)
+
+ # User agent test
+ request = Mock(user_plugins=store.plugins,
+ user_agent='Mock')
+ request.headers.getlist.return_value = []
+ ctx = {'search': Mock(answers=set(),
+ query='user-agent')}
+ store.call('post_search', request, ctx)
+ self.assertTrue('Mock' in ctx['search'].answers)
+ ctx = {'search': Mock(answers=set(),
+ query='user agent')}
+ store.call('post_search', request, ctx)
+ self.assertTrue('Mock' in ctx['search'].answers)
+ ctx = {'search': Mock(answers=set(),
+ query='What is my User-Agent?')}
+ store.call('post_search', request, ctx)
diff --git a/searx/tests/test_search.py b/searx/tests/test_search.py
new file mode 100644
index 000000000..89d0b620d
--- /dev/null
+++ b/searx/tests/test_search.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from searx.search import score_results
+from searx.testing import SearxTestCase
+
+
+def fake_result(url='https://aa.bb/cc?dd=ee#ff',
+ title='aaa',
+ content='bbb',
+ engine='wikipedia'):
+ return {'url': url,
+ 'title': title,
+ 'content': content,
+ 'engine': engine}
+
+
+class ScoreResultsTestCase(SearxTestCase):
+
+ def test_empty(self):
+ self.assertEqual(score_results(dict()), [])
+
+ def test_urlparse(self):
+ results = score_results(dict(a=[fake_result(url='https://aa.bb/cc?dd=ee#ff')]))
+ parsed_url = results[0]['parsed_url']
+ self.assertEqual(parsed_url.query, 'dd=ee')
diff --git a/searx/translations/pt/LC_MESSAGES/messages.mo b/searx/translations/pt/LC_MESSAGES/messages.mo
new file mode 100644
index 000000000..4a9302b60
--- /dev/null
+++ b/searx/translations/pt/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/pt/LC_MESSAGES/messages.po b/searx/translations/pt/LC_MESSAGES/messages.po
new file mode 100644
index 000000000..7c58ebcb7
--- /dev/null
+++ b/searx/translations/pt/LC_MESSAGES/messages.po
@@ -0,0 +1,574 @@
+# Portuguese translations for PROJECT.
+# Copyright (C) 2014 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2015-02-13 18:27+0100\n"
+"PO-Revision-Date: 2014-01-30 15:22+0100\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: en <LL@li.org>\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+
+#: searx/webapp.py:100
+msgid "files"
+msgstr "arquivos"
+
+#: searx/webapp.py:101
+msgid "general"
+msgstr "geral"
+
+#: searx/webapp.py:102
+msgid "music"
+msgstr "música"
+
+#: searx/webapp.py:103
+msgid "social media"
+msgstr "mídias sociais"
+
+#: searx/webapp.py:104
+msgid "images"
+msgstr "imagens"
+
+#: searx/webapp.py:105
+msgid "videos"
+msgstr "vídeos"
+
+#: searx/webapp.py:106
+msgid "it"
+msgstr "informática"
+
+#: searx/webapp.py:107
+msgid "news"
+msgstr "notícias"
+
+#: searx/webapp.py:108
+msgid "map"
+msgstr "mapa"
+
+#: searx/webapp.py:361
+msgid "{minutes} minute(s) ago"
+msgstr "Há {minutes} minutos"
+
+#: searx/webapp.py:363
+msgid "{hours} hour(s), {minutes} minute(s) ago"
+msgstr "Há {hours} e {minutes} minutos"
+
+#: searx/engines/__init__.py:182
+msgid "Page loads (sec)"
+msgstr "Tempo de carregamento (seg)"
+
+#: searx/engines/__init__.py:186
+msgid "Number of results"
+msgstr "Número de resultados"
+
+#: searx/engines/__init__.py:190
+msgid "Scores"
+msgstr "Pontuações"
+
+#: searx/engines/__init__.py:194
+msgid "Scores per result"
+msgstr "Pontuações por resultado"
+
+#: searx/engines/__init__.py:198
+msgid "Errors"
+msgstr "Erros"
+
+#: searx/templates/courgette/index.html:9
+#: searx/templates/courgette/index.html:13
+#: searx/templates/courgette/results.html:5
+#: searx/templates/default/index.html:8 searx/templates/default/index.html:12
+#: searx/templates/oscar/navbar.html:7 searx/templates/oscar/navbar.html:35
+#: searx/templates/oscar/preferences.html:3
+msgid "preferences"
+msgstr "preferências"
+
+#: searx/templates/courgette/index.html:11
+#: searx/templates/default/index.html:10 searx/templates/oscar/about.html:3
+#: searx/templates/oscar/navbar.html:8 searx/templates/oscar/navbar.html:34
+msgid "about"
+msgstr "sobre"
+
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
+#: searx/templates/oscar/preferences.html:12
+msgid "Preferences"
+msgstr "Preferências"
+
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
+#: searx/templates/oscar/preferences.html:34
+#: searx/templates/oscar/preferences.html:36
+msgid "Default categories"
+msgstr "Categorias padrão"
+
+#: searx/templates/courgette/preferences.html:13
+#: searx/templates/default/preferences.html:14
+#: searx/templates/oscar/preferences.html:42
+msgid "Search language"
+msgstr "Língua de pesquisa"
+
+#: searx/templates/courgette/preferences.html:16
+#: searx/templates/default/preferences.html:17
+#: searx/templates/oscar/preferences.html:46
+msgid "Automatic"
+msgstr "Automático"
+
+#: searx/templates/courgette/preferences.html:24
+#: searx/templates/default/preferences.html:25
+#: searx/templates/oscar/preferences.html:53
+msgid "Interface language"
+msgstr "Linguagem da interface"
+
+#: searx/templates/courgette/preferences.html:34
+#: searx/templates/default/preferences.html:35
+#: searx/templates/oscar/preferences.html:63
+msgid "Autocomplete"
+msgstr "Autocompletar"
+
+#: searx/templates/courgette/preferences.html:45
+#: searx/templates/default/preferences.html:46
+#: searx/templates/oscar/preferences.html:74
+msgid "Image proxy"
+msgstr "Proxy de imagens"
+
+#: searx/templates/courgette/preferences.html:48
+#: searx/templates/default/preferences.html:49
+#: searx/templates/oscar/preferences.html:78
+msgid "Enabled"
+msgstr "Ativado"
+
+#: searx/templates/courgette/preferences.html:49
+#: searx/templates/default/preferences.html:50
+#: searx/templates/oscar/preferences.html:79
+msgid "Disabled"
+msgstr "Desativado"
+
+#: searx/templates/courgette/preferences.html:54
+#: searx/templates/default/preferences.html:55
+#: searx/templates/oscar/preferences.html:83
+msgid "Method"
+msgstr "Método"
+
+#: searx/templates/courgette/preferences.html:63
+#: searx/templates/default/preferences.html:64
+#: searx/templates/oscar/preferences.html:92
+msgid "SafeSearch"
+msgstr "Pesquisa segura"
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:67
+#: searx/templates/oscar/preferences.html:96
+msgid "Strict"
+msgstr "Estrito"
+
+#: searx/templates/courgette/preferences.html:67
+#: searx/templates/default/preferences.html:68
+#: searx/templates/oscar/preferences.html:97
+msgid "Moderate"
+msgstr "Moderado"
+
+#: searx/templates/courgette/preferences.html:68
+#: searx/templates/default/preferences.html:69
+#: searx/templates/oscar/preferences.html:98
+msgid "None"
+msgstr "Nenhum"
+
+#: searx/templates/courgette/preferences.html:73
+#: searx/templates/default/preferences.html:74
+#: searx/templates/oscar/preferences.html:102
+msgid "Themes"
+msgstr "Temas"
+
+#: searx/templates/courgette/preferences.html:83
+msgid "Color"
+msgstr "Cor"
+
+#: searx/templates/courgette/preferences.html:86
+msgid "Blue (default)"
+msgstr "Azul (padrão)"
+
+#: searx/templates/courgette/preferences.html:87
+msgid "Violet"
+msgstr "Violeta"
+
+#: searx/templates/courgette/preferences.html:88
+msgid "Green"
+msgstr "Verde"
+
+#: searx/templates/courgette/preferences.html:89
+msgid "Cyan"
+msgstr "Ciano"
+
+#: searx/templates/courgette/preferences.html:90
+msgid "Orange"
+msgstr "Laranja"
+
+#: searx/templates/courgette/preferences.html:91
+msgid "Red"
+msgstr "Vermelho"
+
+#: searx/templates/courgette/preferences.html:96
+#: searx/templates/default/preferences.html:84
+msgid "Currently used search engines"
+msgstr "Motores de busca sendo usados atualmente"
+
+#: searx/templates/courgette/preferences.html:100
+#: searx/templates/default/preferences.html:88
+msgid "Engine name"
+msgstr "Nome do motor"
+
+#: searx/templates/courgette/preferences.html:101
+#: searx/templates/default/preferences.html:89
+msgid "Category"
+msgstr "Categoria"
+
+#: searx/templates/courgette/preferences.html:102
+#: searx/templates/courgette/preferences.html:113
+#: searx/templates/default/preferences.html:90
+#: searx/templates/default/preferences.html:101
+#: searx/templates/oscar/preferences.html:145
+msgid "Allow"
+msgstr "Permitir"
+
+#: searx/templates/courgette/preferences.html:102
+#: searx/templates/courgette/preferences.html:114
+#: searx/templates/default/preferences.html:90
+#: searx/templates/default/preferences.html:102
+#: searx/templates/oscar/preferences.html:144
+msgid "Block"
+msgstr "Bloquear"
+
+#: searx/templates/courgette/preferences.html:122
+#: searx/templates/default/preferences.html:110
+#: searx/templates/oscar/preferences.html:161
+msgid ""
+"These settings are stored in your cookies, this allows us not to store "
+"this data about you."
+msgstr "Essas configurações são armazenadas em seus cookies, isto nos permite não armazenar dados sobre você."
+
+#: searx/templates/courgette/preferences.html:124
+#: searx/templates/default/preferences.html:112
+#: searx/templates/oscar/preferences.html:163
+msgid ""
+"These cookies serve your sole convenience, we don't use these cookies to "
+"track you."
+msgstr "Esses cookies servem unicamente para sua conveniência, nós não usamos eles para te rastrear."
+
+#: searx/templates/courgette/preferences.html:127
+#: searx/templates/default/preferences.html:115
+#: searx/templates/oscar/preferences.html:166
+msgid "save"
+msgstr "salvar"
+
+#: searx/templates/courgette/preferences.html:128
+#: searx/templates/default/preferences.html:116
+#: searx/templates/oscar/preferences.html:167
+msgid "back"
+msgstr "voltar"
+
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:13
+#: searx/templates/oscar/results.html:110
+msgid "Search URL"
+msgstr "Pesquisar URL"
+
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:17
+#: searx/templates/oscar/results.html:115
+msgid "Download results"
+msgstr "Baixar resultados"
+
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:35
+msgid "Answers"
+msgstr "Respostas"
+
+#: searx/templates/courgette/results.html:42
+#: searx/templates/default/results.html:43
+#: searx/templates/oscar/results.html:90
+msgid "Suggestions"
+msgstr "Sugestões"
+
+#: searx/templates/courgette/results.html:70
+#: searx/templates/default/results.html:81
+#: searx/templates/oscar/results.html:51 searx/templates/oscar/results.html:63
+msgid "previous page"
+msgstr "página anterior"
+
+#: searx/templates/courgette/results.html:81
+#: searx/templates/default/results.html:92
+#: searx/templates/oscar/results.html:44 searx/templates/oscar/results.html:71
+msgid "next page"
+msgstr "próxima página"
+
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3 searx/templates/oscar/search.html:4
+#: searx/templates/oscar/search_full.html:9
+msgid "Search for..."
+msgstr "Pesquisar por..."
+
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
+#: searx/templates/oscar/stats.html:5
+msgid "Engine stats"
+msgstr "Estatísticas do motor de busca"
+
+#: searx/templates/courgette/result_templates/images.html:4
+#: searx/templates/default/result_templates/images.html:4
+msgid "original context"
+msgstr "contexto original"
+
+#: searx/templates/courgette/result_templates/torrent.html:7
+#: searx/templates/default/result_templates/torrent.html:11
+#: searx/templates/oscar/result_templates/torrent.html:6
+msgid "Seeder"
+msgstr "Seeder"
+
+#: searx/templates/courgette/result_templates/torrent.html:7
+#: searx/templates/default/result_templates/torrent.html:11
+#: searx/templates/oscar/result_templates/torrent.html:6
+msgid "Leecher"
+msgstr "Leecher"
+
+#: searx/templates/courgette/result_templates/torrent.html:9
+#: searx/templates/default/result_templates/torrent.html:9
+#: searx/templates/oscar/macros.html:21
+msgid "magnet link"
+msgstr "link magnético"
+
+#: searx/templates/courgette/result_templates/torrent.html:10
+#: searx/templates/default/result_templates/torrent.html:10
+#: searx/templates/oscar/macros.html:22
+msgid "torrent file"
+msgstr "arquivo torrent"
+
+#: searx/templates/default/categories.html:8
+msgid "Click on the magnifier to perform search"
+msgstr "Clique na lupa para realizar a busca"
+
+#: searx/templates/default/result_templates/code.html:3
+#: searx/templates/default/result_templates/default.html:3
+#: searx/templates/default/result_templates/map.html:9
+#: searx/templates/oscar/macros.html:20
+msgid "cached"
+msgstr "em cache"
+
+#: searx/templates/oscar/base.html:74
+msgid "Powered by"
+msgstr "Criado por"
+
+#: searx/templates/oscar/base.html:74
+msgid "a privacy-respecting, hackable metasearch engine"
+msgstr "Um metapesquisador hackeável que respeita a privacidade"
+
+#: searx/templates/oscar/navbar.html:9 searx/templates/oscar/navbar.html:33
+msgid "home"
+msgstr "início"
+
+#: searx/templates/oscar/navbar.html:14 searx/templates/oscar/navbar.html:24
+msgid "Toggle navigation"
+msgstr "Mudar navegação"
+
+#: searx/templates/oscar/preferences.html:17
+#: searx/templates/oscar/preferences.html:23
+msgid "General"
+msgstr "Geral"
+
+#: searx/templates/oscar/preferences.html:18
+#: searx/templates/oscar/preferences.html:124
+msgid "Engines"
+msgstr "Motores de busca"
+
+#: searx/templates/oscar/preferences.html:43
+msgid "What language do you prefer for search?"
+msgstr "Que linguagem você prefere para a busca?"
+
+#: searx/templates/oscar/preferences.html:54
+msgid "Change the language of the layout"
+msgstr "Mudar a linguagem da interface"
+
+#: searx/templates/oscar/preferences.html:64
+msgid "Find stuff as you type"
+msgstr "Achar coisas enquanto você digita"
+
+#: searx/templates/oscar/preferences.html:75
+msgid "Proxying image results through searx"
+msgstr "Filtrar resultados de imagens no searx"
+
+#: searx/templates/oscar/preferences.html:84
+msgid ""
+"Change how forms are submited, <a "
+"href=\"http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods\""
+" rel=\"external\">learn more about request methods</a>"
+msgstr "Muda o modo como formulários são enviados <a href=\"https://pt.wikipedia.org/wiki/Hypertext_Transfer_Protocol#M.C3.A9todosn\" rel=\"external\">mais informações sobre os modos de pedido</a>"
+
+#: searx/templates/oscar/preferences.html:93
+msgid "Filter content"
+msgstr "Filtrar conteúdo"
+
+#: searx/templates/oscar/preferences.html:103
+msgid "Change searx layout"
+msgstr "Mudar a interface do searx"
+
+#: searx/templates/oscar/results.html:7
+msgid "Search results"
+msgstr "Pesquisar"
+
+#: searx/templates/oscar/results.html:105
+msgid "Links"
+msgstr "Links"
+
+#: searx/templates/oscar/search.html:6
+#: searx/templates/oscar/search_full.html:11
+msgid "Start search"
+msgstr "Começar pesquisa"
+
+#: searx/templates/oscar/search_full.html:15
+msgid "Show search filters"
+msgstr "Mostrar filtros de pesquisa"
+
+#: searx/templates/oscar/search_full.html:15
+msgid "Hide search filters"
+msgstr "Esconder filtros de pesquisa"
+
+#: searx/templates/oscar/stats.html:2
+msgid "stats"
+msgstr "Estatísticas"
+
+#: searx/templates/oscar/messages/first_time.html:4
+#: searx/templates/oscar/messages/no_results.html:5
+#: searx/templates/oscar/messages/save_settings_successfull.html:5
+#: searx/templates/oscar/messages/unknow_error.html:5
+msgid "Close"
+msgstr "Fechar"
+
+#: searx/templates/oscar/messages/first_time.html:6
+#: searx/templates/oscar/messages/no_data_available.html:3
+msgid "Heads up!"
+msgstr "Atenção"
+
+#: searx/templates/oscar/messages/first_time.html:7
+msgid "It look like you are using searx first time."
+msgstr "Parece que você está usando o seax pela primeira vez."
+
+#: searx/templates/oscar/messages/js_disabled.html:2
+msgid "Warning!"
+msgstr "Atenção!"
+
+#: searx/templates/oscar/messages/js_disabled.html:3
+msgid "Please enable JavaScript to use full functionality of this site."
+msgstr "Ative o Javascript para poder usar toda a funcionalidade deste site, por favor."
+
+#: searx/templates/oscar/messages/no_data_available.html:4
+msgid "There is currently no data available. "
+msgstr "Não há dados disponíveis atualmente. "
+
+#: searx/templates/oscar/messages/no_results.html:7
+msgid "Sorry!"
+msgstr "Desculpe!"
+
+#: searx/templates/oscar/messages/no_results.html:8
+msgid ""
+"we didn't find any results. Please use another query or search in more "
+"categories."
+msgstr "Nós não achamos nenhum resultado. Reformule sua busca ou procure em outras categorias, por favor."
+
+#: searx/templates/oscar/messages/save_settings_successfull.html:7
+msgid "Well done!"
+msgstr "Bem feito!"
+
+#: searx/templates/oscar/messages/save_settings_successfull.html:8
+msgid "Settings saved successfully."
+msgstr "Opções salvadas com sucesso."
+
+#: searx/templates/oscar/messages/unknow_error.html:7
+msgid "Oh snap!"
+msgstr "Droga!"
+
+#: searx/templates/oscar/messages/unknow_error.html:8
+msgid "Something went wrong."
+msgstr "algo de errado aconteceu."
+
+#: searx/templates/oscar/result_templates/default.html:7
+msgid "show media"
+msgstr "mostrar mídia"
+
+#: searx/templates/oscar/result_templates/default.html:7
+msgid "hide media"
+msgstr "esconder mídia"
+
+#: searx/templates/oscar/result_templates/images.html:23
+msgid "Get image"
+msgstr "oter imagem"
+
+#: searx/templates/oscar/result_templates/images.html:24
+msgid "View source"
+msgstr "ver fonte"
+
+#: searx/templates/oscar/result_templates/map.html:7
+msgid "show map"
+msgstr "mostrar mapa"
+
+#: searx/templates/oscar/result_templates/map.html:7
+msgid "hide map"
+msgstr "esconder mapa"
+
+#: searx/templates/oscar/result_templates/map.html:11
+msgid "show details"
+msgstr "mostrar detalhes"
+
+#: searx/templates/oscar/result_templates/map.html:11
+msgid "hide details"
+msgstr "esconder detalhes"
+
+#: searx/templates/oscar/result_templates/torrent.html:7
+msgid "Filesize"
+msgstr "Tamanho de arquivo"
+
+#: searx/templates/oscar/result_templates/torrent.html:9
+msgid "Bytes"
+msgstr "Bytes"
+
+#: searx/templates/oscar/result_templates/torrent.html:10
+msgid "kiB"
+msgstr "kiB"
+
+#: searx/templates/oscar/result_templates/torrent.html:11
+msgid "MiB"
+msgstr "miB"
+
+#: searx/templates/oscar/result_templates/torrent.html:12
+msgid "GiB"
+msgstr "GiB"
+
+#: searx/templates/oscar/result_templates/torrent.html:13
+msgid "TiB"
+msgstr "TiB"
+
+#: searx/templates/oscar/result_templates/torrent.html:15
+msgid "Number of Files"
+msgstr "Número de Arquivos"
+
+#: searx/templates/oscar/result_templates/videos.html:7
+msgid "show video"
+msgstr "mostrar vídeo"
+
+#: searx/templates/oscar/result_templates/videos.html:7
+msgid "hide video"
+msgstr "esconder vídeo"
+
+#~ msgid "Localization"
+#~ msgstr "Localização"
+
+#~ msgid "Yes"
+#~ msgstr "Sim"
+
+#~ msgid "No"
+#~ msgstr "Não"
diff --git a/searx/utils.py b/searx/utils.py
index 129971e31..c9784159c 100644
--- a/searx/utils.py
+++ b/searx/utils.py
@@ -228,6 +228,14 @@ def prettify_url(url):
return url
+# get element in list or default value
+def list_get(a_list, index, default=None):
+ if len(a_list) > index:
+ return a_list[index]
+ else:
+ return default
+
+
def get_blocked_engines(engines, cookies):
if 'blocked_engines' not in cookies:
return [(engine_name, category) for engine_name in engines
diff --git a/searx/webapp.py b/searx/webapp.py
index 3ef5a72c8..fb7157b47 100644
--- a/searx/webapp.py
+++ b/searx/webapp.py
@@ -279,6 +279,12 @@ def render(template_name, override_theme=None, **kwargs):
if x != 'general'
and x in nonblocked_categories)
+ if 'all_categories' not in kwargs:
+ kwargs['all_categories'] = ['general']
+ kwargs['all_categories'].extend(x for x in
+ sorted(categories.keys())
+ if x != 'general')
+
if 'selected_categories' not in kwargs:
kwargs['selected_categories'] = []
for arg in request.args:
@@ -286,11 +292,13 @@ def render(template_name, override_theme=None, **kwargs):
c = arg.split('_', 1)[1]
if c in categories:
kwargs['selected_categories'].append(c)
+
if not kwargs['selected_categories']:
cookie_categories = request.cookies.get('categories', '').split(',')
for ccateg in cookie_categories:
if ccateg in categories:
kwargs['selected_categories'].append(ccateg)
+
if not kwargs['selected_categories']:
kwargs['selected_categories'] = ['general']
@@ -623,6 +631,24 @@ def preferences():
resp.set_cookie('theme', theme, max_age=cookie_max_age)
return resp
+
+ # stats for preferences page
+ stats = {}
+
+ for c in categories:
+ for e in categories[c]:
+ stats[e.name] = {'time': None,
+ 'warn_timeout': False,
+ 'warn_time': False}
+ if e.timeout > settings['server']['request_timeout']:
+ stats[e.name]['warn_timeout'] = True
+
+ for engine_stat in get_engines_stats()[0][1]:
+ stats[engine_stat.get('name')]['time'] = round(engine_stat.get('avg'), 3)
+ if engine_stat.get('avg') > settings['server']['request_timeout']:
+ stats[engine_stat.get('name')]['warn_time'] = True
+ # end of stats
+
return render('preferences.html',
locales=settings['locales'],
current_locale=get_locale(),
@@ -630,6 +656,7 @@ def preferences():
image_proxy=image_proxy,
language_codes=language_codes,
engines_by_category=categories,
+ stats=stats,
blocked_engines=blocked_engines,
autocomplete_backends=autocomplete_backends,
shortcuts={y: x for x, y in engine_shortcuts.items()},
@@ -670,7 +697,7 @@ def image_proxy():
return '', 400
if not resp.headers.get('content-type', '').startswith('image/'):
- logger.debug('image-proxy: wrong content-type: {0}'.format(resp.get('content-type')))
+ logger.debug('image-proxy: wrong content-type: {0}'.format(resp.headers.get('content-type')))
return '', 400
img = ''
@@ -754,10 +781,45 @@ def run():
)
-application = app
+class ReverseProxyPathFix(object):
+ '''Wrap the application in this middleware and configure the
+ front-end server to add these headers, to let you quietly bind
+ this to a URL other than / and to an HTTP scheme that is
+ different than what is used locally.
+
+ http://flask.pocoo.org/snippets/35/
+
+ In nginx:
+ location /myprefix {
+ proxy_pass http://127.0.0.1:8000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Scheme $scheme;
+ proxy_set_header X-Script-Name /myprefix;
+ }
+
+ :param app: the WSGI application
+ '''
+ def __init__(self, app):
+ self.app = app
-app.wsgi_app = ProxyFix(application.wsgi_app)
+ def __call__(self, environ, start_response):
+ script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
+ if script_name:
+ environ['SCRIPT_NAME'] = script_name
+ path_info = environ['PATH_INFO']
+ if path_info.startswith(script_name):
+ environ['PATH_INFO'] = path_info[len(script_name):]
+ scheme = environ.get('HTTP_X_SCHEME', '')
+ if scheme:
+ environ['wsgi.url_scheme'] = scheme
+ return self.app(environ, start_response)
+
+
+application = app
+# patch app to handle non root url-s behind proxy & wsgi
+app.wsgi_app = ReverseProxyPathFix(ProxyFix(application.wsgi_app))
if __name__ == "__main__":
run()
diff --git a/versions.cfg b/versions.cfg
index c4cca4ad9..945217415 100644
--- a/versions.cfg
+++ b/versions.cfg
@@ -13,82 +13,90 @@ collective.recipe.omelette = 0.16
coverage = 3.7.1
decorator = 3.4.2
docutils = 0.12
-flake8 = 2.4.0
+flake8 = 2.4.1
itsdangerous = 0.24
-mccabe = 0.3
+mccabe = 0.3.1
mock = 1.0.1
pep8 = 1.5.7
plone.testing = 4.0.13
pyflakes = 0.8.1
-pytz = 2015.2
+pytz = 2015.4
pyyaml = 3.11
-requests = 2.6.2
+requests = 2.7.0
robotframework-debuglibrary = 0.3
robotframework-httplibrary = 0.4.2
-robotframework-selenium2library = 1.6.0
+robotframework-selenium2library = 1.7.1
robotsuite = 1.6.1
-selenium = 2.45.0
+selenium = 2.46.0
speaklater = 1.3
unittest2 = 1.0.1
waitress = 0.8.9
zc.recipe.testrunner = 2.0.0
pyopenssl = 0.15.1
-ndg-httpsclient = 0.3.3
-pyasn1 = 0.1.7
-pyasn1-modules = 0.0.5
+ndg-httpsclient = 0.4.0
+pyasn1 = 0.1.8
+pyasn1-modules = 0.0.6
certifi = 2015.04.28
#
-cffi = 0.9.2
-cryptography = 0.8.2
+cffi = 1.1.2
+cryptography = 0.9.1
+
+# Required by:
+# robotsuite==1.6.1
+# searx==0.7.0
+lxml = 3.4.4
+
+# Required by:
+# searx==0.7.0
+python-dateutil = 2.4.2
+
+# Required by:
+# searx==0.7.0
+# zope.exceptions==4.0.7
+# zope.interface==4.1.2
+# zope.testrunner==4.4.9
+setuptools = 18.0.1
# Required by:
# WebTest==2.0.18
beautifulsoup4 = 4.3.2
# Required by:
-# cryptography==0.8.2
+# cryptography==0.9.1
enum34 = 1.0.4
# Required by:
+# cryptography==0.9.1
+idna = 2.0
+
+# Required by:
+# cryptography==0.9.1
+ipaddress = 1.0.7
+
+# Required by:
# robotframework-httplibrary==0.4.2
-jsonpatch = 1.9
+jsonpatch = 1.11
# Required by:
# robotframework-httplibrary==0.4.2
-jsonpointer = 1.7
+jsonpointer = 1.9
# Required by:
# traceback2==1.4.0
linecache2 = 1.0.0
# Required by:
-# robotsuite==1.6.1
-# searx==0.7.0
-lxml = 3.4.4
-
-# Required by:
-# cffi==0.9.2
+# cffi==1.1.2
pycparser = 2.12
# Required by:
-# searx==0.7.0
-python-dateutil = 2.4.2
-
-# Required by:
# robotframework-httplibrary==0.4.2
robotframework = 2.8.7
# Required by:
-# searx==0.7.0
-# zope.exceptions==4.0.7
-# zope.interface==4.1.2
-# zope.testrunner==4.4.8
-setuptools = 15.2
-
-# Required by:
# robotsuite==1.6.1
-# zope.testrunner==4.4.8
+# zope.testrunner==4.4.9
six = 1.9.0
# Required by:
@@ -100,17 +108,17 @@ traceback2 = 1.4.0
zc.recipe.egg = 2.0.1
# Required by:
-# zope.testrunner==4.4.8
+# zope.testrunner==4.4.9
zope.exceptions = 4.0.7
# Required by:
-# zope.testrunner==4.4.8
+# zope.testrunner==4.4.9
zope.interface = 4.1.2
# Required by:
# plone.testing==4.0.13
-zope.testing = 4.1.3
+zope.testing = 4.2.0
# Required by:
# zc.recipe.testrunner==2.0.0
-zope.testrunner = 4.4.8
+zope.testrunner = 4.4.9