Merge pull request #1 from YunoHost-Apps/update-v9

Update v9
This commit is contained in:
opi 2016-07-07 10:36:37 +02:00 committed by GitHub
commit 7028ebaf83
59 changed files with 1231 additions and 284 deletions

View File

@ -11,8 +11,8 @@
},
"url": "https://asciimoo.github.io/searx/",
"maintainer": {
"name": "beudbeud",
"email": "beudbeud@beudibox.fr"
"name": "opi",
"email": "opi@zeropi.net"
},
"multi_instance": "false",
"services": [
@ -41,7 +41,7 @@
"default": "/searx"
},
{
"name": "public_site",
"name": "is_public",
"ask": {
"en": "Is it a public Searx site ?",
"fr": "Est-ce un site public ?"

View File

@ -1,4 +1,5 @@
#!/bin/bash
app="searx"
# causes the shell to exit if any subcommand or pipeline returns a non-zero status
set -e
@ -6,30 +7,15 @@ set -e
# Source YNH helpers
. /usr/share/yunohost/helpers
# This is a multi-instance app, meaning it can be installed several times independently
# The id of the app as stated in the manifest is available as $YNH_APP_ID
# The instance number is available as $YNH_APP_INSTANCE_NUMBER (equals "1", "2", ...)
# The app instance name is available as $YNH_APP_INSTANCE_NAME
# - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample
# - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2
# - ynhexample__{N} for the subsequent installations, with N=3,4, ...
# The app instance name is probably what you are interested the most, since this is
# guaranteed to be unique. This is a good unique identifier to define installation path,
# db names, ...
app=$YNH_APP_INSTANCE_NAME
# Retrieve arguments
domain=$(sudo yunohost app setting $app domain)
# Backup directory location for the app from where the script is executed and
# which will be compressed afterward
backup_dir=$YNH_APP_BACKUP_DIR
domain=$(ynh_app_setting_get "$app" domain)
path=$(ynh_app_setting_get "$app" path)
is_public=$(ynh_app_setting_get "$app" is_public)
# Backup sources & data
ynh_backup "/opt/yunohost/$app" "sources"
# Copy Nginx conf
sudo mkdir -p ./conf
ynh_backup "/etc/uwsgi/apps-available/$app.ini" "conf/searx.ini"
ynh_backup "/opt/yunohost/$app/searx/settings.yml" "conf/settings.yml"
ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "conf/nginx.conf"

View File

@ -1,27 +1,30 @@
#!/bin/bash
app="searx"
# causes the shell to exit if any subcommand or pipeline returns a non-zero status
set -e
# Source app helpers
. /usr/share/yunohost/helpers
# Retrieve arguments
domain=$1
path=$2
is_public=$3
domain=$YNH_APP_ARG_DOMAIN
path=$YNH_APP_ARG_PATH
is_public=$YNH_APP_ARG_IS_PUBLIC
# Force path to start with a /
if [ "${path:0:1}" != "/" ]; then
path="/$path"
fi
# Remove trailing slash to path
path=${path%/}
#force location to be / or /foo
location=${path:-/}
# Check domain/path availability
sudo yunohost app checkurl $domain$path -a searx
if [[ ! $? -eq 0 ]]; then
exit 1
fi
# Path need a trailing slash, and location does not.
# See conf/nginx.conf* usage
location=$path
if [[ ! $path == */ ]]; then
# no trailing slash, so add it
path=$path/
fi
if [[ ! "$location" == "/" ]]; then
# remove possible trailing slash
location=${location%/}
fi
sudo yunohost app checkurl $domain$path -a $app \
|| (echo "Path not available: $domain$path" && exit 1)
# Save specific settings
sudo yunohost app setting searx is_public -v $is_public
@ -32,26 +35,26 @@ sudo apt-get install git build-essential libxslt-dev python-dev python-virtualen
# Check Swap
if [ $(sudo swapon -s | wc -l) = 1 ];
then
# It is NOT possible to setup a swap file on a tmpfs filesystem
mount | grep /tmp | grep tmpfs > /dev/null 2>&1
if [ $? = 1 ];
then
tmp_swap_file=/tmp/searx_swapfile
else
tmp_swap_file=/var/cache/searx_swapfile
fi
sudo dd if=/dev/zero of=$tmp_swap_file bs=1M count=256
sudo chmod 600 $tmp_swap_file
sudo mkswap $tmp_swap_file
sudo swapon $tmp_swap_file
# It is NOT possible to setup a swap file on a tmpfs filesystem
mount | grep /tmp | grep tmpfs > /dev/null 2>&1
if [ $? = 1 ];
then
tmp_swap_file=/tmp/searx_swapfile
else
tmp_swap_file=/var/cache/searx_swapfile
fi
sudo dd if=/dev/zero of=$tmp_swap_file bs=1M count=256
sudo chmod 600 $tmp_swap_file
sudo mkswap $tmp_swap_file
sudo swapon $tmp_swap_file
fi
final_path=/opt/yunohost/searx
final_path=/opt/yunohost/$app
# Init virtualenv
if [ ! -d $final_path ];
then
sudo mkdir -p $final_path
sudo mkdir -p $final_path
fi
sudo cp -r ../sources/* $final_path
sudo virtualenv --system-site-packages $final_path
@ -60,8 +63,8 @@ sudo bash -c "source $final_path/bin/activate && pip install -r $final_path/requ
# Disable swapfile
if [[ -v "$tmp_swap_file" ]];
then
sudo swapoff $tmp_swap_file
sudo rm -f $tmp_swap_file
sudo swapoff $tmp_swap_file
sudo rm -f $tmp_swap_file
fi
#Configuration Searx
@ -69,9 +72,9 @@ sudo cp ../conf/settings.yml $final_path/searx/
sudo sed -i -e "s/ultrasecretkey/`openssl rand -hex 16`/g" $final_path/searx/settings.yml
if [ "$path" != "/" ];
then
sudo sed -i -e "s@ynhbaseurl@https://$domain$path/@g" $final_path/searx/settings.yml
sudo sed -i -e "s@ynhbaseurl@https://$domain$path/@g" $final_path/searx/settings.yml
else
sudo sed -i -e "s@ynhbaseurl@False@g" $final_path/searx/settings.yml
sudo sed -i -e "s@ynhbaseurl@False@g" $final_path/searx/settings.yml
fi
# Set permissions to searx directory
@ -79,17 +82,17 @@ sudo useradd searx -d $final_path
sudo chown searx:searx -R $final_path
# Copy uwsgi config
sudo cp ../conf/searx.ini /etc/uwsgi/apps-available/
sudo ln -s /etc/uwsgi/apps-available/searx.ini /etc/uwsgi/apps-enabled/
sudo cp ../conf/searx.ini /etc/uwsgi/apps-available/$app.ini
sudo ln -s /etc/uwsgi/apps-available/searx.ini /etc/uwsgi/apps-enabled/$app.ini
# Modify Nginx configuration file and copy it to Nginx conf directory
sed -i "s@YNH_WWW_LOCATION@$location@g" ../conf/nginx.conf*
sed -i "s@YNH_WWW_PATH@$path@g" ../conf/nginx.conf*
if [ "$path" != "/" ];
then
sudo cp ../conf/nginx.conf-noroot /etc/nginx/conf.d/$domain.d/searx.conf
sudo cp ../conf/nginx.conf-noroot /etc/nginx/conf.d/$domain.d/$app.conf
else
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/searx.conf
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/$app.conf
fi
# Fix permission
@ -99,11 +102,10 @@ fi
## Reload Nginx and regenerate SSOwat conf
sudo service nginx reload
sudo service uwsgi restart
sudo yunohost service add uwsgi -l /var/log/uwsgi/app/searx.log
sudo yunohost service add uwsgi -l /var/log/uwsgi/app/$app.log
if [ "$is_public" = "Yes" ];
then
sudo yunohost app setting searx unprotected_uris -v "/"
sudo yunohost app setting $app unprotected_uris -v "/"
fi
sudo yunohost app ssowatconf

View File

@ -1,14 +1,30 @@
#!/bin/bash
app="searx"
domain=$(sudo yunohost app setting searx domain)
# Source app helpers
. /usr/share/yunohost/helpers
sudo rm -Rf /opt/yunohost/searx
sudo rm -f /etc/uwsgi/apps-enabled/searx.ini
sudo rm -f /etc/uwsgi/apps-available/searx.ini
sudo rm -f /etc/nginx/conf.d/$domain.d/searx.conf
# Retrieve arguments
domain=$(ynh_app_setting_get "$app" domain)
# Remove files
sudo rm -Rf /opt/yunohost/$app
sudo rm -f /etc/uwsgi/apps-enabled/$app.ini
sudo rm -f /etc/uwsgi/apps-available/$app.ini
sudo rm -f /etc/nginx/conf.d/$domain.d/$app.conf
# Stop uwsgi
sudo service uwsgi stop
sudo killall uwsgi
# Remove user (kill attached process before)
if id -u searx > /dev/null 2>&1; then
sudo killall -KILL -u searx
sudo userdel --force searx
fi
# Restart uwsgi
sudo service uwsgi start
# Reload Nginx
sudo service nginx reload
sudo yunohost app ssowatconf
sudo userdel searx

View File

@ -1,4 +1,5 @@
#!/bin/bash
app="searx"
# causes the shell to exit if any subcommand or pipeline returns a non-zero status
set -e
@ -6,22 +7,15 @@ set -e
# Source YNH helpers
. /usr/share/yunohost/helpers
# This is a multi-instance app, meaning it can be installed several times independently
# The id of the app as stated in the manifest is available as $YNH_APP_ID
# The instance number is available as $YNH_APP_INSTANCE_NUMBER (equals "1", "2", ...)
# The app instance name is available as $YNH_APP_INSTANCE_NAME
# - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample
# - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2
# - ynhexample__{N} for the subsequent installations, with N=3,4, ...
# The app instance name is probably what you are interested the most, since this is
# guaranteed to be unique. This is a good unique identifier to define installation path,
# db names, ...
app=$YNH_APP_INSTANCE_NAME
# Retrieve arguments
domain=$(ynh_app_setting_get "$app" domain)
path=$(ynh_app_setting_get "$app" path)
is_public=$(ynh_app_setting_get "$app" is_public)
# Get old parameter of the app
domain=$(sudo yunohost app setting $app domain)
path=$(sudo yunohost app setting $app path)
is_public=$(sudo yunohost app setting $app is_public)
# Remove trailing slash to path
path=${path%/}
#force location to be / or /foo
location=${path:-/}
# Check domain/path availability
sudo yunohost app checkurl $domain$path -a $app
@ -30,7 +24,7 @@ if [[ ! $? -eq 0 ]]; then
exit 1
fi
final_path=/opt/yunohost/searx
final_path=/opt/yunohost/$app
if [ -d $final_path ]; then
echo "There is already a directory: $final_path " | sudo tee /dev/stderr
exit 1
@ -45,21 +39,8 @@ fi
# Run install script
# sudo bash -c "./install $domain $path $is_public $app"
# Path need a trailing slash, and location does not.
# See conf/nginx.conf* usage
location=$path
if [[ ! $path == */ ]]; then
# no trailing slash, so add it
path=$path/
fi
if [[ ! "$location" == "/" ]]; then
# remove possible trailing slash
location=${location%/}
fi
# Save specific settings
sudo yunohost app setting searx is_public -v $is_public
sudo yunohost app setting $app is_public -v $is_public
# Check depends installation
sudo apt-get install git build-essential libxslt-dev python-dev python-virtualenv python-pybabel zlib1g-dev libffi-dev libssl-dev python-lxml uwsgi uwsgi-plugin-python -y
@ -82,7 +63,7 @@ then
fi
# Restore sources
final_path=/opt/yunohost/searx
final_path=/opt/yunohost/$app
sudo cp -a "./sources" $final_path
# Init virtualenv
@ -110,14 +91,13 @@ sudo ln -s "/etc/uwsgi/apps-available/$app.ini" /etc/uwsgi/apps-enabled/
# Nginx conf
sudo cp "conf/nginx.conf" $nginx_conf
## Reload Nginx and regenerate SSOwat conf
sudo service nginx reload
sudo service uwsgi restart
sudo yunohost service add uwsgi -l /var/log/uwsgi/app/searx.log
if [ "$is_public" = "Yes" ];
then
sudo yunohost app setting searx unprotected_uris -v "/"
fi
sudo yunohost app ssowatconf
## Reload services
sudo service nginx reload
sudo service uwsgi restart
sudo yunohost service add uwsgi -l /var/log/uwsgi/app/$app.log

View File

@ -1,21 +1,21 @@
#!/bin/bash
app="searx"
# causes the shell to exit if any subcommand or pipeline returns a non-zero status
set -e
# Source app helpers
. /usr/share/yunohost/helpers
# Retrieve arguments
domain=$(sudo yunohost app setting searx domain)
path=$(sudo yunohost app setting searx path)
is_public=$(sudo yunohost app setting searx is_public)
domain=$(ynh_app_setting_get "$app" domain)
path=$(ynh_app_setting_get "$app" path)
is_public=$(ynh_app_setting_get "$app" is_public)
# Path need a trailing slash, and location does not.
# See conf/nginx.conf usage
location=$path
if [[ ! $path == */ ]]; then
# no trailing slash, so add it
path=$path/
fi
if [[ ! "$location" == "/" ]]; then
# remove possible trailing slash
location=${location%/}
fi
# Remove trailing slash to path
path=${path%/}
#force location to be / or /foo
location=${path:-/}
# Check depends installation
sudo apt-get install git build-essential libxslt-dev python-dev python-virtualenv python-pybabel zlib1g-dev libffi-dev libssl-dev python-lxml uwsgi uwsgi-plugin-python -y
@ -23,20 +23,20 @@ sudo apt-get install git build-essential libxslt-dev python-dev python-virtualen
# Check Swap
if [ $(sudo swapon -s | wc -l) = 1 ];
then
mount | grep /tmp | grep tmpfs > /dev/null 2>&1
if [ $? = 1 ];
then
tmp_swap_file=/tmp/searx_swapfile
else
tmp_swap_file=/var/cache/
fi
sudo dd if=/dev/zero of=$tmp_swap_file bs=1M count=256
sudo chmod 600 $tmp_swap_file
sudo mkswap $tmp_swap_file
sudo swapon $tmp_swap_file
mount | grep /tmp | grep tmpfs > /dev/null 2>&1
if [ $? = 1 ];
then
tmp_swap_file=/tmp/searx_swapfile
else
tmp_swap_file=/var/cache/
fi
sudo dd if=/dev/zero of=$tmp_swap_file bs=1M count=256
sudo chmod 600 $tmp_swap_file
sudo mkswap $tmp_swap_file
sudo swapon $tmp_swap_file
fi
final_path=/opt/yunohost/searx
final_path=/opt/yunohost/$app
sudo cp -r ../sources/* $final_path
sudo bash -c "source $final_path/bin/activate && pip install -r $final_path/requirements-ynh.txt --upgrade"
@ -47,12 +47,6 @@ then
sudo rm -f $tmp_swap_file
fi
# Remove trailing "/" for next commands if installing on a subpath
if [ "$path" != "/" ];
then
path=${path%/}
fi
#Configuration Searx
sudo cp ../conf/settings.yml $final_path/searx/
sudo sed -i -e "s/ultrasecretkey/`openssl rand -hex 16`/g" $final_path/searx/settings.yml
@ -70,30 +64,29 @@ fi
sudo chown searx:searx -R $final_path
# Copy uwsgi config
sudo cp ../conf/searx.ini /etc/uwsgi/apps-available/
sudo cp ../conf/searx.ini /etc/uwsgi/apps-available/$app.ini
# Modify Nginx configuration file and copy it to Nginx conf directory
sed -i "s@YNH_WWW_LOCATION@$location@g" ../conf/nginx.conf*
sed -i "s@YNH_WWW_PATH@$path@g" ../conf/nginx.conf*
if [ "$path" != "/" ];
then
sudo cp ../conf/nginx.conf-noroot /etc/nginx/conf.d/$domain.d/searx.conf
sudo cp ../conf/nginx.conf-noroot /etc/nginx/conf.d/$domain.d/$app.conf
else
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/searx.conf
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/$app.conf
fi
# Fix permission
#sudo find $final_path/ -type d -exec chmod 2755 {} \;
#sudo find $final_path/ -type f -exec chmod g+r,o+r {} \;
## Reload Nginx and regenerate SSOwat conf
sudo service nginx reload
sudo service uwsgi restart
if [ "$is_public" = "Yes" ];
then
sudo yunohost app setting searx skipped_uris -d
sudo yunohost app setting searx unprotected_uris -v "/"
fi
sudo yunohost app ssowatconf
## Reload services
sudo service nginx reload
sudo service uwsgi restart

20
sources/.coveragerc Normal file
View File

@ -0,0 +1,20 @@
[run]
branch = True
source =
searx/engines
searx/__init__.py
searx/autocomplete.py
searx/https_rewrite.py
searx/languages.py
searx/search.py
searx/testing.py
searx/utils.py
searx/webapp.py
[report]
show_missing = True
exclude_lines =
if __name__ == .__main__.:
[html]
directory = coverage

17
sources/.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
.coverage
.installed.cfg
engines.cfg
env
robot_log.html
robot_output.xml
robot_report.html
test_basic/
setup.cfg
*.pyc
*/*.pyc
*~
node_modules/
.tx/

3
sources/.landscape.yaml Normal file
View File

@ -0,0 +1,3 @@
strictness: high
ignore-paths:
- bootstrap.py

32
sources/.travis.yml Normal file
View File

@ -0,0 +1,32 @@
sudo: false
cache:
- pip
- npm
- directories:
- $HOME/.cache/pip
language: python
python:
- "2.7"
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- npm install less grunt-cli
- ( cd searx/static/themes/oscar;npm install; cd - )
install:
- ./manage.sh update_dev_packages
- pip install coveralls
script:
- ./manage.sh pep8_check
- ./manage.sh styles
- ./manage.sh grunt_build
- ./manage.sh py_test_coverage
- ./manage.sh robot_tests
after_success:
coveralls
notifications:
irc:
channels:
- "irc.freenode.org#searx"
template:
- "%{repository}/#%{build_number}/%{branch} (%{author}): %{message} %{build_url}"
on_success: change

View File

@ -44,3 +44,10 @@ generally made searx better:
- Kirill Isakov
- Guilhem Bonnefille
- Marc Abonce Seguin
- @jibe-b
- Christian Pietsch @pietsch
- @Maxqia
- Ashutosh Das @pyprism
- YuLun Shih @imZack
- Dmitry Mikhirev @mikhirev

View File

@ -1,3 +1,41 @@
0.9.0 2016.05.24
================
- New search category: science
- New engines
- Wolframalpha (science)
- Frinkiac (images)
- Arch Linux (it)
- BASE - Bielefeld Academic Search Engine (science)
- Dokuwiki (general)
- Nyaa.se (files, images, music, video)
- Reddit (general, images, news, social media)
- Torrentz.eu (files, music, video)
- Tokyo Toshokan (files, music, video)
- F-Droid (files)
- Erowid (general)
- Bitbucket (it)
- GitLab (it)
- Geektimes (it)
- Habrahabr (it)
- New plugins
- Open links in new tab
- Vim hotkeys for better navigation
- Wikipedia/Mediawiki engine improvements
- Configurable instance name
- Configurable connection pool size
- Fixed broken google engine
- Better docker image
- Images in standard results
- Fixed and refactored user settings (Warning: backward incompatibility - you have to reset your custom engine preferences)
- Suspending engines on errors
- Simplified development/deployment tooling
- Translation updates
- Multilingual autocompleter
- Qwant autocompleter backend
0.8.1 2015.12.22
================

View File

@ -18,7 +18,7 @@ Installation
``git clone git@github.com:asciimoo/searx.git && cd searx``
- install dependencies: ``./manage.sh update_packages``
- edit your
`settings.yml <https://github.com/asciimoo/searx/blob/master/settings.yml>`__
`settings.yml <https://github.com/asciimoo/searx/blob/master/searx/settings.yml>`__
(set your ``secret_key``!)
- run ``python searx/webapp.py`` to start the application

View File

@ -11,3 +11,4 @@ pyopenssl==0.15.1
python-dateutil==2.4.2
pyyaml==3.11
requests==2.9.1
cffi>=1.6

View File

@ -54,6 +54,12 @@ def response(resp):
dom = html.fromstring(resp.text)
try:
results.append({'number_of_results': int(dom.xpath('//span[@class="sb_count"]/text()')[0]
.split()[0].replace(',', ''))})
except:
pass
# parse results
for result in dom.xpath('//div[@class="sa_cc"]'):
link = result.xpath('.//h3/a')[0]
@ -66,10 +72,6 @@ def response(resp):
'title': title,
'content': content})
# return results if something is found
if results:
return results
# parse results again if nothing is found yet
for result in dom.xpath('//li[@class="b_algo"]'):
link = result.xpath('.//h2/a')[0]

View File

@ -9,13 +9,13 @@ categories = []
url = 'https://download.finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s={query}=X'
weight = 100
parser_re = re.compile(u'^\W*(\d+(?:\.\d+)?)\W*([^.0-9].+)\W+in?\W+([^\.]+)\W*$', re.I) # noqa
parser_re = re.compile(u'.*?(\d+(?:\.\d+)?) ([^.0-9]+) (?:in|to) ([^.0-9]+)', re.I) # noqa
db = 1
def normalize_name(name):
name = name.lower().replace('-', ' ')
name = name.lower().replace('-', ' ').rstrip('s')
name = re.sub(' +', ' ', name)
return unicodedata.normalize('NFKD', name).lower()

View File

@ -31,9 +31,9 @@ search_string = 'search?{query}'\
'&s={offset}'\
'&format=json'\
'&qh=0'\
'&rxiwd={rxiwd}'\
'&qlang={lang}'\
'&ff={safesearch}'
'&ff={safesearch}'\
'&rxikd={rxikd}' # random number - 9 digits
# specific xpath variables
results_xpath = '//response//result'
@ -59,8 +59,7 @@ def request(query, params):
search_path = search_string.format(query=urlencode({'q': query}),
offset=offset,
number_of_results=number_of_results,
rxiwd=1,
# rand=int(time()),
rxikd=str(time())[:9],
lang=language,
safesearch=safesearch)

View File

@ -77,6 +77,13 @@ def response(resp):
dom = html.fromstring(resp.text)
try:
results_num = int(dom.xpath('//div[@class="compPagination"]/span[last()]/text()')[0]
.split()[0].replace(',', ''))
results.append({'number_of_results': results_num})
except:
pass
# parse results
for result in dom.xpath(results_xpath):
try:

View File

@ -23,7 +23,8 @@ from searx.plugins import (https_rewrite,
open_results_on_new_tab,
self_info,
search_on_category_select,
tracker_url_remover)
tracker_url_remover,
vim_hotkeys)
required_attrs = (('name', str),
('description', str),
@ -77,3 +78,4 @@ plugins.register(open_results_on_new_tab)
plugins.register(self_info)
plugins.register(search_on_category_select)
plugins.register(tracker_url_remover)
plugins.register(vim_hotkeys)

View File

@ -0,0 +1,10 @@
from flask.ext.babel import gettext
name = gettext('Vim-like hotkeys')
description = gettext('Navigate search results with Vim-like hotkeys '
'(JavaScript required). '
'Press "h" key on main or result page to get help.')
default_on = False
js_dependencies = ('plugins/js/vim_hotkeys.js',)
css_dependencies = ('plugins/css/vim_hotkeys.css',)

View File

@ -41,14 +41,18 @@ class HTTPAdapterWithConnParams(requests.adapters.HTTPAdapter):
block=self._pool_block, **self._conn_params)
connect = settings['outgoing'].get('pool_connections', 100) # Magic number kept from previous code
maxsize = settings['outgoing'].get('pool_maxsize', requests.adapters.DEFAULT_POOLSIZE) # Picked from constructor
if settings['outgoing'].get('source_ips'):
http_adapters = cycle(HTTPAdapterWithConnParams(pool_connections=100, source_address=(source_ip, 0))
http_adapters = cycle(HTTPAdapterWithConnParams(pool_connections=connect, pool_maxsize=maxsize,
source_address=(source_ip, 0))
for source_ip in settings['outgoing']['source_ips'])
https_adapters = cycle(HTTPAdapterWithConnParams(pool_connections=100, source_address=(source_ip, 0))
https_adapters = cycle(HTTPAdapterWithConnParams(pool_connections=connect, pool_maxsize=maxsize,
source_address=(source_ip, 0))
for source_ip in settings['outgoing']['source_ips'])
else:
http_adapters = cycle((HTTPAdapterWithConnParams(pool_connections=100), ))
https_adapters = cycle((HTTPAdapterWithConnParams(pool_connections=100), ))
http_adapters = cycle((HTTPAdapterWithConnParams(pool_connections=connect, pool_maxsize=maxsize), ))
https_adapters = cycle((HTTPAdapterWithConnParams(pool_connections=connect, pool_maxsize=maxsize), ))
class SessionSinglePool(requests.Session):

View File

@ -181,7 +181,7 @@ class EnginesSetting(SwitchableSetting):
return [item[len('engine_'):].replace('_', ' ').replace(' ', '__') for item in items]
def transform_values(self, values):
if len(values) == 1 and values[0] == '':
if len(values) == 1 and next(iter(values)) == '':
return list()
transformed_values = []
for value in values:
@ -229,6 +229,7 @@ class Preferences(object):
self.engines = EnginesSetting('engines', choices=engines)
self.plugins = PluginsSetting('plugins', choices=plugins)
self.unknown_params = {}
def parse_cookies(self, input_data):
for user_setting_name, user_setting in input_data.iteritems():
@ -254,6 +255,8 @@ class Preferences(object):
enabled_categories.append(user_setting_name[len('category_'):])
elif user_setting_name.startswith('plugin_'):
disabled_plugins.append(user_setting_name)
else:
self.unknown_params[user_setting_name] = user_setting
self.key_value_settings['categories'].parse_form(enabled_categories)
self.engines.parse_form(disabled_engines)
self.plugins.parse_form(disabled_plugins)
@ -268,4 +271,6 @@ class Preferences(object):
user_setting.save(user_setting_name, resp)
self.engines.save(resp)
self.plugins.save(resp)
for k, v in self.unknown_params.items():
resp.set_cookie(k, v, max_age=COOKIE_MAX_AGE)
return resp

View File

@ -99,6 +99,7 @@ class ResultContainer(object):
self._infobox_ids = {}
self.suggestions = set()
self.answers = set()
self.number_of_results = 0
def extend(self, engine_name, results):
for result in list(results):
@ -111,6 +112,9 @@ class ResultContainer(object):
elif 'infobox' in result:
self._merge_infobox(result)
results.remove(result)
elif 'number_of_results' in result:
self.number_of_results = max(self.number_of_results, result['number_of_results'])
results.remove(result)
with RLock():
engines[engine_name].stats['search_count'] += 1

View File

@ -21,6 +21,8 @@ ui:
outgoing: # communication with search engines
request_timeout : 2.0 # seconds
useragent_suffix : "" # suffix of searx_useragent, could contain informations like an email address to the administrator
pool_connections : 100 # Number of different hosts
pool_maxsize : 10 # Number of simultaneous requests by host
# uncomment below section if you want to use a proxy
# see http://docs.python-requests.org/en/latest/user/advanced/#proxies
# SOCKS proxies are not supported : see https://github.com/kennethreitz/requests/pull/478
@ -38,6 +40,17 @@ engines:
engine : archlinux
shortcut : al
- name : archive is
engine : xpath
search_url : https://archive.is/{query}
url_xpath : (//div[@class="TEXT-BLOCK"]/a)/@href
title_xpath : (//div[@class="TEXT-BLOCK"]/a)
content_xpath : //div[@class="TEXT-BLOCK"]/ul/li
categories : general
timeout : 7.0
disabled : True
shortcut : ai
- name : base
engine : base
shortcut : bs
@ -221,8 +234,8 @@ engines:
engine : xpath
paging : True
search_url : https://geektimes.ru/search/page{pageno}/?q={query}
url_xpath : //div[@class="search_results"]//a[@class="post_title"]/@href
title_xpath : //div[@class="search_results"]//a[@class="post_title"]
url_xpath : //div[@class="search_results"]//a[@class="post__title_link"]/@href
title_xpath : //div[@class="search_results"]//a[@class="post__title_link"]
content_xpath : //div[@class="search_results"]//div[contains(@class, "content")]
categories : it
timeout : 4.0

View File

@ -0,0 +1,26 @@
.vim-hotkeys-help {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999999;
overflow-y: auto;
max-height: 80%;
box-shadow: 0 0 1em;
}
.dflex {
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Chrome */
display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
}
.iflex {
-webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
-moz-box-flex: 1; /* OLD - Firefox 19- */
-webkit-flex: 1; /* Chrome */
-ms-flex: 1; /* IE 10 */
flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */
}

View File

@ -0,0 +1,336 @@
$(document).ready(function() {
highlightResult('top')();
$('.result').on('click', function() {
highlightResult($(this))();
});
var vimKeys = {
27: {
key: 'Escape',
fun: removeFocus,
des: 'remove focus from the focused input',
cat: 'Control'
},
73: {
key: 'i',
fun: searchInputFocus,
des: 'focus on the search input',
cat: 'Control'
},
66: {
key: 'b',
fun: scrollPage(-window.innerHeight),
des: 'scroll one page up',
cat: 'Navigation'
},
70: {
key: 'f',
fun: scrollPage(window.innerHeight),
des: 'scroll one page down',
cat: 'Navigation'
},
85: {
key: 'u',
fun: scrollPage(-window.innerHeight / 2),
des: 'scroll half a page up',
cat: 'Navigation'
},
68: {
key: 'd',
fun: scrollPage(window.innerHeight / 2),
des: 'scroll half a page down',
cat: 'Navigation'
},
71: {
key: 'g',
fun: scrollPageTo(-document.body.scrollHeight, 'top'),
des: 'scroll to the top of the page',
cat: 'Navigation'
},
86: {
key: 'v',
fun: scrollPageTo(document.body.scrollHeight, 'bottom'),
des: 'scroll to the bottom of the page',
cat: 'Navigation'
},
75: {
key: 'k',
fun: highlightResult('up'),
des: 'select previous search result',
cat: 'Results'
},
74: {
key: 'j',
fun: highlightResult('down'),
des: 'select next search result',
cat: 'Results'
},
80: {
key: 'p',
fun: pageButtonClick(0),
des: 'go to previous page',
cat: 'Results'
},
78: {
key: 'n',
fun: pageButtonClick(1),
des: 'go to next page',
cat: 'Results'
},
79: {
key: 'o',
fun: openResult(false),
des: 'open search result',
cat: 'Results'
},
84: {
key: 't',
fun: openResult(true),
des: 'open the result in a new tab',
cat: 'Results'
},
82: {
key: 'r',
fun: reloadPage,
des: 'reload page from the server',
cat: 'Control'
},
72: {
key: 'h',
fun: toggleHelp,
des: 'toggle help window',
cat: 'Other'
}
};
$(document).keyup(function(e) {
// check for modifiers so we don't break browser's hotkeys
if (vimKeys.hasOwnProperty(e.keyCode)
&& !e.ctrlKey
&& !e.altKey
&& !e.shiftKey
&& !e.metaKey)
{
if (e.keyCode === 27) {
if (e.target.tagName.toLowerCase() === 'input') {
vimKeys[e.keyCode].fun();
}
} else {
if (e.target === document.body) {
vimKeys[e.keyCode].fun();
}
}
}
});
function highlightResult(which) {
return function() {
var current = $('.result[data-vim-selected]');
if (current.length === 0) {
current = $('.result:first');
if (current.length === 0) {
return;
}
}
var next;
if (typeof which !== 'string') {
next = which;
} else {
switch (which) {
case 'visible':
var top = $(window).scrollTop();
var bot = top + $(window).height();
var results = $('.result');
for (var i = 0; i < results.length; i++) {
next = $(results[i]);
var etop = next.offset().top;
var ebot = etop + next.height();
if ((ebot <= bot) && (etop > top)) {
break;
}
}
break;
case 'down':
next = current.next('.result');
if (next.length === 0) {
next = $('.result:first');
}
break;
case 'up':
next = current.prev('.result');
if (next.length === 0) {
next = $('.result:last');
}
break;
case 'bottom':
next = $('.result:last');
break;
case 'top':
default:
next = $('.result:first');
}
}
if (next) {
current.removeAttr('data-vim-selected').removeClass('well well-sm');
next.attr('data-vim-selected', 'true').addClass('well well-sm');
scrollPageToSelected();
}
}
}
function reloadPage() {
document.location.reload(false);
}
function removeFocus() {
if (document.activeElement) {
document.activeElement.blur();
}
}
function pageButtonClick(num) {
return function() {
var buttons = $('div#pagination button[type="submit"]');
if (buttons.length !== 2) {
console.log('page navigation with this theme is not supported');
return;
}
if (num >= 0 && num < buttons.length) {
buttons[num].click();
} else {
console.log('pageButtonClick(): invalid argument');
}
}
}
function scrollPageToSelected() {
var sel = $('.result[data-vim-selected]');
if (sel.length !== 1) {
return;
}
var wnd = $(window);
var wtop = wnd.scrollTop();
var etop = sel.offset().top;
var offset = 30;
if (wtop > etop) {
wnd.scrollTop(etop - offset);
} else {
var ebot = etop + sel.height();
var wbot = wtop + wnd.height();
if (wbot < ebot) {
wnd.scrollTop(ebot - wnd.height() + offset);
}
}
}
function scrollPage(amount) {
return function() {
window.scrollBy(0, amount);
highlightResult('visible')();
}
}
function scrollPageTo(position, nav) {
return function() {
window.scrollTo(0, position);
highlightResult(nav)();
}
}
function searchInputFocus() {
$('input#q').focus();
}
function openResult(newTab) {
return function() {
var link = $('.result[data-vim-selected] .result_header a');
if (link.length) {
var url = link.attr('href');
if (newTab) {
window.open(url);
} else {
window.location.href = url;
}
}
};
}
function toggleHelp() {
var helpPanel = $('#vim-hotkeys-help');
if (helpPanel.length) {
helpPanel.toggleClass('hidden');
return;
}
var categories = {};
for (var k in vimKeys) {
var key = vimKeys[k];
categories[key.cat] = categories[key.cat] || [];
categories[key.cat].push(key);
}
var sorted = Object.keys(categories).sort(function(a, b) {
return categories[b].length - categories[a].length;
});
if (sorted.length === 0) {
return;
}
var html = '<div id="vim-hotkeys-help" class="well vim-hotkeys-help">';
html += '<div class="container-fluid">';
html += '<div class="row">';
html += '<div class="col-sm-12">';
html += '<h3>How to navigate searx with Vim-like hotkeys</h3>';
html += '</div>'; // col-sm-12
html += '</div>'; // row
for (var i = 0; i < sorted.length; i++) {
var cat = categories[sorted[i]];
var lastCategory = i === (sorted.length - 1);
var first = i % 2 === 0;
if (first) {
html += '<div class="row dflex">';
}
html += '<div class="col-sm-' + (first && lastCategory ? 12 : 6) + ' dflex">';
html += '<div class="panel panel-default iflex">';
html += '<div class="panel-heading">' + cat[0].cat + '</div>';
html += '<div class="panel-body">';
html += '<ul class="list-unstyled">';
for (var cj in cat) {
html += '<li><kbd>' + cat[cj].key + '</kbd> ' + cat[cj].des + '</li>';
}
html += '</ul>';
html += '</div>'; // panel-body
html += '</div>'; // panel
html += '</div>'; // col-sm-*
if (!first || lastCategory) {
html += '</div>'; // row
}
}
html += '</div>'; // container-fluid
html += '</div>'; // vim-hotkeys-help
$('body').append(html);
}
});

File diff suppressed because one or more lines are too long

View File

@ -1,88 +0,0 @@
html{position:relative;min-height:100%}
body{margin-bottom:80px}
.footer{position:absolute;bottom:0;width:100%;height:60px}
input[type=checkbox]:checked+.label_hide_if_checked,input[type=checkbox]:checked+.label_hide_if_not_checked+.label_hide_if_checked{display:none}
input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbox]:not(:checked)+.label_hide_if_checked+.label_hide_if_not_checked{display:none}
.result_header{margin-bottom:5px;margin-top:20px}.result_header .favicon{margin-bottom:-3px}
.result_header a{vertical-align:bottom}.result_header a .highlight{font-weight:bold}
.result-content{margin-top:5px;word-wrap:break-word}.result-content .highlight{font-weight:bold}
.result-default{clear:both}
.result-images{float:left !important}
.img-thumbnail{margin:5px;max-height:128px;min-height:128px}
.result-videos{clear:both}
.result-torrents{clear:both}
.result-map{clear:both}
.result-code{clear:both}
.suggestion_item{margin:2px 5px}
.result_download{margin-right:5px}
#pagination{margin-top:30px;padding-bottom:50px}
.label-default{color:#aaa;background:#fff}
.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}
.infobox .infobox_part:last-child{margin-bottom:0}
.search_categories{margin:10px 0;text-transform:capitalize}
.cursor-text{cursor:text !important}
.cursor-pointer{cursor:pointer !important}
.highlight .hll{background-color:#ffc}
.highlight{background:#f8f8f8}
.highlight .c{color:#408080;font-style:italic}
.highlight .err{border:1px solid #f00}
.highlight .k{color:#008000;font-weight:bold}
.highlight .o{color:#666}
.highlight .cm{color:#408080;font-style:italic}
.highlight .cp{color:#bc7a00}
.highlight .c1{color:#408080;font-style:italic}
.highlight .cs{color:#408080;font-style:italic}
.highlight .gd{color:#a00000}
.highlight .ge{font-style:italic}
.highlight .gr{color:#f00}
.highlight .gh{color:#000080;font-weight:bold}
.highlight .gi{color:#00a000}
.highlight .go{color:#888}
.highlight .gp{color:#000080;font-weight:bold}
.highlight .gs{font-weight:bold}
.highlight .gu{color:#800080;font-weight:bold}
.highlight .gt{color:#04d}
.highlight .kc{color:#008000;font-weight:bold}
.highlight .kd{color:#008000;font-weight:bold}
.highlight .kn{color:#008000;font-weight:bold}
.highlight .kp{color:#008000}
.highlight .kr{color:#008000;font-weight:bold}
.highlight .kt{color:#b00040}
.highlight .m{color:#666}
.highlight .s{color:#ba2121}
.highlight .na{color:#7d9029}
.highlight .nb{color:#008000}
.highlight .nc{color:#00f;font-weight:bold}
.highlight .no{color:#800}
.highlight .nd{color:#a2f}
.highlight .ni{color:#999;font-weight:bold}
.highlight .ne{color:#d2413a;font-weight:bold}
.highlight .nf{color:#00f}
.highlight .nl{color:#a0a000}
.highlight .nn{color:#00f;font-weight:bold}
.highlight .nt{color:#008000;font-weight:bold}
.highlight .nv{color:#19177c}
.highlight .ow{color:#a2f;font-weight:bold}
.highlight .w{color:#bbb}
.highlight .mf{color:#666}
.highlight .mh{color:#666}
.highlight .mi{color:#666}
.highlight .mo{color:#666}
.highlight .sb{color:#ba2121}
.highlight .sc{color:#ba2121}
.highlight .sd{color:#ba2121;font-style:italic}
.highlight .s2{color:#ba2121}
.highlight .se{color:#b62;font-weight:bold}
.highlight .sh{color:#ba2121}
.highlight .si{color:#b68;font-weight:bold}
.highlight .sx{color:#008000}
.highlight .sr{color:#b68}
.highlight .s1{color:#ba2121}
.highlight .ss{color:#19177c}
.highlight .bp{color:#008000}
.highlight .vc{color:#19177c}
.highlight .vg{color:#19177c}
.highlight .vi{color:#19177c}
.highlight .il{color:#666}
.highlight .lineno{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.highlight .lineno::selection{background:transparent;}
.highlight .lineno::-moz-selection{background:transparent;}

View File

@ -0,0 +1 @@
html{position:relative;min-height:100%}body{margin-bottom:80px}.footer{position:absolute;bottom:0;width:100%;height:60px}input[type=checkbox]:checked+.label_hide_if_checked,input[type=checkbox]:checked+.label_hide_if_not_checked+.label_hide_if_checked{display:none}input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbox]:not(:checked)+.label_hide_if_checked+.label_hide_if_not_checked{display:none}.result_header{margin-bottom:5px;margin-top:20px}.result_header .favicon{margin-bottom:-3px}.result_header a{vertical-align:bottom}.result_header a .highlight{font-weight:700}.result-content{margin-top:5px;word-wrap:break-word}.result-content .highlight{font-weight:700}.result-default{clear:both}.result-images{float:left!important}.img-thumbnail{margin:5px;max-height:128px;min-height:128px}.result-videos{clear:both}.result-torrents{clear:both}.result-map{clear:both}.result-code{clear:both}.suggestion_item{margin:2px 5px}.result_download{margin-right:5px}#pagination{margin-top:30px;padding-bottom:50px}.label-default{color:#AAA;background:#FFF}.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}.infobox .infobox_part:last-child{margin-bottom:0}.search_categories{margin:10px 0;text-transform:capitalize}.cursor-text{cursor:text!important}.cursor-pointer{cursor:pointer!important}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.highlight .gr{color:red}.highlight .gh{color:navy;font-weight:700}.highlight .gi{color:#00a000}.highlight .go{color:#888}.highlight .gp{color:navy;font-weight:700}.highlight .gs{font-weight:700}.highlight .gu{color:purple;font-weight:700}.highlight .gt{color:#04d}.highlight .kc{color:green;font-weight:700}.highlight .kd{color:green;font-weight:700}.highlight .kn{color:green;font-weight:700}.highlight .kp{color:green}.highlight .kr{color:green;font-weight:700}.highlight .kt{color:#b00040}.highlight .m{color:#666}.highlight .s{color:#ba2121}.highlight .na{color:#7d9029}.highlight .nb{color:green}.highlight .nc{color:#00F;font-weight:700}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999;font-weight:700}.highlight .ne{color:#D2413A;font-weight:700}.highlight .nf{color:#00f}.highlight .nl{color:#a0a000}.highlight .nn{color:#00F;font-weight:700}.highlight .nt{color:green;font-weight:700}.highlight .nv{color:#19177c}.highlight .ow{color:#A2F;font-weight:700}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#ba2121}.highlight .sc{color:#ba2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{color:#ba2121}.highlight .se{color:#B62;font-weight:700}.highlight .sh{color:#ba2121}.highlight .si{color:#B68;font-weight:700}.highlight .sx{color:green}.highlight .sr{color:#b68}.highlight .s1{color:#ba2121}.highlight .ss{color:#19177c}.highlight .bp{color:green}.highlight .vc{color:#19177c}.highlight .vg{color:#19177c}.highlight .vi{color:#19177c}.highlight .il{color:#666}.highlight .lineno{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}

View File

@ -36,18 +36,20 @@ module.exports = function(grunt) {
less: {
development: {
options: {
paths: ["less/oscar"]
paths: ["less/pointhi", "less/logicodev"]
//banner: '/*! less/oscar/oscar.css | <%= grunt.template.today("dd-mm-yyyy") %> | https://github.com/asciimoo/searx */\n'
},
files: {"css/oscar.css": "less/oscar/oscar.less"}
files: {"css/pointhi.css": "less/pointhi/oscar.less",
"css/logicodev.css": "less/logicodev/oscar.less"}
},
production: {
options: {
paths: ["less/oscar"],
paths: ["less/pointhi", "less/logicodev"],
//banner: '/*! less/oscar/oscar.css | <%= grunt.template.today("dd-mm-yyyy") %> | https://github.com/asciimoo/searx */\n',
cleancss: true
},
files: {"css/oscar.min.css": "less/oscar/oscar.less"}
files: {"css/pointhi.min.css": "less/pointhi/oscar.less",
"css/logicodev.min.css": "less/logicodev/oscar.less"}
},
bootstrap: {
options: {
@ -63,7 +65,7 @@ module.exports = function(grunt) {
tasks: ['jshint', 'concat', 'uglify']
},
oscar_styles: {
files: ['less/oscar/**/*.less'],
files: ['less/pointhi/**/*.less'],
tasks: ['less:development', 'less:production']
},
bootstrap_styles: {

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,103 @@
pre, code{
font-family: 'Ubuntu Mono', 'Courier New', 'Lucida Console', monospace !important;
}
.lineno{
margin-right: 5px;
}
.highlight .hll { background-color: #ffffcc }
.highlight { background: #f8f8f8; }
.highlight .c { color: #556366; font-style: italic } /* Comment */
.highlight .err { border: 1px solid @orange } /* Error */
.highlight .k { color: #BE74D5; font-weight: bold } /* Keyword */
.highlight .o { color: #D19A66 } /* Operator */
.highlight .cm { color: #556366; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
.highlight .c1 { color: #556366; font-style: italic } /* Comment.Single */
.highlight .cs { color: #556366; font-style: italic } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #BE74D5; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #BE74D5; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #BE74D5; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #BE74D5 } /* Keyword.Pseudo */
.highlight .kr { color: #BE74D5; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #D46C72 } /* Keyword.Type */
.highlight .m { color: #D19A66 } /* Literal.Number */
.highlight .s { color: #86C372 } /* Literal.String */
.highlight .na { color: #7D9029 } /* Name.Attribute */
.highlight .nb { color: #BE74D5 } /* Name.Builtin */
.highlight .nc { color: #61AFEF; font-weight: bold } /* Name.Class */
.highlight .no { color: #D19A66 } /* Name.Constant */
.highlight .nd { color: #AA22FF } /* Name.Decorator */
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #61AFEF } /* Name.Function */
.highlight .nl { color: #A0A000 } /* Name.Label */
.highlight .nn { color: #61AFEF; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #BE74D5; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #DFC06F } /* Name.Variable */
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.highlight .w { color: #D7DAE0 } /* Text.Whitespace */
.highlight .mf { color: #D19A66 } /* Literal.Number.Float */
.highlight .mh { color: #D19A66 } /* Literal.Number.Hex */
.highlight .mi { color: #D19A66 } /* Literal.Number.Integer */
.highlight .mo { color: #D19A66 } /* Literal.Number.Oct */
.highlight .sb { color: #86C372 } /* Literal.String.Backtick */
.highlight .sc { color: #86C372 } /* Literal.String.Char */
.highlight .sd { color: #86C372; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #86C372 } /* Literal.String.Double */
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #86C372 } /* Literal.String.Heredoc */
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.highlight .sx { color: #BE74D5 } /* Literal.String.Other */
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
.highlight .s1 { color: #86C372 } /* Literal.String.Single */
.highlight .ss { color: #DFC06F } /* Literal.String.Symbol */
.highlight .bp { color: #BE74D5 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #DFC06F } /* Name.Variable.Class */
.highlight .vg { color: #DFC06F } /* Name.Variable.Global */
.highlight .vi { color: #DFC06F } /* Name.Variable.Instance */
.highlight .il { color: #D19A66 } /* Literal.Number.Integer.Long */
.highlight .lineno {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
color: #556366;
&::selection {
background: transparent; /* WebKit/Blink Browsers */
}
&::-moz-selection {
background: transparent; /* Gecko Browsers */
}
}
.highlight pre {
background-color: #282C34;
color: #D7DAE0;
border: none;
margin-bottom: 25px;
font-size: 15px;
padding: 20px 10px;
}
.highlight {
font-weight: 700;
}

View File

@ -0,0 +1,30 @@
// Sticky footer styles
*{
border-radius: 0 !important;
}
html {
position: relative;
min-height: 100%;
color: @black;
}
body {
/* Margin bottom by footer height */
font-family: 'Roboto', Helvetica, Arial, sans-serif;
margin-bottom: 80px;
background-color: white;
a{
color: @blue;
}
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
text-align: center;
color: #999;
}

View File

@ -0,0 +1,37 @@
.infobox {
.panel-heading{
background-color: @dim-gray;
.panel-title{
font-weight: 700;
}
}
p{
font-family: "DejaVu Serif", Georgia, Cambria, "Times New Roman", Times, serif !important;
font-style: italic;
}
.btn{
background-color: @green;
border: none;
a{
color: white;
margin: 5px;
}
}
.infobox_part {
margin-bottom: 20px;
word-wrap: break-word;
table-layout: fixed;
}
.infobox_part:last-child {
margin-bottom: 0;
}
}

View File

@ -0,0 +1,54 @@
.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus{
background: @black;
color: @light-green;
}
.navbar > li > a {
padding: 0;
margin: 0;
}
.navbar-nav > li > a {
background: @black;
padding: 0 8px;
margin: 0;
line-height: 30px;
}
.navbar, .navbar-default{
background-color: @black;
border: none;
border-top: 4px solid @light-green;
padding-top: 5px;
color: @dim-gray !important;
font-weight: 700;
font-size: 1.1em;
text-transform: lowercase;
margin-bottom: 24px;
height: 30px;
line-height: 30px;
.navbar-nav > li > a{
color: @dim-gray;
}
.navbar-brand{
font-weight: 700;
color: @light-green;
line-height: 30px;
padding: 0 30px;
margin: 0;
}
z-index: 1;
}
// Hover color
// http://stackoverflow.com/users/114029/leniel-macaferi
.navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus {
color: @light-green;
background: @black;
}
.navbar-toggle {
margin-top: 0;
}

View File

@ -0,0 +1,17 @@
@import "variables.less";
@import "navbar.less";
@import "footer.less";
@import "checkbox.less";
@import "results.less";
@import "infobox.less";
@import "search.less";
@import "cursor.less";
@import "code.less";

View File

@ -0,0 +1,150 @@
.result_header {
margin-top: 6px;
margin-bottom: 4px;
font-size: 16px;
.favicon {
margin-bottom:-3px;
}
a {
color: @black;
text-decoration: none;
&:hover{
color: @blue;
}
&:visited{
color: @violet;
}
.highlight {
background-color: @dim-gray;
// Chrome hack: bold is different size than normal
// https://stackoverflow.com/questions/20713988/weird-text-alignment-issue-in-css-when-bolded-lucida-sans
}
}
}
.result-content {
margin-top: 2px;
margin-bottom: 0;
word-wrap: break-word;
color: @dark-gray;
font-size: 13px;
.highlight {
font-weight:bold;
}
}
.external-link, .external-link a{
color: @green;
a{
margin-right: 3px;
}
}
// default formating of results
.result-default, .result-code, .result-torrent, .result-videos, .result-map {
clear: both;
padding: 2px 4px;
&:hover{
background-color: @dim-gray;
}
}
// image formating of results
.result-images {
float: left !important;
width: 24%;
margin: .5%;
a{
display: block;
width: 100%;
height: 170px;
background-size: cover;
}
}
.img-thumbnail {
margin: 5px;
max-height: 128px;
min-height: 128px;
}
// video formating of results
.result-videos {
clear: both;
hr{
margin: 5px 0 15px 0;
}
.collapse{
width: 100%;
}
.in{
margin-bottom: 8px;
}
}
// torrent formating of results
.result-torrent {
clear: both;
b{
margin-right: 5px;
margin-left: 5px;
}
.seeders{
color: @green;
}
.leechers{
color: @red;
}
}
// map formating of results
.result-map {
clear: both;
}
// code formating of results
.result-code {
clear: both;
.code-fork, .code-fork a{
color: @dark-gray;
}
}
// suggestion
.suggestion_item {
margin: 2px 5px;
}
// download result
.result_download {
margin-right: 5px;
}
// page forward, backward
#pagination {
margin-top: 30px;
padding-bottom: 60px;
}
.label-default {
color: @gray;
background: transparent;
}

View File

@ -0,0 +1,59 @@
.search_categories, #categories {
margin: 10px 0 4px 0;
text-transform: capitalize;
label{
border: none;
box-shadow: none;
font-size: 13px;
padding-bottom: 2px;
color: @gray;
margin-bottom: 5px;
&:hover{
color: @black;
background-color: transparent;
}
&:active{
box-shadow: none;
}
}
.active, .btn-primary{
color: @black;
font-weight: 700;
border-bottom: 5px solid @light-green;
background-color: transparent;
}
}
#categories{
margin: 0;
}
#main-logo{
margin-top: 10vh;
margin-bottom: 25px;
}
#main-logo > img {
max-width: 350px;
width: 80%;
}
#q{
box-shadow: none;
border-right: none;
border-color: @gray;
}
#search_form .input-group-btn .btn{
border-color: @gray;
}
#search_form .input-group-btn .btn:hover{
background-color: @green;
color: white;
}

View File

@ -0,0 +1,10 @@
@black: #29314D;
@gray: #A4A4A4;
@dim-gray: #F6F9FA;
@dark-gray: #666;
@blue: #0088CC;
@red: #F35E77;
@violet: #684898;
@green: #2ecc71;
@light-green: #01D7D4;
@orange: #FFA92F;

View File

@ -0,0 +1,9 @@
// Hide element if checkbox is checked
input[type=checkbox]:checked + .label_hide_if_checked, input[type=checkbox]:checked + .label_hide_if_not_checked + .label_hide_if_checked {
display:none;
}
// Hide element if checkbox is not checked
input[type=checkbox]:not(:checked) + .label_hide_if_not_checked, input[type=checkbox]:not(:checked) + .label_hide_if_checked + .label_hide_if_not_checked {
display:none;
}

View File

@ -0,0 +1,8 @@
// display cursor
.cursor-text {
cursor: text !important;
}
.cursor-pointer {
cursor: pointer !important;
}

View File

@ -12,7 +12,11 @@
<title>{% block title %}{% endblock %}{{ instance_name }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" type="text/css" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/oscar.min.css') }}" type="text/css" />
{% if cookies['oscar-style'] %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/'+cookies['oscar-style']+'.min.css') }}" type="text/css" />
{% else %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/logicodev.min.css') }}" type="text/css" />
{% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/leaflet.min.css') }}" type="text/css" />
{% for css in styles %}
<link rel="stylesheet" href="{{ url_for('static', filename=css) }}" type="text/css" />
@ -48,8 +52,8 @@
</noscript>
</head>
<body>
<div class="container">
{% include 'oscar/navbar.html' %}
<div class="container">
{% block site_alert_error %}
{% endblock %}
@ -75,7 +79,7 @@
<div class="container">
{% block footer %}
{% endblock %}
<p class="text-muted">{{ _('Powered by') }} <a href="https://asciimoo.github.io/searx/">searx</a> - {{ searx_version }} - {{ _('a privacy-respecting, hackable metasearch engine') }}</p>
<p class="text-muted"><small>{{ _('Powered by') }} <a href="https://asciimoo.github.io/searx/">searx</a> - {{ searx_version }} - {{ _('a privacy-respecting, hackable metasearch engine') }}</small></p>
</div>
</div>
<script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}"></script>

View File

@ -3,7 +3,14 @@
<div class="container-fluid">
<div class="row">
<div class="text-center col-sm-12 col-md-12">
<h1 class="text-hide center-block"><img class="center-block img-responsive" src="{{ url_for('static', filename='img/searx_logo.png') }}" alt="searx logo"/>searx</h1>
{% if cookies['oscar-style'] == 'pointhi' %}
<h1 class="text-hide center-block"><img class="center-block img-responsive" src="{{ url_for('static', filename='img/searx_logo.png') }}" alt="searx logo"/>searx</h1>
{% else %}
<h1 class="text-hide center-block" id="main-logo">
<img class="center-block img-responsive" src="{{ url_for('static', filename='img/logo_searx_a.png') }}" alt="searx logo" />
searx
</h1>
{% endif %}
</div>
</div>
<div class="row">

View File

@ -17,7 +17,6 @@
<!-- Draw result sub header -->
{% macro result_sub_header(result) -%}
{% if result.publishedDate %}<time class="text-muted" datetime="{{ result.pubdate }}" >{{ result.publishedDate }}</time>{% endif %}
<small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ icon('link') }} {{ _('cached') }}</a></small>
{% if result.magnetlink %}<small> &bull; <a href="{{ result.magnetlink }}" class="magnetlink">{{ icon('magnet') }} {{ _('magnet link') }}</a></small>{% endif %}
{% if result.torrentfile %}<small> &bull; <a href="{{ result.torrentfile }}" class="torrentfile" rel="noreferrer">{{ icon('download-alt') }} {{ _('torrent file') }}</a></small>{% endif %}
{%- endmacro %}
@ -29,15 +28,19 @@
{% for engine in result.engines %}
<span class="label label-default">{{ engine }}</span>
{% endfor %}
<small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ icon('link') }} {{ _('cached') }}</a></small>
</div>
<p class="text-muted">{{ result.pretty_url }}</p>
<div class="text-muted"><small>{{ result.pretty_url }}</small></div>
{%- endmacro %}
<!-- Draw result footer -->
{% macro result_footer_rtl(result) -%}
<div class="clearfix"></div>
<span class="label label-default pull-left">{{ result.engine }}</span>
<p class="text-muted">{{ result.pretty_url }}&lrm;</p>
{% for engine in result.engines %}
<span class="label label-default">{{ engine }}</span>
{% endfor %}
<small><a class="text-info" href="https://web.archive.org/web/{{ result.url }}" rel="noreferrer">{{ icon('link') }} {{ _('cached') }}</a></small>
<div class="text-muted"><small>{{ result.pretty_url }}</small></div>
{%- endmacro %}
{% macro preferences_item_header(info, label, rtl) -%}

View File

@ -110,6 +110,13 @@
{% endfor %}
</select>
{{ preferences_item_footer(theme_info, theme_label, rtl) }}
{{ preferences_item_header(_('Choose style for this theme'), _('Style'), rtl) }}
<select class="form-control" name='oscar-style'>
<option value="logicodev" >Logicodev</option>
<option value="pointhi" {% if cookies['oscar-style'] == 'pointhi' %}selected="selected"{% endif %}>Pointhi</option>
</select>
{{ preferences_item_footer(_('Choose style for this theme'), _('Style'), rtl) }}
</div>
</fieldset>
</div>

View File

@ -78,6 +78,16 @@
</div><!-- /#main_results -->
<div class="col-sm-4" id="sidebar_results">
{% if number_of_results != '0' %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">{{ _('Number of results') }}</h4>
</div>
<div class="panel-body">
{{ number_of_results }}
</div>
</div>
{% endif %}
{% if infoboxes %}
{% for infobox in infoboxes %}
{% include 'oscar/infobox.html' %}

View File

@ -18,8 +18,8 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
# version of searx
VERSION_MAJOR = 0
VERSION_MINOR = 8
VERSION_BUILD = 1
VERSION_MINOR = 9
VERSION_BUILD = 0
VERSION_STRING = "{0}.{1}.{2}".format(VERSION_MAJOR,
VERSION_MINOR,

View File

@ -48,7 +48,8 @@ from flask import (
Flask, request, render_template, url_for, Response, make_response,
redirect, send_from_directory
)
from flask.ext.babel import Babel, gettext, format_date
from flask.ext.babel import Babel, gettext, format_date, format_decimal
from flask.json import jsonify
from searx import settings, searx_dir
from searx.engines import (
categories, engines, get_engines_stats, engine_shortcuts
@ -64,7 +65,7 @@ from searx.search import Search
from searx.query import Query
from searx.autocomplete import searx_bang, backends as autocomplete_backends
from searx.plugins import plugins
from searx.preferences import Preferences
from searx.preferences import Preferences, ValidationException
# check if the pyopenssl, ndg-httpsclient, pyasn1 packages are installed.
# They are needed for SSL connection without trouble, see #298
@ -419,6 +420,7 @@ def index():
if search.request_data.get('format') == 'json':
return Response(json.dumps({'query': search.query,
'number_of_results': search.result_container.number_of_results,
'results': search.result_container.get_ordered_results()}),
mimetype='application/json')
elif search.request_data.get('format') == 'csv':
@ -438,7 +440,7 @@ def index():
'opensearch_response_rss.xml',
results=search.result_container.get_ordered_results(),
q=search.request_data['q'],
number_of_results=search.result_container.results_length(),
number_of_results=search.result_container.number_of_results,
base_url=get_base_url()
)
return Response(response_rss, mimetype='text/xml')
@ -449,6 +451,7 @@ def index():
q=search.request_data['q'],
selected_categories=search.categories,
paging=search.paging,
number_of_results=format_decimal(search.result_container.number_of_results),
pageno=search.pageno,
base_url=get_base_url(),
suggestions=search.result_container.suggestions,
@ -685,6 +688,24 @@ def clear_cookies():
return resp
@app.route('/config')
def config():
return jsonify({'categories': categories.keys(),
'engines': [{'name': engine_name,
'categories': engine.categories,
'enabled': not engine.disabled}
for engine_name, engine in engines.items()],
'plugins': [{'name': plugin.name,
'enabled': plugin.default_on}
for plugin in plugins],
'instance_name': settings['general']['instance_name'],
'locales': settings['locales'],
'default_locale': settings['ui']['default_locale'],
'autocomplete': settings['search']['autocomplete'],
'safe_search': settings['search']['safe_search'],
'default_theme': settings['ui']['default_theme']})
def run():
app.run(
debug=settings['general']['debug'],

View File

@ -53,6 +53,8 @@ setup(
'searx': [
'settings.yml',
'../README.rst',
'../requirements.txt',
'../requirements-dev.txt',
'data/*',
'plugins/*/*',
'static/*.*',

View File

@ -14,23 +14,19 @@ class TestCurrencyConvertEngine(SearxTestCase):
params = currency_convert.request(query, dicto)
self.assertNotIn('url', params)
query = '1.1.1 EUR in USD'
params = currency_convert.request(query, dicto)
self.assertNotIn('url', params)
query = '10 eur in usd'
query = 'convert 10 Pound Sterlings to United States Dollars'
params = currency_convert.request(query, dicto)
self.assertIn('url', params)
self.assertIn('finance.yahoo.com', params['url'])
self.assertIn('EUR', params['url'])
self.assertIn('GBP', params['url'])
self.assertIn('USD', params['url'])
def test_response(self):
dicto = defaultdict(dict)
dicto['ammount'] = float(10)
dicto['from'] = "EUR"
dicto['from'] = "GBP"
dicto['to'] = "USD"
dicto['from_name'] = "euro"
dicto['from_name'] = "pound sterling"
dicto['to_name'] = "United States dollar"
response = mock.Mock(text='a,b,c,d', search_params=dicto)
self.assertEqual(currency_convert.response(response), [])
@ -40,7 +36,8 @@ class TestCurrencyConvertEngine(SearxTestCase):
results = currency_convert.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 1)
self.assertEqual(results[0]['answer'], '10.0 EUR = 5.0 USD, 1 EUR (euro) = 0.5 USD (United States dollar)')
self.assertEqual(results[0]['answer'], '10.0 GBP = 5.0 USD, 1 GBP (pound sterling)' +
' = 0.5 USD (United States dollar)')
now_date = datetime.now().strftime('%Y%m%d')
self.assertEqual(results[0]['url'], 'https://finance.yahoo.com/currency/converter-results/' +
now_date + '/10.0-eur-to-usd.html')
now_date + '/10.0-gbp-to-usd.html')