Commit 22e7c1e2 authored by Johnny Kalajdzic's avatar Johnny Kalajdzic

Merge branch 'eelo_theme' into 'master'

Eelo theme

See merge request spot/my-spot!2
parents c1890578 e7b0883e
......@@ -14,6 +14,36 @@ See the `documentation <https://asciimoo.github.io/searx>`__ and the `wiki <http
|OpenCollective searx backers|
|OpenCollective searx sponsors|
Setup MySql
~~~~~~~~~~~
**Install MySql**
``$ sudo apt-get install mysql-server
$ pip install mymysql``
**Start MySql**
``$ sudo service mysql start
$ mysql -u root -p``
**Create a new database and give all rights to a new searx user**
change password!
``mysql> create database searx;
mysql> create user "searx"@"localhost" identified by "password";
mysql> grant all on searx.* to "searx"@"localhost" identified by "password";``
**You can now quit the MySql console by typing ``quit`` and connect as searx user**
``$ mysql -u searx -p``
**Here are some commands to init the database**
``mysql> use searx;``
``mysql> create table SEARCH_HISTORY(QUERY varchar(512), CATEGORIY varchar(256), PAGENO int(11), PAGING tinyint(1), SAFE_SEARCH int(11), LANGUAGE varchar(8), TIME_RANGE varchar(16), ENGINES varchar(4096), RESULTS mediumtext), RESULTS_NUMBER int(11), ANSWERS varchar(2048), CORRECTIONS varchar(256), INFOBOXES varchar(8192), SUGGESTIONS varchar(1024), UNRESPONSIVE_ENGINES varchar(1024));``
``mysql> quit``
MySql is done !
Installation
~~~~~~~~~~~~
......@@ -22,7 +52,7 @@ Installation
- install dependencies: ``./manage.sh update_packages``
- edit your
`settings.yml <https://github.com/asciimoo/searx/blob/master/searx/settings.yml>`__
(set your ``secret_key``!)
(set your ``secret_key`` and ``mysql password``!)
- run ``python searx/webapp.py`` to start the application
For all the details, follow this `step by step
......
......@@ -224,7 +224,7 @@ def https_url_rewrite(result):
return result
def on_result(request, search, result):
def on_result(request, searchData, result):
if result['parsed_url'].scheme == 'http':
https_url_rewrite(result)
return True
......
......@@ -33,7 +33,7 @@ def get_doi_resolver(args, preference_doi_resolver):
return doi_resolver
def on_result(request, search, result):
def on_result(request, searchData, result):
doi = extract_doi(result['parsed_url'])
if doi and len(doi) < 50:
for suffix in ('/', '.pdf', '/full', '/meta', '/abstract'):
......
......@@ -28,19 +28,19 @@ p = re.compile(b'.*user[ -]agent.*', re.IGNORECASE)
# attach callback to the post search hook
# request: flask request object
# ctx: the whole local context of the pre search hook
def post_search(request, search):
if search.search_query.pageno > 1:
def post_search(request, searchData):
if searchData.pageno > 1:
return True
if search.search_query.query == b'ip':
if searchData.query == b'ip':
x_forwarded_for = request.headers.getlist("X-Forwarded-For")
if x_forwarded_for:
ip = x_forwarded_for[0]
else:
ip = request.remote_addr
search.result_container.answers.clear()
search.result_container.answers.add(ip)
elif p.match(search.search_query.query):
searchData.answers.clear()
searchData.answers.add(ip)
elif p.match(searchData.query):
ua = request.user_agent
search.result_container.answers.clear()
search.result_container.answers.add(ua)
searchData.answers.clear()
searchData.answers.add(ua)
return True
......@@ -29,7 +29,7 @@ default_on = True
preference_section = 'privacy'
def on_result(request, search, result):
def on_result(request, searchData, result):
query = result['parsed_url'].query
if query == "":
......
......@@ -164,7 +164,7 @@ class SearchQuery(object):
"""container for all the search parameters (query, language, etc...)"""
def __init__(self, query, engines, categories, lang, safesearch, pageno, time_range):
self.query = query.encode('utf-8')
self.query = query
self.engines = engines
self.categories = categories
self.lang = lang
......
......@@ -20,19 +20,21 @@ import sys
import threading
from time import time
from uuid import uuid4
from flask_babel import gettext
import requests.exceptions
from flask_babel import gettext
import searx.poolrequests as requests_lib
from searx import logger
from searx.answerers import ask
from searx.engines import (
categories, engines, settings
)
from searx.answerers import ask
from searx.utils import gen_useragent
from searx.exceptions import SearxParameterException
from searx.plugins import plugins
from searx.query import RawTextQuery, SearchQuery, VALID_LANGUAGE_CODE
from searx.results import ResultContainer
from searx import logger
from searx.plugins import plugins
from searx.exceptions import SearxParameterException
from searx.utils import gen_useragent
try:
from thread import start_new_thread
......@@ -202,7 +204,7 @@ def get_search_query_from_webapp(preferences, form):
raw_text_query.parse_query()
# set query
query = raw_text_query.getSearchQuery()
query = raw_text_query.getSearchQuery().encode('utf-8')
# get and check page number
pageno_param = form.get('pageno', '1')
......@@ -253,81 +255,19 @@ def get_search_query_from_webapp(preferences, form):
query_engines = raw_text_query.engines
# query_categories
query_categories = []
# if engines are calculated from query,
# set categories by using that informations
if query_engines and raw_text_query.specific:
additional_categories = set()
for engine in query_engines:
if 'from_bang' in engine and engine['from_bang']:
additional_categories.add('none')
else:
additional_categories.add(engine['category'])
query_categories = list(additional_categories)
query_category = form.get('category')
if query_category is None:
query_category = 'general'
# otherwise, using defined categories to
# calculate which engines should be used
else:
# set categories/engines
load_default_categories = True
for pd_name, pd in form.items():
if pd_name == 'categories':
query_categories.extend(categ for categ in map(unicode.strip, pd.split(',')) if categ in categories)
elif pd_name == 'engines':
pd_engines = [{'category': engines[engine].categories[0],
'name': engine}
for engine in map(unicode.strip, pd.split(',')) if engine in engines]
if pd_engines:
query_engines.extend(pd_engines)
load_default_categories = False
elif pd_name.startswith('category_'):
category = pd_name[9:]
# if category is not found in list, skip
if category not in categories:
continue
if pd != 'off':
# add category to list
query_categories.append(category)
elif category in query_categories:
# remove category from list if property is set to 'off'
query_categories.remove(category)
if not load_default_categories:
if not query_categories:
query_categories = list(set(engine['category']
for engine in query_engines))
else:
# if no category is specified for this search,
# using user-defined default-configuration which
# (is stored in cookie)
if not query_categories:
cookie_categories = preferences.get_value('categories')
for ccateg in cookie_categories:
if ccateg in categories:
query_categories.append(ccateg)
# if still no category is specified, using general
# as default-category
if not query_categories:
query_categories = ['general']
# using all engines for that search, which are
# declared under the specific categories
for categ in query_categories:
query_engines.extend({'category': categ,
'name': engine.name}
for engine in categories[categ]
if (engine.name, categ) not in disabled_engines)
return SearchQuery(query, query_engines, query_categories,
query_lang, query_safesearch, query_pageno, query_time_range)
for engine in categories[query_category]:
if (engine.name, query_category) not in disabled_engines:
query_engines.append({'category': query_category, 'name': engine.name})
return SearchQuery(query, query_engines, [query_category], query_lang, query_safesearch, query_pageno,
query_time_range)
class Search(object):
class Search(object):
"""Search information container"""
def __init__(self, search_query):
......@@ -417,7 +357,6 @@ class Search(object):
class SearchWithPlugins(Search):
"""Similar to the Search class but call the plugins."""
def __init__(self, search_query, ordered_plugin_list, request):
......
import json
import threading
import urllib
import pymysql
from searx.plugins import plugins
from searx.query import SearchQuery
from searx.search import Search, get_search_query_from_webapp
from searx.url_utils import urlparse
settings = None
class SearchData(object):
def __init__(self, search_query, results, paging,
results_number, answers, corrections, infoboxes, suggestions, unresponsive_engines):
self.categories = search_query.categories
self.query = search_query.query
self.pageno = search_query.pageno
self.safe_search = search_query.safesearch
self.language = search_query.lang
self.time_range = search_query.time_range
self.engines = search_query.engines
self.results = results
self.paging = paging
self.results_number = results_number
self.answers = answers
self.corrections = corrections
self.infoboxes = infoboxes
self.suggestions = suggestions
self.unresponsive_engines = unresponsive_engines
def read(q):
time_range = q.time_range
if q.time_range is None:
q.time_range = ""
connection = pymysql.connect(host=settings['host'], user=settings['user'], password=settings['password'],
database=settings['database'])
try:
with connection.cursor() as cursor:
sql = "SELECT RESULTS, PAGING, RESULTS_NUMBER, ANSWERS, CORRECTIONS, INFOBOXES, SUGGESTIONS, " \
"UNRESPONSIVE_ENGINES FROM SEARCH_HISTORY WHERE QUERY='%s' AND CATEGORY='%s' AND PAGENO=%s AND " \
"SAFE_SEARCH=%s AND LANGUAGE='%s' AND TIME_RANGE='%s' AND ENGINES='%s'"
cursor.execute(
sql % (e(q.query), q.categories[0], q.pageno, q.safesearch, q.lang, time_range, je(q.engines)))
for response in cursor:
results = jd(response[0])
for result in results:
result['parsed_url'] = urlparse(result['url'])
return SearchData(q, results, response[1] != 0, response[2], jds(response[3]),
jds(response[4]), jd(response[5]), jds(response[6]), jds(response[7]))
finally:
connection.close()
return None
def save(d):
connection = pymysql.connect(host=settings['host'], user=settings['user'], password=settings['password'],
database=settings['database'])
try:
with connection.cursor() as cursor:
sql = "INSERT INTO SEARCH_HISTORY(QUERY, CATEGORY, PAGENO, SAFE_SEARCH, LANGUAGE, TIME_RANGE, ENGINES, " \
"RESULTS, PAGING, RESULTS_NUMBER, ANSWERS, CORRECTIONS, INFOBOXES, SUGGESTIONS, " \
"UNRESPONSIVE_ENGINES) VALUES('%s', '%s', %s, %s, '%s', '%s', '%s', '%s', %s, %s, '%s', '%s', '%s'," \
" '%s', '%s')"
cursor.execute(sql % (e(d.query), d.categories[0], d.pageno, d.safe_search, d.language, d.time_range,
je(d.engines), je(d.results), d.paging, d.results_number, jes(d.answers),
jes(d.corrections), je(d.infoboxes), jes(d.suggestions), jes(d.unresponsive_engines)))
connection.commit()
finally:
connection.close()
def get_twenty_queries(x):
result = []
connection = pymysql.connect(host=settings['host'], user=settings['user'], password=settings['password'],
database=settings['database'])
try:
with connection.cursor() as cursor:
cursor.execute("SELECT QUERY, ENGINES, CATEGORY, LANGUAGE , SAFE_SEARCH, PAGENO, TIME_RANGE FROM "
"SEARCH_HISTORY LIMIT %s,20" % x)
for row in cursor:
result.append(SearchQuery(d(row[0]), jd(row[1]), [row[2]], row[3], row[4], row[5], row[6]))
finally:
connection.close()
return result
def e(obj):
return urllib.quote_plus(obj)
def d(coded):
return urllib.unquote_plus(coded)
def je(obj):
return e(json.dumps(obj))
def jd(coded):
return json.loads(d(coded))
def jes(set):
return je(list(set))
def jds(coded):
return set(jd(coded))
def get_search_data(q, r):
results_number = r.results_number()
if results_number < r.results_length():
results_number = 0
results = r.get_ordered_results()
for result in results:
result['engines'] = list(result['engines'])
if not type(result['engines']) is list:
print(result['engines'])
if 'publishedDate' in result:
try:
result['pubdate'] = result['publishedDate'].strftime('%Y-%m-%d %H:%M:%S')
finally:
result['publishedDate'] = None
if q.time_range is None:
q.time_range = ""
return SearchData(q, results, r.paging, results_number, r.answers, r.corrections,
r.infoboxes, r.suggestions, r.unresponsive_engines)
def search(request):
search_query = get_search_query_from_webapp(request.preferences, request.form)
searchData = read(search_query)
if searchData is None:
result_container = Search(search_query).search()
searchData = get_search_data(search_query, result_container)
threading.Thread(target=save, args=(searchData,), name='save_search_' + str(searchData)).start()
ordered_plugin = request.user_plugins
plugins.call(ordered_plugin, 'post_search', request, searchData)
for result in searchData.results:
plugins.call(ordered_plugin, 'on_result', request, searchData, result)
return searchData
def update(d):
connection = pymysql.connect(host=settings['host'], user=settings['user'], password=settings['password'],
database=settings['database'])
try:
with connection.cursor() as cursor:
sql = "UPDATE SEARCH_HISTORY SET RESULTS='%s', PAGING=%s, RESULTS_NUMBER=%s, ANSWERS='%s', CORRECTIONS='%s', INFOBOXES='%s', SUGGESTIONS='%s', " \
"UNRESPONSIVE_ENGINES='%s' WHERE QUERY='%s' AND CATEGORY='%s' AND PAGENO=%s AND " \
"SAFE_SEARCH=%s AND LANGUAGE='%s' AND TIME_RANGE='%s' AND ENGINES='%s'"
cursor.execute(sql % (je(d.results), d.paging, d.results_number, jes(d.answers), jes(d.corrections),
je(d.infoboxes), jes(d.suggestions), jes(d.unresponsive_engines),
e(d.query), d.categories[0], d.pageno, d.safe_search, d.language, d.time_range,
je(d.engines)))
connection.commit()
finally:
connection.close()
general:
debug : debug # Debug mode, only for development
debug : False # Debug mode, only for development
instance_name : "eelo" # displayed name
search:
safe_search : 0 # Filter results. 0: None, 1: Moderate, 2: Strict
autocomplete : "" # Existing autocomplete backends: "dbpedia", "duckduckgo", "google", "startpage", "wikipedia" - leave blank to turn it off by default
safe_search : 2 # Filter results. 0: None, 1: Moderate, 2: Strict
autocomplete : "duckduckgo" # Existing autocomplete backends: "dbpedia", "duckduckgo", "google", "startpage", "wikipedia" - leave blank to turn it off by default
language : "en-US"
server:
......@@ -15,6 +15,13 @@ server:
image_proxy : False # Proxying image results through searx
http_protocol_version : "1.0" # 1.0 and 1.1 are supported
mysql:
host : "127.0.0.1"
user : "searx"
password : "password" # change this!
database : "searx"
upgrade_history: 86400 # in seconds (1day = 86400s)
ui:
static_path : "" # Custom static path - leave it blank if you didn't change
templates_path : "" # Custom templates path - leave it blank if you didn't change
......
......@@ -11,6 +11,10 @@ a {
#search_form #search_input_container {
transition: border-color 0.2s ease-in-out;
}
.fill-transition,
.checkmark .icon {
transition: fill 0.2s ease-in-out;
}
.disabled,
[disabled],
#preferences #engines .engine input:checked ~ .data {
......@@ -86,6 +90,9 @@ a {
max-width: 100%;
width: 100%;
}
main {
margin-top: 16px;
}
#search_input_container {
margin-left: 16px;
margin-right: 16px;
......@@ -179,6 +186,27 @@ input.btn[type="submit"] {
color: rgba(255, 255, 255, 0.8);
background-color: rgba(0, 0, 0, 0.5);
}
.checkmark .icon {
fill: #60686f;
}
.checkmark .icon.checked {
fill: #5068dd;
}
.checkmark_input {
display: none;
}
.checkmark_input.inverted:checked + .checkmark .checked {
display: none;
}
.checkmark_input.inverted:checked + .checkmark .unchecked {
display: initial;
}
.checkmark_input.inverted:not(:checked) + .checkmark .checked {
display: initial;
}
.checkmark_input.inverted:not(:checked) + .checkmark .unchecked {
display: none;
}
.custom-select {
appearance: none;
-webkit-appearance: none;
......@@ -250,23 +278,6 @@ fieldset {
border: none;
padding: 0px;
}
nav {
height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
text-transform: capitalize;
margin-bottom: 24px;
}
nav a {
color: #868686;
}
nav a:hover {
color: #5068dd;
}
nav #eelo_links > * {
margin-right: 32px;
}
footer {
position: absolute;
bottom: 0;
......@@ -332,6 +343,83 @@ footer {
display: inline-block;
margin-right: 8px;
}
nav {
height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
text-transform: capitalize;
margin-bottom: 24px;
}
nav a {
color: #868686;
}
nav a:hover {
color: #5068dd;
}
nav #eelo_links > * {
margin-right: 32px;
}
#nav_toggle_btn {
display: none;
}
@media screen and (max-width: 600px) {
nav {
position: fixed;
right: 0;
top: 0;
flex-direction: column;
height: 100%;
background-color: white;
z-index: 1000;
width: 280px;
min-width: 280px;
max-width: 280px;
align-items: unset;
box-shadow: 0 0 0 rgba(0, 0, 0, 0.2);
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
padding: 0;
}
nav > :first-child {
margin-top: 8px;
}
nav > :last-child {
margin-bottom: 8px;
}
nav a {
display: block;
padding: 8px 16px;
}
#nav_toggle_btn {
display: block;
position: absolute;
top: 24px;
right: 8px;
z-index: 1001;
}
#nav_toggle:checked ~ #nav_toggle_btn > .menu-open {
display: none;
}
#nav_toggle:checked ~ #nav_toggle_btn > .menu-close {
display: initial;
}
#nav_toggle:checked ~ nav {
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
transform: translateX(0);
}
#nav_toggle:checked ~ #nav_toggle_btn {
position: fixed;
}
#nav_toggle:not(:checked) ~ #nav_toggle_btn > .menu-open {
display: initial;
}
#nav_toggle:not(:checked) ~ #nav_toggle_btn > .menu-close {
display: none;
}
#nav_toggle:not(:checked) ~ nav {
transform: translateX(100%);
}
}
#search_form #search_input_container {
position: relative;
display: flex;
......@@ -350,7 +438,11 @@ footer {
height: 100%;
width: 100%;
min-width: 0;
margin-left: 16px;
padding-left: 16px;
background-color: transparent;
}
#search_form #search_input_container #q:focus {
outline: none;
}
#search_form #search_input_container button[type="submit"] {
position: relative;
......@@ -379,16 +471,41 @@ footer {
}
#search_form #search_input_container.rtl #q {
margin-left: 0;
margin-right: 16px;
padding-right: 16px;
}
#search_form #search_input_container.rtl #logo_link {
padding-left: 0;
padding-right: 4px;
}
#search_form .twitter-typeahead {
position: unset !important;
background-color: transparent;
width: 100%;
height: 100%;
}
#search_form input.tt-hint {
display: none;
}
#search_form .tt-dropdown-menu {
position: absolute;
left: 0;
right: 0 !important;
top: calc(116%) !important;
border-radius: 8px;
border: 2px solid rgba(0, 0, 0, 0.2);
width: 100% !important;
box-sizing: border-box !important;
background-color: #eeeeee;
padding-left: 16px;
padding-right: 16px;
}
#search_form.inline-search .tt-dropdown-menu {
padding-left: 52px;
}
#search_form #search_params {
display: flex;
justify-content: space-between;
font-size: 11px;
font-size: 14px;
padding-bottom: 16px;
padding-top: 16px;
padding-left: 24px;
......@@ -403,8 +520,11 @@ footer {
text-align: right;
padding-right: 32px;
}
#search_form #search_params #time_and_lang > :first-child {
margin-right: 16px;
#search_form #search_params #time_and_lang {
text-align: right;
}
#search_form #search_params #time_and_lang > * {
width: 160px;
}
#search_form .search_categories,
#search_form #categories {
......@@ -412,30 +532,102 @@ footer {
display: flex;
flex-wrap: wrap;
align-items: center;
position: relative;
flex: 100%;
}
#search_form .search_categories label,
#search_form #categories label {
flex-grow: 1;
flex-basis: auto;
font-size: 14px;
font-weight: normal;
margin-right: 16px;
}
#search_form .search_categories input[type="checkbox"]:checked + label,
#search_form #categories input[type="checkbox"]:checked + label {
#search_form .search_categories input[type="radio"]:checked + label,
#search_form #categories input[type="radio"]:checked + label {
color: #5068dd;
font-weight: bold;
}
#search_form #more_categories {
position: absolute;
top: 24px;
display: flex;
flex-direction: column;
border-radius: 8px;
border: 2px solid rgba(0, 0, 0, 0.2);
background-color: #eeeeee;
padding-bottom: 16px;
left: 0;
z-index: 998;
}
#search_form #more_categories > label {
padding: 16px;
padding-bottom: 0;
white-space: nowrap;
}
#more_categories_container {
border-left: 1px solid rgba(0, 0, 0, 0.24);
padding-left: 16px;
display: flex;
align-items: center;
position: relative;