Merge branch 'master' of https://github.com/asciimoo/searx into code_results
Conflicts: searx/engines/searchcode_code.py searx/engines/searchcode_doc.py searx/static/oscar/js/searx.min.js searx/templates/oscar/result_templates/default.html searx/templates/oscar/result_templates/images.html searx/templates/oscar/result_templates/map.html searx/templates/oscar/result_templates/torrent.html searx/templates/oscar/result_templates/videos.html
| @ -5,7 +5,7 @@ before_install: | ||||
|   - "export DISPLAY=:99.0" | ||||
|   - "sh -e /etc/init.d/xvfb start" | ||||
|   - npm install -g less grunt-cli | ||||
|   - ( cd searx/static/oscar;npm install ) | ||||
|   - ( cd searx/static/themes/oscar;npm install ) | ||||
| install: | ||||
|   - "make" | ||||
|   - pip install coveralls | ||||
|  | ||||
| @ -29,3 +29,6 @@ generally made searx better: | ||||
| - @kernc | ||||
| - @Cqoicebordel | ||||
| - @Reventl0v | ||||
| - Caner Başaran | ||||
| - Benjamin Sonntag | ||||
| - @opi | ||||
|  | ||||
							
								
								
									
										27
									
								
								CHANGELOG.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,27 @@ | ||||
| 0.6.0 - 2014.12.25 | ||||
| ================== | ||||
| 
 | ||||
| - Changelog added | ||||
| - New engines | ||||
| 
 | ||||
|   - Flickr (api) | ||||
|   - Subtitleseeker | ||||
|   - photon | ||||
|   - 500px | ||||
|   - Searchcode | ||||
|   - Searchcode doc | ||||
|   - Kickass torrent | ||||
| - Precise search request timeout handling | ||||
| - Better favicon support | ||||
| - Stricter config parsing | ||||
| - Translation updates | ||||
| - Multiple ui fixes | ||||
| - Flickr (noapi) engine fix | ||||
| - Pep8 fixes | ||||
| 
 | ||||
| 
 | ||||
| News | ||||
| ~~~~ | ||||
| 
 | ||||
| Health status of searx instances and engines: http://stats.searx.oe5tpo.com | ||||
| (source: https://github.com/pointhi/searx_stats) | ||||
							
								
								
									
										18
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @ -18,10 +18,6 @@ $(python): | ||||
| 	virtualenv -p python$(version) --no-site-packages . | ||||
| 	@touch $@ | ||||
| 
 | ||||
| tests: .installed.cfg | ||||
| 	@bin/test | ||||
| 	@grunt test --gruntfile searx/static/oscar/gruntfile.js | ||||
| 
 | ||||
| robot: .installed.cfg | ||||
| 	@bin/robot | ||||
| 
 | ||||
| @ -29,6 +25,10 @@ flake8: .installed.cfg | ||||
| 	@bin/flake8 setup.py | ||||
| 	@bin/flake8 ./searx/ | ||||
| 
 | ||||
| tests: .installed.cfg flake8 | ||||
| 	@bin/test | ||||
| 	@grunt test --gruntfile searx/static/themes/oscar/gruntfile.js | ||||
| 
 | ||||
| coverage: .installed.cfg | ||||
| 	@bin/coverage run bin/test | ||||
| 	@bin/coverage report | ||||
| @ -45,18 +45,18 @@ minimal: bin/buildout minimal.cfg setup.py | ||||
| 	bin/buildout -c minimal.cfg $(options) | ||||
| 
 | ||||
| styles: | ||||
| 	@lessc -x searx/static/default/less/style.less > searx/static/default/css/style.css | ||||
| 	@lessc -x searx/static/oscar/less/bootstrap/bootstrap.less > searx/static/oscar/css/bootstrap.min.css | ||||
| 	@lessc -x searx/static/oscar/less/oscar/oscar.less > searx/static/oscar/css/oscar.min.css | ||||
| 	@lessc -x searx/static/themes/default/less/style.less > searx/static/themes/default/css/style.css | ||||
| 	@lessc -x searx/static/themes/oscar/less/bootstrap/bootstrap.less > searx/static/themes/oscar/css/bootstrap.min.css | ||||
| 	@lessc -x searx/static/themes/oscar/less/oscar/oscar.less > searx/static/themes/oscar/css/oscar.min.css | ||||
| 
 | ||||
| grunt: | ||||
| 	@grunt --gruntfile searx/static/oscar/gruntfile.js | ||||
| 	@grunt --gruntfile searx/static/themes/oscar/gruntfile.js | ||||
| 
 | ||||
| locales: | ||||
| 	@pybabel compile -d searx/translations | ||||
| 
 | ||||
| clean: | ||||
| 	@rm -rf .installed.cfg .mr.developer.cfg bin parts develop-eggs \
 | ||||
| 		searx.egg-info lib include .coverage coverage searx/static/default/css/*.css | ||||
| 		searx.egg-info lib include .coverage coverage searx/static/themes/default/css/*.css | ||||
| 
 | ||||
| .PHONY: all tests robot flake8 coverage production minimal styles locales clean | ||||
|  | ||||
							
								
								
									
										34
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						| @ -14,16 +14,17 @@ See the `wiki <https://github.com/asciimoo/searx/wiki>`__ for more information. | ||||
| Features | ||||
| ~~~~~~~~ | ||||
| 
 | ||||
| -  Tracking free | ||||
| -  Supports multiple output formats | ||||
|     -  json ``curl https://searx.me/?format=json&q=[query]`` | ||||
|     -  csv ``curl https://searx.me/?format=csv&q=[query]`` | ||||
|     -  opensearch/rss ``curl https://searx.me/?format=rss&q=[query]`` | ||||
| -  Opensearch support (you can set as default search engine) | ||||
| -  Configurable search engines/categories | ||||
| -  Different search languages | ||||
| -  Duckduckgo like !bang functionality with engine shortcuts | ||||
| -  Parallel queries - relatively fast | ||||
| - Tracking free | ||||
| - Supports multiple output formats | ||||
| 
 | ||||
|   - json ``curl https://searx.me/?format=json&q=[query]`` | ||||
|   - csv ``curl https://searx.me/?format=csv&q=[query]`` | ||||
|   - opensearch/rss ``curl https://searx.me/?format=rss&q=[query]`` | ||||
| - Opensearch support (you can set as default search engine) | ||||
| - Configurable search engines/categories | ||||
| - Different search languages | ||||
| - Duckduckgo like !bang functionality with engine shortcuts | ||||
| - Parallel queries - relatively fast | ||||
| 
 | ||||
| Installation | ||||
| ~~~~~~~~~~~~ | ||||
| @ -131,14 +132,11 @@ next time you run any other ``make`` command it will rebuild everithing. | ||||
| TODO | ||||
| ~~~~ | ||||
| 
 | ||||
| -  Moar engines | ||||
| -  Better ui | ||||
| -  Browser integration | ||||
| -  Documentation | ||||
| -  Fix ``flake8`` errors, ``make flake8`` will be merged into | ||||
|    ``make tests`` when it does not fail anymore | ||||
| -  Tests | ||||
| -  When we have more tests, we can integrate Travis-CI | ||||
| - Moar engines | ||||
| - Better ui | ||||
| - Browser integration | ||||
| - Documentation | ||||
| - Tests | ||||
| 
 | ||||
| Bugs | ||||
| ~~~~ | ||||
|  | ||||
| @ -15,9 +15,9 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. | ||||
| (C) 2013- by Adam Tauber, <asciimoo@gmail.com> | ||||
| ''' | ||||
| 
 | ||||
| import logging | ||||
| from os import environ | ||||
| from os.path import realpath, dirname, join, abspath | ||||
| from searx.https_rewrite import load_https_rules | ||||
| try: | ||||
|     from yaml import load | ||||
| except: | ||||
| @ -45,7 +45,17 @@ else: | ||||
| with open(settings_path) as settings_yaml: | ||||
|     settings = load(settings_yaml) | ||||
| 
 | ||||
| if settings.get('server', {}).get('debug'): | ||||
|     logging.basicConfig(level=logging.DEBUG) | ||||
| else: | ||||
|     logging.basicConfig(level=logging.WARNING) | ||||
| 
 | ||||
| logger = logging.getLogger('searx') | ||||
| 
 | ||||
| # load https rules only if https rewrite is enabled | ||||
| if settings.get('server', {}).get('https_rewrite'): | ||||
|     # loade https rules | ||||
|     from searx.https_rewrite import load_https_rules | ||||
|     load_https_rules(https_rewrite_path) | ||||
| 
 | ||||
| logger.info('Initialisation done') | ||||
|  | ||||
| @ -35,9 +35,9 @@ def request(query, params): | ||||
| # get response from search-request | ||||
| def response(resp): | ||||
|     results = [] | ||||
|      | ||||
| 
 | ||||
|     dom = html.fromstring(resp.text) | ||||
|      | ||||
| 
 | ||||
|     # parse results | ||||
|     for result in dom.xpath('//div[@class="photo"]'): | ||||
|         link = result.xpath('.//a')[0] | ||||
|  | ||||
| @ -22,6 +22,10 @@ from imp import load_source | ||||
| from flask.ext.babel import gettext | ||||
| from operator import itemgetter | ||||
| from searx import settings | ||||
| from searx import logger | ||||
| 
 | ||||
| 
 | ||||
| logger = logger.getChild('engines') | ||||
| 
 | ||||
| engine_dir = dirname(realpath(__file__)) | ||||
| 
 | ||||
| @ -81,7 +85,7 @@ def load_engine(engine_data): | ||||
|         if engine_attr.startswith('_'): | ||||
|             continue | ||||
|         if getattr(engine, engine_attr) is None: | ||||
|             print('[E] Engine config error: Missing attribute "{0}.{1}"'\ | ||||
|             logger.error('Missing engine config attribute: "{0}.{1}"' | ||||
|                   .format(engine.name, engine_attr)) | ||||
|             sys.exit(1) | ||||
| 
 | ||||
| @ -100,9 +104,8 @@ def load_engine(engine_data): | ||||
|         categories['general'].append(engine) | ||||
| 
 | ||||
|     if engine.shortcut: | ||||
|         # TODO check duplications | ||||
|         if engine.shortcut in engine_shortcuts: | ||||
|             print('[E] Engine config error: ambigious shortcut: {0}'\ | ||||
|             logger.error('Engine config error: ambigious shortcut: {0}' | ||||
|                   .format(engine.shortcut)) | ||||
|             sys.exit(1) | ||||
|         engine_shortcuts[engine.shortcut] = engine.name | ||||
| @ -199,7 +202,7 @@ def get_engines_stats(): | ||||
| 
 | ||||
| 
 | ||||
| if 'engines' not in settings or not settings['engines']: | ||||
|     print '[E] Error no engines found. Edit your settings.yml' | ||||
|     logger.error('No engines found. Edit your settings.yml') | ||||
|     exit(2) | ||||
| 
 | ||||
| for engine_data in settings['engines']: | ||||
|  | ||||
| @ -6,12 +6,14 @@ | ||||
| # @using-api   yes | ||||
| # @results     JSON | ||||
| # @stable      yes | ||||
| # @parse       url, title, thumbnail | ||||
| # @parse       url, title, thumbnail, publishedDate, embedded | ||||
| # | ||||
| # @todo        set content-parameter with correct data | ||||
| 
 | ||||
| from urllib import urlencode | ||||
| from json import loads | ||||
| from cgi import escape | ||||
| from datetime import datetime | ||||
| 
 | ||||
| # engine dependent config | ||||
| categories = ['videos'] | ||||
| @ -20,7 +22,9 @@ language_support = True | ||||
| 
 | ||||
| # search-url | ||||
| # see http://www.dailymotion.com/doc/api/obj-video.html | ||||
| search_url = 'https://api.dailymotion.com/videos?fields=title,description,duration,url,thumbnail_360_url&sort=relevance&limit=5&page={pageno}&{query}'  # noqa | ||||
| search_url = 'https://api.dailymotion.com/videos?fields=created_time,title,description,duration,url,thumbnail_360_url,id&sort=relevance&limit=5&page={pageno}&{query}'  # noqa | ||||
| embedded_url = '<iframe frameborder="0" width="540" height="304" ' +\ | ||||
|     'data-src="//www.dailymotion.com/embed/video/{videoid}" allowfullscreen></iframe>' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| @ -51,14 +55,17 @@ def response(resp): | ||||
|     for res in search_res['list']: | ||||
|         title = res['title'] | ||||
|         url = res['url'] | ||||
|         #content = res['description'] | ||||
|         content = '' | ||||
|         content = escape(res['description']) | ||||
|         thumbnail = res['thumbnail_360_url'] | ||||
|         publishedDate = datetime.fromtimestamp(res['created_time'], None) | ||||
|         embedded = embedded_url.format(videoid=res['id']) | ||||
| 
 | ||||
|         results.append({'template': 'videos.html', | ||||
|                         'url': url, | ||||
|                         'title': title, | ||||
|                         'content': content, | ||||
|                         'publishedDate': publishedDate, | ||||
|                         'embedded': embedded, | ||||
|                         'thumbnail': thumbnail}) | ||||
| 
 | ||||
|     # return results | ||||
|  | ||||
							
								
								
									
										61
									
								
								searx/engines/deezer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,61 @@ | ||||
| ## Deezer (Music) | ||||
| # | ||||
| # @website     https://deezer.com | ||||
| # @provide-api yes (http://developers.deezer.com/api/) | ||||
| # | ||||
| # @using-api   yes | ||||
| # @results     JSON | ||||
| # @stable      yes | ||||
| # @parse       url, title, content, embedded | ||||
| 
 | ||||
| from json import loads | ||||
| from urllib import urlencode | ||||
| 
 | ||||
| # engine dependent config | ||||
| categories = ['music'] | ||||
| paging = True | ||||
| 
 | ||||
| # search-url | ||||
| url = 'http://api.deezer.com/' | ||||
| search_url = url + 'search?{query}&index={offset}' | ||||
| 
 | ||||
| embedded_url = '<iframe scrolling="no" frameborder="0" allowTransparency="true" ' +\ | ||||
|     'data-src="http://www.deezer.com/plugins/player?type=tracks&id={audioid}" ' +\ | ||||
|     'width="540" height="80"></iframe>' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| def request(query, params): | ||||
|     offset = (params['pageno'] - 1) * 25 | ||||
| 
 | ||||
|     params['url'] = search_url.format(query=urlencode({'q': query}), | ||||
|                                       offset=offset) | ||||
| 
 | ||||
|     return params | ||||
| 
 | ||||
| 
 | ||||
| # get response from search-request | ||||
| def response(resp): | ||||
|     results = [] | ||||
| 
 | ||||
|     search_res = loads(resp.text) | ||||
| 
 | ||||
|     # parse results | ||||
|     for result in search_res.get('data', []): | ||||
|         if result['type'] == 'track': | ||||
|             title = result['title'] | ||||
|             url = result['link'] | ||||
|             content = result['artist']['name'] +\ | ||||
|                 " • " +\ | ||||
|                 result['album']['title'] +\ | ||||
|                 " • " + result['title'] | ||||
|             embedded = embedded_url.format(audioid=result['id']) | ||||
| 
 | ||||
|             # append result | ||||
|             results.append({'url': url, | ||||
|                             'title': title, | ||||
|                             'embedded': embedded, | ||||
|                             'content': content}) | ||||
| 
 | ||||
|     # return results | ||||
|     return results | ||||
							
								
								
									
										70
									
								
								searx/engines/digg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,70 @@ | ||||
| ## Digg (News, Social media) | ||||
| # | ||||
| # @website     https://digg.com/ | ||||
| # @provide-api no | ||||
| # | ||||
| # @using-api   no | ||||
| # @results     HTML (using search portal) | ||||
| # @stable      no (HTML can change) | ||||
| # @parse       url, title, content, publishedDate, thumbnail | ||||
| 
 | ||||
| from urllib import quote_plus | ||||
| from json import loads | ||||
| from lxml import html | ||||
| from cgi import escape | ||||
| from dateutil import parser | ||||
| 
 | ||||
| # engine dependent config | ||||
| categories = ['news', 'social media'] | ||||
| paging = True | ||||
| 
 | ||||
| # search-url | ||||
| base_url = 'https://digg.com/' | ||||
| search_url = base_url+'api/search/{query}.json?position={position}&format=html' | ||||
| 
 | ||||
| # specific xpath variables | ||||
| results_xpath = '//article' | ||||
| link_xpath = './/small[@class="time"]//a' | ||||
| title_xpath = './/h2//a//text()' | ||||
| content_xpath = './/p//text()' | ||||
| pubdate_xpath = './/time' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| def request(query, params): | ||||
|     offset = (params['pageno'] - 1) * 10 | ||||
|     params['url'] = search_url.format(position=offset, | ||||
|                                       query=quote_plus(query)) | ||||
|     return params | ||||
| 
 | ||||
| 
 | ||||
| # get response from search-request | ||||
| def response(resp): | ||||
|     results = [] | ||||
| 
 | ||||
|     search_result = loads(resp.text) | ||||
| 
 | ||||
|     if search_result['html'] == '': | ||||
|         return results | ||||
| 
 | ||||
|     dom = html.fromstring(search_result['html']) | ||||
| 
 | ||||
|     # parse results | ||||
|     for result in dom.xpath(results_xpath): | ||||
|         url = result.attrib.get('data-contenturl') | ||||
|         thumbnail = result.xpath('.//img')[0].attrib.get('src') | ||||
|         title = ''.join(result.xpath(title_xpath)) | ||||
|         content = escape(''.join(result.xpath(content_xpath))) | ||||
|         pubdate = result.xpath(pubdate_xpath)[0].attrib.get('datetime') | ||||
|         publishedDate = parser.parse(pubdate) | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': url, | ||||
|                         'title': title, | ||||
|                         'content': content, | ||||
|                         'template': 'videos.html', | ||||
|                         'publishedDate': publishedDate, | ||||
|                         'thumbnail': thumbnail}) | ||||
| 
 | ||||
|     # return results | ||||
|     return results | ||||
| @ -1,6 +1,7 @@ | ||||
| import json | ||||
| from urllib import urlencode | ||||
| from lxml import html | ||||
| from searx.utils import html_to_text | ||||
| from searx.engines.xpath import extract_text | ||||
| 
 | ||||
| url = 'https://api.duckduckgo.com/'\ | ||||
| @ -17,11 +18,6 @@ def result_to_text(url, text, htmlResult): | ||||
|         return text | ||||
| 
 | ||||
| 
 | ||||
| def html_to_text(htmlFragment): | ||||
|     dom = html.fromstring(htmlFragment) | ||||
|     return extract_text(dom) | ||||
| 
 | ||||
| 
 | ||||
| def request(query, params): | ||||
|     # TODO add kl={locale} | ||||
|     params['url'] = url.format(query=urlencode({'q': query})) | ||||
|  | ||||
							
								
								
									
										95
									
								
								searx/engines/flickr-noapi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,95 @@ | ||||
| #!/usr/bin/env python | ||||
| 
 | ||||
| #  Flickr (Images) | ||||
| # | ||||
| # @website     https://www.flickr.com | ||||
| # @provide-api yes (https://secure.flickr.com/services/api/flickr.photos.search.html) | ||||
| # | ||||
| # @using-api   no | ||||
| # @results     HTML | ||||
| # @stable      no | ||||
| # @parse       url, title, thumbnail, img_src | ||||
| 
 | ||||
| from urllib import urlencode | ||||
| from json import loads | ||||
| import re | ||||
| 
 | ||||
| categories = ['images'] | ||||
| 
 | ||||
| url = 'https://secure.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) | ||||
| image_sizes = ('o', 'k', 'h', 'b', 'c', 'z', 'n', 'm', 't', 'q', 's') | ||||
| 
 | ||||
| paging = True | ||||
| 
 | ||||
| 
 | ||||
| def build_flickr_url(user_id, photo_id): | ||||
|     return photo_url.format(userid=user_id, photoid=photo_id) | ||||
| 
 | ||||
| 
 | ||||
| def request(query, params): | ||||
|     params['url'] = search_url.format(query=urlencode({'text': query}), | ||||
|                                       page=params['pageno']) | ||||
|     return params | ||||
| 
 | ||||
| 
 | ||||
| def response(resp): | ||||
|     results = [] | ||||
| 
 | ||||
|     matches = regex.search(resp.text) | ||||
| 
 | ||||
|     if matches is None: | ||||
|         return results | ||||
| 
 | ||||
|     match = matches.group(1) | ||||
|     search_results = loads(match) | ||||
| 
 | ||||
|     if '_data' not in search_results: | ||||
|         return [] | ||||
| 
 | ||||
|     photos = search_results['_data'] | ||||
| 
 | ||||
|     for photo in photos: | ||||
| 
 | ||||
|         # In paged configuration, the first pages' photos | ||||
|         # are represented by a None object | ||||
|         if photo is None: | ||||
|             continue | ||||
| 
 | ||||
|         img_src = None | ||||
|         # From the biggest to the lowest format | ||||
|         for image_size in image_sizes: | ||||
|             if image_size in photo['sizes']: | ||||
|                 img_src = photo['sizes'][image_size]['displayUrl'] | ||||
|                 break | ||||
| 
 | ||||
|         if not img_src: | ||||
|             continue | ||||
| 
 | ||||
|         if 'id' not in photo['owner']: | ||||
|             continue | ||||
| 
 | ||||
|         url = build_flickr_url(photo['owner']['id'], photo['id']) | ||||
| 
 | ||||
|         title = photo['title'] | ||||
| 
 | ||||
|         content = '<span class="photo-author">' +\ | ||||
|                   photo['owner']['username'] +\ | ||||
|                   '</span><br />' | ||||
| 
 | ||||
|         if 'description' in photo: | ||||
|             content = content +\ | ||||
|                 '<span class="description">' +\ | ||||
|                 photo['description'] +\ | ||||
|                 '</span>' | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': url, | ||||
|                         'title': title, | ||||
|                         'img_src': img_src, | ||||
|                         'content': content, | ||||
|                         'template': 'images.html'}) | ||||
| 
 | ||||
|     return results | ||||
| @ -1,54 +1,87 @@ | ||||
| #!/usr/bin/env python | ||||
| 
 | ||||
| ## Flickr (Images) | ||||
| # | ||||
| # @website     https://www.flickr.com | ||||
| # @provide-api yes (https://secure.flickr.com/services/api/flickr.photos.search.html) | ||||
| # | ||||
| # @using-api   yes | ||||
| # @results     JSON | ||||
| # @stable      yes | ||||
| # @parse       url, title, thumbnail, img_src | ||||
| #More info on api-key : https://www.flickr.com/services/apps/create/ | ||||
| 
 | ||||
| from urllib import urlencode | ||||
| #from json import loads | ||||
| from urlparse import urljoin | ||||
| from lxml import html | ||||
| from time import time | ||||
| from json import loads | ||||
| 
 | ||||
| categories = ['images'] | ||||
| 
 | ||||
| url = 'https://secure.flickr.com/' | ||||
| search_url = url+'search/?{query}&page={page}' | ||||
| results_xpath = '//div[@class="view display-item-tile"]/figure/div' | ||||
| nb_per_page = 15 | ||||
| paging = True | ||||
| api_key = None | ||||
| 
 | ||||
| 
 | ||||
| url = 'https://api.flickr.com/services/rest/?method=flickr.photos.search' +\ | ||||
|       '&api_key={api_key}&{text}&sort=relevance' +\ | ||||
|       '&extras=description%2C+owner_name%2C+url_o%2C+url_z' +\ | ||||
|       '&per_page={nb_per_page}&format=json&nojsoncallback=1&page={page}' | ||||
| photo_url = 'https://www.flickr.com/photos/{userid}/{photoid}' | ||||
| 
 | ||||
| paging = True | ||||
| 
 | ||||
| 
 | ||||
| def build_flickr_url(user_id, photo_id): | ||||
|     return photo_url.format(userid=user_id, photoid=photo_id) | ||||
| 
 | ||||
| 
 | ||||
| def request(query, params): | ||||
|     params['url'] = search_url.format(query=urlencode({'text': query}), | ||||
|                                       page=params['pageno']) | ||||
|     time_string = str(int(time())-3) | ||||
|     params['cookies']['BX'] = '3oqjr6d9nmpgl&b=3&s=dh' | ||||
|     params['cookies']['xb'] = '421409' | ||||
|     params['cookies']['localization'] = 'en-us' | ||||
|     params['cookies']['flrbp'] = time_string +\ | ||||
|         '-3a8cdb85a427a33efda421fbda347b2eaf765a54' | ||||
|     params['cookies']['flrbs'] = time_string +\ | ||||
|         '-ed142ae8765ee62c9ec92a9513665e0ee1ba6776' | ||||
|     params['cookies']['flrb'] = '9' | ||||
|     params['url'] = url.format(text=urlencode({'text': query}), | ||||
|                                api_key=api_key, | ||||
|                                nb_per_page=nb_per_page, | ||||
|                                page=params['pageno']) | ||||
|     return params | ||||
| 
 | ||||
| 
 | ||||
| def response(resp): | ||||
|     results = [] | ||||
|     dom = html.fromstring(resp.text) | ||||
|     for result in dom.xpath(results_xpath): | ||||
|         img = result.xpath('.//img') | ||||
| 
 | ||||
|         if not img: | ||||
|     search_results = loads(resp.text) | ||||
| 
 | ||||
|     # return empty array if there are no results | ||||
|     if not 'photos' in search_results: | ||||
|         return [] | ||||
| 
 | ||||
|     if not 'photo' in search_results['photos']: | ||||
|         return [] | ||||
| 
 | ||||
|     photos = search_results['photos']['photo'] | ||||
| 
 | ||||
|     # parse results | ||||
|     for photo in photos: | ||||
|         if 'url_o' in photo: | ||||
|             img_src = photo['url_o'] | ||||
|         elif 'url_z' in photo: | ||||
|             img_src = photo['url_z'] | ||||
|         else: | ||||
|             continue | ||||
| 
 | ||||
|         img = img[0] | ||||
|         img_src = 'https:'+img.attrib.get('src') | ||||
|         url = build_flickr_url(photo['owner'], photo['id']) | ||||
| 
 | ||||
|         if not img_src: | ||||
|             continue | ||||
|         title = photo['title'] | ||||
| 
 | ||||
|         href = urljoin(url, result.xpath('.//a')[0].attrib.get('href')) | ||||
|         title = img.attrib.get('alt', '') | ||||
|         results.append({'url': href, | ||||
|         content = '<span class="photo-author">' +\ | ||||
|                   photo['ownername'] +\ | ||||
|                   '</span><br />' +\ | ||||
|                   '<span class="description">' +\ | ||||
|                   photo['description']['_content'] +\ | ||||
|                   '</span>' | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': url, | ||||
|                         'title': title, | ||||
|                         'img_src': img_src, | ||||
|                         'content': content, | ||||
|                         'template': 'images.html'}) | ||||
| 
 | ||||
|     # return results | ||||
|     return results | ||||
|  | ||||
| @ -24,7 +24,7 @@ search_url = url + 'search/{search_term}/{pageno}/' | ||||
| 
 | ||||
| # specific xpath variables | ||||
| magnet_xpath = './/a[@title="Torrent magnet link"]' | ||||
| #content_xpath = './/font[@class="detDesc"]//text()' | ||||
| content_xpath = './/span[@class="font11px lightgrey block"]' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| @ -56,7 +56,8 @@ def response(resp): | ||||
|         link = result.xpath('.//a[@class="cellMainLink"]')[0] | ||||
|         href = urljoin(url, link.attrib['href']) | ||||
|         title = ' '.join(link.xpath('.//text()')) | ||||
|         content = escape(html.tostring(result.xpath('.//span[@class="font11px lightgrey block"]')[0], method="text")) | ||||
|         content = escape(html.tostring(result.xpath(content_xpath)[0], | ||||
|                                        method="text")) | ||||
|         seed = result.xpath('.//td[contains(@class, "green")]/text()')[0] | ||||
|         leech = result.xpath('.//td[contains(@class, "red")]/text()')[0] | ||||
| 
 | ||||
|  | ||||
| @ -38,10 +38,14 @@ def response(resp): | ||||
|     for result in search_results['results']: | ||||
|         href = result['url'] | ||||
|         title = "[" + result['type'] + "] " +\ | ||||
|                 result['namespace'] + " " + result['name'] | ||||
|         content = '<span class="highlight">[' + result['type'] + "] " +\ | ||||
|                   result['name'] + " " + result['synopsis'] +\ | ||||
|                   "</span><br />" + result['description'] | ||||
|                 result['namespace'] +\ | ||||
|                 " " + result['name'] | ||||
|         content = '<span class="highlight">[' +\ | ||||
|                   result['type'] + "] " +\ | ||||
|                   result['name'] + " " +\ | ||||
|                   result['synopsis'] +\ | ||||
|                   "</span><br />" +\ | ||||
|                   result['description'] | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': href, | ||||
|  | ||||
| @ -6,10 +6,11 @@ | ||||
| # @using-api   yes | ||||
| # @results     JSON | ||||
| # @stable      yes | ||||
| # @parse       url, title, content | ||||
| # @parse       url, title, content, publishedDate, embedded | ||||
| 
 | ||||
| from json import loads | ||||
| from urllib import urlencode | ||||
| from urllib import urlencode, quote_plus | ||||
| from dateutil import parser | ||||
| 
 | ||||
| # engine dependent config | ||||
| categories = ['music'] | ||||
| @ -27,6 +28,10 @@ search_url = url + 'search?{query}'\ | ||||
|                          '&linked_partitioning=1'\ | ||||
|                          '&client_id={client_id}'   # noqa | ||||
| 
 | ||||
| embedded_url = '<iframe width="100%" height="166" ' +\ | ||||
|     'scrolling="no" frameborder="no" ' +\ | ||||
|     'data-src="https://w.soundcloud.com/player/?url={uri}"></iframe>' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| def request(query, params): | ||||
| @ -50,10 +55,15 @@ def response(resp): | ||||
|         if result['kind'] in ('track', 'playlist'): | ||||
|             title = result['title'] | ||||
|             content = result['description'] | ||||
|             publishedDate = parser.parse(result['last_modified']) | ||||
|             uri = quote_plus(result['uri']) | ||||
|             embedded = embedded_url.format(uri=uri) | ||||
| 
 | ||||
|             # append result | ||||
|             results.append({'url': result['permalink_url'], | ||||
|                             'title': title, | ||||
|                             'publishedDate': publishedDate, | ||||
|                             'embedded': embedded, | ||||
|                             'content': content}) | ||||
| 
 | ||||
|     # return results | ||||
|  | ||||
| @ -66,7 +66,10 @@ def response(resp): | ||||
|             continue | ||||
|         link = links[0] | ||||
|         url = link.attrib.get('href') | ||||
|         title = escape(link.text_content()) | ||||
|         try: | ||||
|             title = escape(link.text_content()) | ||||
|         except UnicodeDecodeError: | ||||
|             continue | ||||
| 
 | ||||
|         # block google-ad url's | ||||
|         if re.match("^http(s|)://www.google.[a-z]+/aclk.*$", url): | ||||
|  | ||||
							
								
								
									
										78
									
								
								searx/engines/subtitleseeker.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,78 @@ | ||||
| ## Subtitleseeker (Video) | ||||
| # | ||||
| # @website     http://www.subtitleseeker.com | ||||
| # @provide-api no | ||||
| # | ||||
| # @using-api   no | ||||
| # @results     HTML | ||||
| # @stable      no (HTML can change) | ||||
| # @parse       url, title, content | ||||
| 
 | ||||
| from cgi import escape | ||||
| from urllib import quote_plus | ||||
| from lxml import html | ||||
| from searx.languages import language_codes | ||||
| 
 | ||||
| # engine dependent config | ||||
| categories = ['videos'] | ||||
| paging = True | ||||
| language = "" | ||||
| 
 | ||||
| # search-url | ||||
| url = 'http://www.subtitleseeker.com/' | ||||
| search_url = url+'search/TITLES/{query}&p={pageno}' | ||||
| 
 | ||||
| # specific xpath variables | ||||
| results_xpath = '//div[@class="boxRows"]' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| def request(query, params): | ||||
|     params['url'] = search_url.format(query=quote_plus(query), | ||||
|                                       pageno=params['pageno']) | ||||
|     return params | ||||
| 
 | ||||
| 
 | ||||
| # get response from search-request | ||||
| def response(resp): | ||||
|     results = [] | ||||
| 
 | ||||
|     dom = html.fromstring(resp.text) | ||||
| 
 | ||||
|     search_lang = "" | ||||
| 
 | ||||
|     if resp.search_params['language'] != 'all': | ||||
|         search_lang = [lc[1] | ||||
|                        for lc in language_codes | ||||
|                        if lc[0][:2] == resp.search_params['language']][0] | ||||
| 
 | ||||
|     # parse results | ||||
|     for result in dom.xpath(results_xpath): | ||||
|         link = result.xpath(".//a")[0] | ||||
|         href = link.attrib.get('href') | ||||
| 
 | ||||
|         if language is not "": | ||||
|             href = href + language + '/' | ||||
|         elif search_lang: | ||||
|             href = href + search_lang + '/' | ||||
| 
 | ||||
|         title = escape(link.xpath(".//text()")[0]) | ||||
| 
 | ||||
|         content = result.xpath('.//div[contains(@class,"red")]//text()')[0] | ||||
|         content = content + " - " | ||||
|         text = result.xpath('.//div[contains(@class,"grey-web")]')[0] | ||||
|         content = content + html.tostring(text, method='text') | ||||
| 
 | ||||
|         if result.xpath(".//span") != []: | ||||
|             content = content +\ | ||||
|                 " - (" +\ | ||||
|                 result.xpath(".//span//text()")[0].strip() +\ | ||||
|                 ")" | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': href, | ||||
|                         'title': title, | ||||
|                         'content': escape(content)}) | ||||
| 
 | ||||
|     # return results | ||||
|     return results | ||||
| @ -1,6 +1,6 @@ | ||||
| ## Twitter (Social media) | ||||
| # | ||||
| # @website     https://www.bing.com/news | ||||
| # @website     https://twitter.com/ | ||||
| # @provide-api yes (https://dev.twitter.com/docs/using-search) | ||||
| # | ||||
| # @using-api   no | ||||
| @ -14,6 +14,7 @@ from urlparse import urljoin | ||||
| from urllib import urlencode | ||||
| from lxml import html | ||||
| from cgi import escape | ||||
| from datetime import datetime | ||||
| 
 | ||||
| # engine dependent config | ||||
| categories = ['social media'] | ||||
| @ -27,7 +28,8 @@ search_url = base_url+'search?' | ||||
| results_xpath = '//li[@data-item-type="tweet"]' | ||||
| link_xpath = './/small[@class="time"]//a' | ||||
| title_xpath = './/span[@class="username js-action-profile-name"]//text()' | ||||
| content_xpath = './/p[@class="js-tweet-text tweet-text"]//text()' | ||||
| content_xpath = './/p[@class="js-tweet-text tweet-text"]' | ||||
| timestamp_xpath = './/span[contains(@class,"_timestamp")]' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| @ -52,12 +54,21 @@ def response(resp): | ||||
|         link = tweet.xpath(link_xpath)[0] | ||||
|         url = urljoin(base_url, link.attrib.get('href')) | ||||
|         title = ''.join(tweet.xpath(title_xpath)) | ||||
|         content = escape(''.join(tweet.xpath(content_xpath))) | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': url, | ||||
|                         'title': title, | ||||
|                         'content': content}) | ||||
|         content = escape(html.tostring(tweet.xpath(content_xpath)[0], method='text', encoding='UTF-8').decode("utf-8")) | ||||
|         pubdate = tweet.xpath(timestamp_xpath) | ||||
|         if len(pubdate) > 0: | ||||
|             timestamp = float(pubdate[0].attrib.get('data-time')) | ||||
|             publishedDate = datetime.fromtimestamp(timestamp, None) | ||||
|             # append result | ||||
|             results.append({'url': url, | ||||
|                             'title': title, | ||||
|                             'content': content, | ||||
|                             'publishedDate': publishedDate}) | ||||
|         else: | ||||
|             # append result | ||||
|             results.append({'url': url, | ||||
|                             'title': title, | ||||
|                             'content': content}) | ||||
| 
 | ||||
|     # return results | ||||
|     return results | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| ## Vimeo (Videos) | ||||
| #  Vimeo (Videos) | ||||
| # | ||||
| # @website     https://vimeo.com/ | ||||
| # @provide-api yes (http://developer.vimeo.com/api), | ||||
| @ -7,14 +7,14 @@ | ||||
| # @using-api   no (TODO, rewrite to api) | ||||
| # @results     HTML (using search portal) | ||||
| # @stable      no (HTML can change) | ||||
| # @parse       url, title, publishedDate,  thumbnail | ||||
| # @parse       url, title, publishedDate,  thumbnail, embedded | ||||
| # | ||||
| # @todo        rewrite to api | ||||
| # @todo        set content-parameter with correct data | ||||
| 
 | ||||
| from urllib import urlencode | ||||
| from HTMLParser import HTMLParser | ||||
| from lxml import html | ||||
| from HTMLParser import HTMLParser | ||||
| from searx.engines.xpath import extract_text | ||||
| from dateutil import parser | ||||
| 
 | ||||
| @ -23,26 +23,26 @@ categories = ['videos'] | ||||
| paging = True | ||||
| 
 | ||||
| # search-url | ||||
| base_url = 'https://vimeo.com' | ||||
| base_url = 'http://vimeo.com' | ||||
| search_url = base_url + '/search/page:{pageno}?{query}' | ||||
| 
 | ||||
| # specific xpath variables | ||||
| url_xpath = './a/@href' | ||||
| content_xpath = './a/img/@src' | ||||
| title_xpath = './a/div[@class="data"]/p[@class="title"]/text()' | ||||
| 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' | ||||
| 
 | ||||
| embedded_url = '<iframe data-src="//player.vimeo.com/video{videoid}" ' +\ | ||||
|     'width="540" height="304" frameborder="0" ' +\ | ||||
|     'webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| def request(query, params): | ||||
|     params['url'] = search_url.format(pageno=params['pageno'], | ||||
|                                       query=urlencode({'q': query})) | ||||
| 
 | ||||
|     # TODO required? | ||||
|     params['cookies']['__utma'] =\ | ||||
|         '00000000.000#0000000.0000000000.0000000000.0000000000.0' | ||||
| 
 | ||||
|     return params | ||||
| 
 | ||||
| 
 | ||||
| @ -51,16 +51,17 @@ def response(resp): | ||||
|     results = [] | ||||
| 
 | ||||
|     dom = html.fromstring(resp.text) | ||||
| 
 | ||||
|     p = HTMLParser() | ||||
| 
 | ||||
|     # parse results | ||||
|     for result in dom.xpath(results_xpath): | ||||
|         url = base_url + result.xpath(url_xpath)[0] | ||||
|         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]) | ||||
|         publishedDate = parser.parse(extract_text( | ||||
|             result.xpath(publishedDate_xpath)[0])) | ||||
|         embedded = embedded_url.format(videoid=videoid) | ||||
| 
 | ||||
|         # append result | ||||
|         results.append({'url': url, | ||||
| @ -68,6 +69,7 @@ def response(resp): | ||||
|                         'content': '', | ||||
|                         'template': 'videos.html', | ||||
|                         'publishedDate': publishedDate, | ||||
|                         'embedded': embedded, | ||||
|                         'thumbnail': thumbnail}) | ||||
| 
 | ||||
|     # return results | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| import json | ||||
| from requests import get | ||||
| from urllib import urlencode | ||||
| import locale | ||||
| import dateutil.parser | ||||
| 
 | ||||
| result_count = 1 | ||||
| wikidata_host = 'https://www.wikidata.org' | ||||
| @ -35,6 +37,16 @@ def response(resp): | ||||
|     language = resp.search_params['language'].split('_')[0] | ||||
|     if language == 'all': | ||||
|         language = 'en' | ||||
| 
 | ||||
|     try: | ||||
|         locale.setlocale(locale.LC_ALL, str(resp.search_params['language'])) | ||||
|     except: | ||||
|         try: | ||||
|             locale.setlocale(locale.LC_ALL, 'en_US') | ||||
|         except: | ||||
|             pass | ||||
|         pass | ||||
| 
 | ||||
|     url = url_detail.format(query=urlencode({'ids': '|'.join(wikidata_ids), | ||||
|                                             'languages': language + '|en'})) | ||||
| 
 | ||||
| @ -164,10 +176,12 @@ def getDetail(jsonresponse, wikidata_id, language): | ||||
| 
 | ||||
|     date_of_birth = get_time(claims, 'P569', None) | ||||
|     if date_of_birth is not None: | ||||
|         date_of_birth = dateutil.parser.parse(date_of_birth[8:]).strftime(locale.nl_langinfo(locale.D_FMT)) | ||||
|         attributes.append({'label': 'Date of birth', 'value': date_of_birth}) | ||||
| 
 | ||||
|     date_of_death = get_time(claims, 'P570', None) | ||||
|     if date_of_death is not None: | ||||
|         date_of_death = dateutil.parser.parse(date_of_death[8:]).strftime(locale.nl_langinfo(locale.D_FMT)) | ||||
|         attributes.append({'label': 'Date of death', 'value': date_of_death}) | ||||
| 
 | ||||
|     if len(attributes) == 0 and len(urls) == 2 and len(description) == 0: | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
| # @using-api   yes | ||||
| # @results     JSON | ||||
| # @stable      yes | ||||
| # @parse       url, title, content, publishedDate, thumbnail | ||||
| # @parse       url, title, content, publishedDate, thumbnail, embedded | ||||
| 
 | ||||
| from json import loads | ||||
| from urllib import urlencode | ||||
| @ -19,7 +19,11 @@ language_support = True | ||||
| 
 | ||||
| # search-url | ||||
| base_url = 'https://gdata.youtube.com/feeds/api/videos' | ||||
| search_url = base_url + '?alt=json&{query}&start-index={index}&max-results=5'  # noqa | ||||
| search_url = base_url + '?alt=json&{query}&start-index={index}&max-results=5' | ||||
| 
 | ||||
| embedded_url = '<iframe width="540" height="304" ' +\ | ||||
|     'data-src="//www.youtube-nocookie.com/embed/{videoid}" ' +\ | ||||
|     'frameborder="0" allowfullscreen></iframe>' | ||||
| 
 | ||||
| 
 | ||||
| # do search-request | ||||
| @ -60,6 +64,8 @@ def response(resp): | ||||
|         if url.endswith('&'): | ||||
|             url = url[:-1] | ||||
| 
 | ||||
|         videoid = url[32:] | ||||
| 
 | ||||
|         title = result['title']['$t'] | ||||
|         content = '' | ||||
|         thumbnail = '' | ||||
| @ -72,12 +78,15 @@ def response(resp): | ||||
| 
 | ||||
|         content = result['content']['$t'] | ||||
| 
 | ||||
|         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 | ||||
|  | ||||
| @ -20,8 +20,11 @@ from urlparse import urlparse | ||||
| from lxml import etree | ||||
| from os import listdir | ||||
| from os.path import isfile, isdir, join | ||||
| from searx import logger | ||||
| 
 | ||||
| 
 | ||||
| logger = logger.getChild("https_rewrite") | ||||
| 
 | ||||
| # https://gitweb.torproject.org/\ | ||||
| # pde/https-everywhere.git/tree/4.0:/src/chrome/content/rules | ||||
| 
 | ||||
| @ -131,7 +134,7 @@ def load_single_https_ruleset(filepath): | ||||
| def load_https_rules(rules_path): | ||||
|     # check if directory exists | ||||
|     if not isdir(rules_path): | ||||
|         print("[E] directory not found: '" + rules_path + "'") | ||||
|         logger.error("directory not found: '" + rules_path + "'") | ||||
|         return | ||||
| 
 | ||||
|     # search all xml files which are stored in the https rule directory | ||||
| @ -151,8 +154,7 @@ def load_https_rules(rules_path): | ||||
|         # append ruleset | ||||
|         https_rules.append(ruleset) | ||||
| 
 | ||||
|     print(' * {n} https-rules loaded'.format(n=len(https_rules))) | ||||
| 
 | ||||
|     logger.info('{n} rules loaded'.format(n=len(https_rules))) | ||||
| 
 | ||||
| 
 | ||||
| def https_url_rewrite(result): | ||||
|  | ||||
| @ -29,21 +29,23 @@ from searx.engines import ( | ||||
| from searx.languages import language_codes | ||||
| from searx.utils import gen_useragent | ||||
| from searx.query import Query | ||||
| from searx import logger | ||||
| 
 | ||||
| 
 | ||||
| logger = logger.getChild('search') | ||||
| 
 | ||||
| number_of_searches = 0 | ||||
| 
 | ||||
| 
 | ||||
| def search_request_wrapper(fn, url, engine_name, **kwargs): | ||||
|     try: | ||||
|         return fn(url, **kwargs) | ||||
|     except Exception, e: | ||||
|     except: | ||||
|         # increase errors stats | ||||
|         engines[engine_name].stats['errors'] += 1 | ||||
| 
 | ||||
|         # print engine name and specific error message | ||||
|         print('[E] Error with engine "{0}":\n\t{1}'.format( | ||||
|             engine_name, str(e))) | ||||
|         logger.exception('engine crash: {0}'.format(engine_name)) | ||||
|         return | ||||
| 
 | ||||
| 
 | ||||
| @ -66,14 +68,19 @@ def threaded_requests(requests): | ||||
|             remaining_time = max(0.0, timeout_limit - (time() - search_start)) | ||||
|             th.join(remaining_time) | ||||
|             if th.isAlive(): | ||||
|                 print('engine timeout: {0}'.format(th._engine_name)) | ||||
| 
 | ||||
|                 logger.warning('engine timeout: {0}'.format(th._engine_name)) | ||||
| 
 | ||||
| 
 | ||||
| # get default reqest parameter | ||||
| def default_request_params(): | ||||
|     return { | ||||
|         'method': 'GET', 'headers': {}, 'data': {}, 'url': '', 'cookies': {}, 'verify': True} | ||||
|         'method': 'GET', | ||||
|         'headers': {}, | ||||
|         'data': {}, | ||||
|         'url': '', | ||||
|         'cookies': {}, | ||||
|         'verify': True | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| # create a callback wrapper for the search engine results | ||||
| @ -487,14 +494,15 @@ class Search(object): | ||||
|                 continue | ||||
| 
 | ||||
|             # append request to list | ||||
|             requests.append((req, request_params['url'], request_args, selected_engine['name'])) | ||||
|             requests.append((req, request_params['url'], | ||||
|                              request_args, | ||||
|                              selected_engine['name'])) | ||||
| 
 | ||||
|         if not requests: | ||||
|             return results, suggestions, answers, infoboxes | ||||
|         # send all search-request | ||||
|         threaded_requests(requests) | ||||
| 
 | ||||
| 
 | ||||
|         while not results_queue.empty(): | ||||
|             engine_name, engine_results = results_queue.get_nowait() | ||||
| 
 | ||||
|  | ||||
| @ -35,6 +35,10 @@ engines: | ||||
|     engine : currency_convert | ||||
|     categories : general | ||||
|     shortcut : cc | ||||
|      | ||||
|   - name : deezer | ||||
|     engine : deezer | ||||
|     shortcut : dz | ||||
| 
 | ||||
|   - name : deviantart | ||||
|     engine : deviantart | ||||
| @ -44,6 +48,10 @@ engines: | ||||
|   - name : ddg definitions | ||||
|     engine : duckduckgo_definitions | ||||
|     shortcut : ddd | ||||
|      | ||||
|   - name : digg | ||||
|     engine : digg | ||||
|     shortcut : dg | ||||
| 
 | ||||
|   - name : wikidata | ||||
|     engine : wikidata | ||||
| @ -70,10 +78,14 @@ engines: | ||||
|     shortcut : px | ||||
| 
 | ||||
|   - name : flickr | ||||
|     engine : flickr | ||||
|     categories : images | ||||
|     shortcut : fl | ||||
|     timeout: 3.0 | ||||
| # You can use the engine using the official stable API, but you need an API key | ||||
| # See : https://www.flickr.com/services/apps/create/ | ||||
| #    engine : flickr | ||||
| #    api_key: 'apikey' # required! | ||||
| # Or you can use the html non-stable engine, activated by default | ||||
|     engine : flickr-noapi | ||||
| 
 | ||||
|   - name : general-file | ||||
|     engine : generalfile | ||||
| @ -95,6 +107,33 @@ engines: | ||||
|     engine : google_news | ||||
|     shortcut : gon | ||||
| 
 | ||||
|   - name : google play apps | ||||
|     engine        : xpath | ||||
|     search_url    : https://play.google.com/store/search?q={query}&c=apps | ||||
|     url_xpath     : //a[@class="title"]/@href | ||||
|     title_xpath   : //a[@class="title"] | ||||
|     content_xpath : //a[@class="subtitle"] | ||||
|     categories : files | ||||
|     shortcut : gpa | ||||
|      | ||||
|   - name : google play movies | ||||
|     engine        : xpath | ||||
|     search_url    : https://play.google.com/store/search?q={query}&c=movies | ||||
|     url_xpath     : //a[@class="title"]/@href | ||||
|     title_xpath   : //a[@class="title"] | ||||
|     content_xpath : //a[@class="subtitle"] | ||||
|     categories : videos | ||||
|     shortcut : gpm | ||||
|      | ||||
|   - name : google play music | ||||
|     engine        : xpath | ||||
|     search_url    : https://play.google.com/store/search?q={query}&c=music | ||||
|     url_xpath     : //a[@class="title"]/@href | ||||
|     title_xpath   : //a[@class="title"] | ||||
|     content_xpath : //a[@class="subtitle"] | ||||
|     categories : music | ||||
|     shortcut : gps | ||||
|      | ||||
|   - name : openstreetmap | ||||
|     engine : openstreetmap | ||||
|     shortcut : osm | ||||
| @ -127,6 +166,13 @@ engines: | ||||
|     engine : searchcode_code | ||||
|     shortcut : scc | ||||
| 
 | ||||
|   - name : subtitleseeker | ||||
|     engine : subtitleseeker | ||||
|     shortcut : ss | ||||
| # The language is an option. You can put any language written in english | ||||
| # Examples : English, French, German, Hungarian, Chinese... | ||||
| #    language : English | ||||
| 
 | ||||
|   - name : startpage | ||||
|     engine : startpage | ||||
|     shortcut : sp | ||||
| @ -194,3 +240,4 @@ locales: | ||||
|     it : Italiano | ||||
|     nl : Nederlands | ||||
|     ja : 日本語 (Japanese) | ||||
|     tr : Türkçe | ||||
|  | ||||
							
								
								
									
										2
									
								
								searx/static/oscar/js/searx.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,2 +0,0 @@ | ||||
| /*! oscar/searx.min.js | 22-12-2014 | https://github.com/asciimoo/searx */ | ||||
| requirejs.config({baseUrl:"./static/oscar/js",paths:{app:"../app"}}),searx.autocompleter&&(searx.searchResults=new Bloodhound({datumTokenizer:Bloodhound.tokenizers.obj.whitespace("value"),queryTokenizer:Bloodhound.tokenizers.whitespace,remote:"/autocompleter?q=%QUERY"}),searx.searchResults.initialize()),$(document).ready(function(){searx.autocompleter&&$("#q").typeahead(null,{name:"search-results",displayKey:function(a){return a},source:searx.searchResults.ttAdapter()})}),$(document).ready(function(){$("#q.autofocus").focus(),$(".select-all-on-click").click(function(){$(this).select()}),$(".btn-collapse").click(function(){var a=$(this).data("btn-text-collapsed"),b=$(this).data("btn-text-not-collapsed");""!==a&&""!==b&&(new_html=$(this).hasClass("collapsed")?$(this).html().replace(a,b):$(this).html().replace(b,a),$(this).html(new_html))}),$(".btn-toggle .btn").click(function(){var a="btn-"+$(this).data("btn-class"),b=$(this).data("btn-label-default"),c=$(this).data("btn-label-toggled");""!==c&&(new_html=$(this).hasClass("btn-default")?$(this).html().replace(b,c):$(this).html().replace(c,b),$(this).html(new_html)),$(this).toggleClass(a),$(this).toggleClass("btn-default")}),$(".btn-sm").dblclick(function(){var a="btn-"+$(this).data("btn-class");$(this).hasClass("btn-default")?($(".btn-sm > input").attr("checked","checked"),$(".btn-sm > input").prop("checked",!0),$(".btn-sm").addClass(a),$(".btn-sm").addClass("active"),$(".btn-sm").removeClass("btn-default")):($(".btn-sm > input").attr("checked",""),$(".btn-sm > input").removeAttr("checked"),$(".btn-sm > input").checked=!1,$(".btn-sm").removeClass(a),$(".btn-sm").removeClass("active"),$(".btn-sm").addClass("btn-default"))})}),$(document).ready(function(){$(".searx_overpass_request").on("click",function(a){var b="https://overpass-api.de/api/interpreter?data=",c=b+"[out:json][timeout:25];(",d=");out meta;",e=$(this).data("osm-id"),f=$(this).data("osm-type"),g=$(this).data("result-table"),h="#"+$(this).data("result-table-loadicon"),i=["addr:city","addr:country","addr:housenumber","addr:postcode","addr:street"];if(e&&f&&g){g="#"+g;var j=null;switch(f){case"node":j=c+"node("+e+");"+d;break;case"way":j=c+"way("+e+");"+d;break;case"relation":j=c+"relation("+e+");"+d}if(j){$.ajax(j).done(function(a){if(a&&a.elements&&a.elements[0]){var b=a.elements[0],c=$(g).html();for(var d in b.tags)if(null===b.tags.name||-1==i.indexOf(d)){switch(c+="<tr><td>"+d+"</td><td>",d){case"phone":case"fax":c+='<a href="tel:'+b.tags[d].replace(/ /g,"")+'">'+b.tags[d]+"</a>";break;case"email":c+='<a href="mailto:'+b.tags[d]+'">'+b.tags[d]+"</a>";break;case"website":case"url":c+='<a href="'+b.tags[d]+'">'+b.tags[d]+"</a>";break;case"wikidata":c+='<a href="https://www.wikidata.org/wiki/'+b.tags[d]+'">'+b.tags[d]+"</a>";break;case"wikipedia":if(-1!=b.tags[d].indexOf(":")){c+='<a href="https://'+b.tags[d].substring(0,b.tags[d].indexOf(":"))+".wikipedia.org/wiki/"+b.tags[d].substring(b.tags[d].indexOf(":")+1)+'">'+b.tags[d]+"</a>";break}default:c+=b.tags[d]}c+="</td></tr>"}$(g).html(c),$(g).removeClass("hidden"),$(h).addClass("hidden")}}).fail(function(){$(h).html($(h).html()+'<p class="text-muted">could not load data!</p>')})}}$(this).off(a)}),$(".searx_init_map").on("click",function(a){var b=$(this).data("leaflet-target"),c=$(this).data("map-lon"),d=$(this).data("map-lat"),e=$(this).data("map-zoom"),f=$(this).data("map-boundingbox"),g=$(this).data("map-geojson");require(["leaflet-0.7.3.min"],function(){f&&(southWest=L.latLng(f[0],f[2]),northEast=L.latLng(f[1],f[3]),map_bounds=L.latLngBounds(southWest,northEast)),L.Icon.Default.imagePath="./static/oscar/img/map";{var a=L.map(b),h="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",i='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors',j=new L.TileLayer(h,{minZoom:1,maxZoom:19,attribution:i}),k="http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg",l='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors | Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">',m=new L.TileLayer(k,{minZoom:1,maxZoom:18,subdomains:"1234",attribution:l}),n="http://otile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg",o='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors | Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="https://developer.mapquest.com/content/osm/mq_logo.png"> | Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency';new L.TileLayer(n,{minZoom:1,maxZoom:11,subdomains:"1234",attribution:o})}map_bounds?setTimeout(function(){a.fitBounds(map_bounds,{maxZoom:17})},0):c&&d&&(e?a.setView(new L.LatLng(d,c),e):a.setView(new L.LatLng(d,c),8)),a.addLayer(m);var p={"OSM Mapnik":j,MapQuest:m};L.control.layers(p).addTo(a),g&&L.geoJson(g).addTo(a)}),$(this).off(a)})}); | ||||
| Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 342 KiB | 
| Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB | 
| Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB | 
| Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB | 
| Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB | 
| Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB | 
| Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB | 
| Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB | 
| Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB | 
| Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB | 
| Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB | 
| Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB | 
| Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB | 
| Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB | 
| Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB | 
| Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB | 
| Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B | 
| Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB | 
| Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB | 
| Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB | 
| @ -1,14 +1,14 @@ | ||||
| install dependencies | ||||
| ~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| run this command in the directory ``searx/static/oscar`` | ||||
| run this command in the directory ``searx/static/themes/oscar`` | ||||
| 
 | ||||
| ``npm install`` | ||||
| 
 | ||||
| compile sources | ||||
| ~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| run this command in the directory ``searx/static/oscar`` | ||||
| run this command in the directory ``searx/static/themes/oscar`` | ||||
| 
 | ||||
| ``grunt`` | ||||
| 
 | ||||
| Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB | 
| Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB | 
| Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB | 
| Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB | 
| Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB | 
| Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB | 
| Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB | 
| Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								searx/static/themes/oscar/img/icons/openstreetmap.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								searx/static/themes/oscar/img/icons/photon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								searx/static/themes/oscar/img/icons/searchcode code.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								searx/static/themes/oscar/img/icons/searchcode doc.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.3 KiB | 
| Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB | 
| Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB | 
 Thomas Pointhuber
						Thomas Pointhuber